diff --git a/.azure/lint-linux.yml b/.azure/lint-linux.yml index 532c2072ed29..5855fe1e6e67 100644 --- a/.azure/lint-linux.yml +++ b/.azure/lint-linux.yml @@ -26,7 +26,10 @@ jobs: -c constraints.txt \ -r requirements.txt \ -r requirements-dev.txt \ + ./qiskit_pkg \ -e . + # Build and install both qiskit and qiskit-terra so that any optionals + # depending on `qiskit` will resolve correctly. displayName: 'Install dependencies' env: SETUPTOOLS_ENABLE_FEATURES: "legacy-editable" diff --git a/.azure/test-linux.yml b/.azure/test-linux.yml index c97d4f693975..eb456f8497ad 100644 --- a/.azure/test-linux.yml +++ b/.azure/test-linux.yml @@ -29,6 +29,7 @@ jobs: QISKIT_SUPPRESS_PACKAGING_WARNINGS: Y PIP_CACHE_DIR: $(Pipeline.Workspace)/.pip QISKIT_TEST_CAPTURE_STREAMS: 1 + HAVE_VISUAL_TESTS_RUN: false steps: - task: UsePythonVersion@0 @@ -70,15 +71,17 @@ jobs: # Use stable Rust, rather than MSRV, to spot-check that stable builds properly. rustup override set stable source test-job/bin/activate - python -m pip install -U pip setuptools wheel - # Install setuptools-rust for building sdist - python -m pip install -U -c constraints.txt setuptools-rust - python setup.py sdist + python -m pip install -U pip + python -m pip install -U build + python -m build --sdist . + python -m build --sdist qiskit_pkg python -m pip install -U \ -c constraints.txt \ -r requirements.txt \ -r requirements-dev.txt \ - dist/qiskit-terra*.tar.gz + dist/qiskit*.tar.gz + # Build and install both qiskit and qiskit-terra so that any optionals + # depending on `qiskit` will resolve correctly. displayName: "Install Terra from sdist" - ${{ if eq(parameters.installFromSdist, false) }}: @@ -89,7 +92,10 @@ jobs: -c constraints.txt \ -r requirements.txt \ -r requirements-dev.txt \ + ./qiskit_pkg \ -e . + # Build and install both qiskit and qiskit-terra so that any optionals + # depending on `qiskit` will resolve correctly. displayName: "Install Terra directly" env: SETUPTOOLS_ENABLE_FEATURES: "legacy-editable" @@ -197,7 +203,9 @@ jobs: env: SETUPTOOLS_ENABLE_FEATURES: "legacy-editable" - - bash: image_tests/bin/python -m unittest discover -v test/visual + - bash: | + echo "##vso[task.setvariable variable=HAVE_VISUAL_TESTS_RUN;]true" + image_tests/bin/python -m unittest discover -v test/visual displayName: 'Run image test' env: # Needed to suppress a warning in jupyter-core 5.x by eagerly migrating to @@ -213,7 +221,7 @@ jobs: archiveType: tar archiveFile: '$(Build.ArtifactStagingDirectory)/visual_test_failures.tar.gz' verbose: true - condition: failed() + condition: and(failed(), eq(variables.HAVE_VISUAL_TESTS_RUN, 'true')) - task: ArchiveFiles@2 displayName: Archive circuit results @@ -222,7 +230,7 @@ jobs: archiveType: tar archiveFile: '$(Build.ArtifactStagingDirectory)/circuit_results.tar.gz' verbose: true - condition: failed() + condition: and(failed(), eq(variables.HAVE_VISUAL_TESTS_RUN, 'true')) - task: ArchiveFiles@2 displayName: Archive graph results @@ -231,7 +239,7 @@ jobs: archiveType: tar archiveFile: '$(Build.ArtifactStagingDirectory)/graph_results.tar.gz' verbose: true - condition: failed() + condition: and(failed(), eq(variables.HAVE_VISUAL_TESTS_RUN, 'true')) - task: PublishBuildArtifacts@1 displayName: 'Publish image test failure diffs' @@ -240,4 +248,4 @@ jobs: artifactName: 'image_test_failure_img_diffs' Parallel: true ParallelCount: 8 - condition: failed() + condition: and(failed(), eq(variables.HAVE_VISUAL_TESTS_RUN, 'true')) diff --git a/.azure/test-macos.yml b/.azure/test-macos.yml index 2b195afbdae3..cbf2fc8b0e08 100644 --- a/.azure/test-macos.yml +++ b/.azure/test-macos.yml @@ -43,7 +43,10 @@ jobs: -c constraints.txt \ -r requirements.txt \ -r requirements-dev.txt \ + ./qiskit_pkg \ -e . + # Build and install both qiskit and qiskit-terra so that any optionals + # depending on `qiskit` will resolve correctly. pip check displayName: 'Install dependencies' env: diff --git a/.azure/test-windows.yml b/.azure/test-windows.yml index 30591a5dadbe..6ba2e442946c 100644 --- a/.azure/test-windows.yml +++ b/.azure/test-windows.yml @@ -42,7 +42,10 @@ jobs: -c constraints.txt \ -r requirements.txt \ -r requirements-dev.txt \ + ./qiskit_pkg \ -e . + # Build and install both qiskit and qiskit-terra so that any optionals + # depending on `qiskit` will resolve correctly. pip check displayName: 'Install dependencies' env: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index de3485c5fdce..221840b4e7c6 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -42,7 +42,7 @@ jobs: run: python -m pip install -c constraints.txt --upgrade pip setuptools wheel - name: Build and install qiskit-terra - run: python -m pip install -c constraints.txt -e . + run: python -m pip install -c constraints.txt -e . ./qiskit_pkg env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Cinstrument-coverage" diff --git a/.github/workflows/docs_deploy.yml b/.github/workflows/docs_deploy.yml index 9320fd298045..8f922121f938 100644 --- a/.github/workflows/docs_deploy.yml +++ b/.github/workflows/docs_deploy.yml @@ -217,7 +217,7 @@ jobs: - name: Deploy translations id: ssh_key - run: qiskit/tools/deploy_translatable_strings.sh + run: qiskit/tools/deploy_translatable_strings.sh "${{ github.workspace }}/deploy" env: encrypted_deploy_po_branch_key: ${{ secrets.ENCRYPTED_DEPLOY_PO_BRANCH_KEY }} encrypted_deploy_po_branch_iv: ${{ secrets.ENCRYPTED_DEPLOY_PO_BRANCH_IV }} diff --git a/.github/workflows/slow.yml b/.github/workflows/slow.yml index 62fa9ec19c62..528305740d95 100644 --- a/.github/workflows/slow.yml +++ b/.github/workflows/slow.yml @@ -19,7 +19,7 @@ jobs: python -m pip install -U pip setuptools wheel python -m pip install -U -r requirements.txt -c constraints.txt python -m pip install -U -r requirements-dev.txt -c constraints.txt - python -m pip install -c constraints.txt -e . + python -m pip install -c constraints.txt -e . ./qiskit_pkg python -m pip install "qiskit-aer" "z3-solver" "cplex" -c constraints.txt env: SETUPTOOLS_ENABLE_FEATURES: "legacy-editable" diff --git a/.mergify.yml b/.mergify.yml index 80346d9b03d0..0f32d2895772 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -6,4 +6,4 @@ pull_request_rules: actions: backport: branches: - - stable/0.25 + - stable/0.45 diff --git a/Cargo.lock b/Cargo.lock index 866c920c7ab6..221942d5c846 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,14 +4,15 @@ version = 3 [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -108,15 +109,21 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ "ahash", "allocator-api2", "rayon", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "indexmap" version = "1.9.3" @@ -129,20 +136,20 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad227c3af19d4914570ad36d30409928b75967c298feb9ea1969db3a610bb14e" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.2", "rayon", ] [[package]] name = "indoc" -version = "1.0.9" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "itertools" @@ -250,9 +257,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437213adf41bbccf4aeae535fbfcdad0f6fed241e1ae182ebe97fa1f3ce19389" +checksum = "bef41cbb417ea83b30525259e30ccef6af39b31c240bda578889494c5392d331" dependencies = [ "libc", "ndarray", @@ -320,22 +327,22 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b" dependencies = [ "cfg-if", - "hashbrown 0.14.0", - "indexmap 2.0.1", + "hashbrown 0.14.2", + "indexmap 2.1.0", "indoc", "libc", "memoffset", @@ -350,9 +357,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5" dependencies = [ "once_cell", "target-lexicon", @@ -360,9 +367,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b" dependencies = [ "libc", "pyo3-build-config", @@ -370,9 +377,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -382,10 +389,11 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424" dependencies = [ + "heck", "proc-macro2", "quote", "syn", @@ -393,19 +401,19 @@ dependencies = [ [[package]] name = "qiskit-qasm2" -version = "0.45.0" +version = "1.0.0" dependencies = [ - "hashbrown 0.14.0", + "hashbrown 0.14.2", "pyo3", ] [[package]] name = "qiskit_accelerate" -version = "0.45.0" +version = "1.0.0" dependencies = [ "ahash", - "hashbrown 0.14.0", - "indexmap 2.0.1", + "hashbrown 0.14.2", + "indexmap 2.1.0", "ndarray", "num-bigint", "num-complex", @@ -537,8 +545,8 @@ checksum = "72abf7976bc09a30391248b3c6509338b235c02b0e9b0bf8af686c289cad3f45" dependencies = [ "ahash", "fixedbitset", - "hashbrown 0.14.0", - "indexmap 2.0.1", + "hashbrown 0.14.2", + "indexmap 2.1.0", "num-traits", "petgraph", "priority-queue", @@ -556,15 +564,15 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -585,9 +593,9 @@ checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unindent" -version = "0.1.11" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "version_check" @@ -657,3 +665,23 @@ name = "windows_x86_64_msvc" version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" + +[[package]] +name = "zerocopy" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69c48d63854f77746c68a5fbb4aa17f3997ece1cb301689a257af8cb80610d21" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c258c1040279e4f88763a113de72ce32dde2d50e2a94573f15dd534cea36a16d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 1f3b4f7c344f..02e453ce587b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = ["crates/*"] [workspace.package] -version = "0.45.0" +version = "1.0.0" edition = "2021" rust-version = "1.64" # Keep in sync with README.md and rust-toolchain.toml. license = "Apache-2.0" @@ -10,7 +10,7 @@ license = "Apache-2.0" [workspace.dependencies] # This doesn't set `extension-module` as a shared feature because we need to be able to disable it # during Rust-only testing (see # https://github.com/PyO3/pyo3/issues/340). -pyo3 = { version = "0.19.2", features = ["abi3-py38"] } +pyo3 = { version = "0.20.0", features = ["abi3-py38"] } [profile.release] lto = 'fat' diff --git a/asv.conf.json b/asv.conf.json index 70e736e26f75..64dfa188fae4 100644 --- a/asv.conf.json +++ b/asv.conf.json @@ -4,20 +4,19 @@ "project_url": "https://qiskit.org", "repo": ".", "install_command": [ - "in-dir={env_dir} python -mpip install {wheel_file}[all] python-constraint qiskit-experiments==0.3.0" + "in-dir={env_dir} python -m pip install {wheel_file}[all] python-constraint qiskit-experiments==0.3.0" ], "uninstall_command": [ - "return-code=any python -mpip uninstall -y {project}" + "return-code=any python -m pip uninstall -y qiskit qiskit-terra" ], "build_command": [ - "pip install -U setuptools-rust", - "python setup.py build_rust --release", - "PIP_NO_BUILD_ISOLATION=false python -mpip wheel --no-deps --no-index -w {build_cache_dir} {build_dir}" + "python -m pip install -U build", + "python -m build --outdir {build_cache_dir} --wheel {build_dir}" ], "branches": ["main"], "dvcs": "git", "environment_type": "virtualenv", - "show_commit_url": "http://github.com/Qiskit/qiskit-terra/commit/", + "show_commit_url": "http://github.com/Qiskit/qiskit/commit/", "pythons": ["3.8", "3.9", "3.10", "3.11"], "benchmark_dir": "test/benchmarks", "env_dir": ".asv/env", diff --git a/circuit.qpy b/circuit.qpy deleted file mode 100644 index ae72d271d09d..000000000000 Binary files a/circuit.qpy and /dev/null differ diff --git a/constraints.txt b/constraints.txt index 455610b304cf..07b9862e426c 100644 --- a/constraints.txt +++ b/constraints.txt @@ -11,3 +11,8 @@ numpy<1.25 # eigensystem code for one of the test cases. See # https://github.com/Qiskit/qiskit-terra/issues/10345 for current details. scipy<1.11 + +# Aer 0.13 causes several randomised tests to begin failing, and some +# `QuantumInstance` use of noise models to raise exceptions. These need fixes +# on Terra. +qiskit-aer==0.12.2 diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index 2051680d6598..f50d9deb594d 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -16,11 +16,11 @@ extension-module = ["pyo3/extension-module"] [dependencies] rayon = "1.8" -numpy = "0.19.0" +numpy = "0.20.0" rand = "0.8" rand_pcg = "0.3" rand_distr = "0.4.3" -ahash = "0.8.3" +ahash = "0.8.6" num-complex = "0.4" num-bigint = "0.4" rustworkx-core = "0.13" @@ -42,5 +42,5 @@ version = "0.14.0" features = ["rayon"] [dependencies.indexmap] -version = "2.0.1" +version = "2.1.0" features = ["rayon"] diff --git a/crates/accelerate/src/vf2_layout.rs b/crates/accelerate/src/vf2_layout.rs index 65817f4ac477..fe361b079410 100644 --- a/crates/accelerate/src/vf2_layout.rs +++ b/crates/accelerate/src/vf2_layout.rs @@ -10,8 +10,6 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use indexmap::IndexMap; - use numpy::PyReadonlyArray1; use pyo3::prelude::*; use pyo3::wrap_pyfunction; @@ -22,6 +20,19 @@ use crate::nlayout::{NLayout, VirtualQubit}; const PARALLEL_THRESHOLD: usize = 50; +#[pyclass] +pub struct EdgeList { + pub edge_list: Vec<([VirtualQubit; 2], i32)>, +} + +#[pymethods] +impl EdgeList { + #[new] + pub fn new(edge_list: Vec<([VirtualQubit; 2], i32)>) -> Self { + EdgeList { edge_list } + } +} + /// Score a given circuit with a layout applied #[pyfunction] #[pyo3( @@ -29,14 +40,14 @@ const PARALLEL_THRESHOLD: usize = 50; )] pub fn score_layout( bit_list: PyReadonlyArray1, - edge_list: IndexMap<[VirtualQubit; 2], i32>, + edge_list: &EdgeList, error_map: &ErrorMap, layout: &NLayout, strict_direction: bool, run_in_parallel: bool, ) -> PyResult { let bit_counts = bit_list.as_slice()?; - let edge_filter_map = |(index_arr, gate_count): (&[VirtualQubit; 2], &i32)| -> Option { + let edge_filter_map = |(index_arr, gate_count): &([VirtualQubit; 2], i32)| -> Option { let mut error = error_map .error_map .get(&[index_arr[0].to_phys(layout), index_arr[1].to_phys(layout)]); @@ -66,10 +77,18 @@ pub fn score_layout( }) }; - let mut fidelity: f64 = if edge_list.len() < PARALLEL_THRESHOLD || !run_in_parallel { - edge_list.iter().filter_map(edge_filter_map).product() + let mut fidelity: f64 = if edge_list.edge_list.len() < PARALLEL_THRESHOLD || !run_in_parallel { + edge_list + .edge_list + .iter() + .filter_map(edge_filter_map) + .product() } else { - edge_list.par_iter().filter_map(edge_filter_map).product() + edge_list + .edge_list + .par_iter() + .filter_map(edge_filter_map) + .product() }; fidelity *= if bit_list.len() < PARALLEL_THRESHOLD || !run_in_parallel { bit_counts @@ -90,5 +109,6 @@ pub fn score_layout( #[pymodule] pub fn vf2_layout(_py: Python, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(score_layout))?; + m.add_class::()?; Ok(()) } diff --git a/docs/apidoc/algorithms.rst b/docs/apidoc/algorithms.rst deleted file mode 100644 index 25d1bd4a412b..000000000000 --- a/docs/apidoc/algorithms.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-algorithms: - -.. automodule:: qiskit.algorithms - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst index 60c30286fa8c..e957cabb12f3 100644 --- a/docs/apidoc/index.rst +++ b/docs/apidoc/index.rst @@ -57,5 +57,4 @@ Deprecated Modules .. toctree:: :maxdepth: 1 - algorithms opflow diff --git a/docs/conf.py b/docs/conf.py index 58d5dc1b17bd..bd33981313a7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,9 +27,9 @@ author = "Qiskit Development Team" # The short X.Y version -version = "0.45" +version = "1.0" # The full version, including alpha/beta/rc tags -release = "0.45.0" +release = "1.0.0" language = "en" @@ -133,7 +133,7 @@ html_context = { # Enable segment analytics for qiskit.org/documentation "analytics_enabled": bool(os.getenv("QISKIT_ENABLE_ANALYTICS", "")), - "theme_announcement": "🎉 Starting on December 1, 2023, Qiskit Documentation will only live on IBM Quantum", + "theme_announcement": "🎉 Starting on November 29, 2023, Qiskit Documentation will only live on IBM Quantum", "announcement_url": "https://medium.com/qiskit/important-changes-to-qiskit-documentation-and-learning-resources-7f4e346b19ab", "announcement_url_text": "Learn More", } @@ -242,6 +242,7 @@ # Redirects # ---------------------------------------------------------------------------------- + def determine_api_redirects() -> dict[str, str]: """Set up API redirects for functions that we moved to module pages. @@ -257,8 +258,7 @@ def determine_api_redirects() -> dict[str, str]: obj_name, new_module_page_name = line.split(" ") # E.g. `../apidoc/assembler.html#qiskit.assembler.assemble_circuits new_url = ( - "https://qiskit.org/documentation/apidoc/" + - f"{new_module_page_name}.html#{obj_name}" + "https://qiskit.org/documentation/apidoc/" + f"{new_module_page_name}.html#{obj_name}" ) result[f"stubs/{obj_name}"] = new_url return result @@ -286,21 +286,9 @@ def determine_api_redirects() -> dict[str, str]: def add_versions_to_config(_app, config): """Add a list of old documentation versions that should have links generated to them into the context, so the theme can use them to generate a sidebar.""" - # First 0.x version where Qiskit/Terra and the metapackage aligned on number. - first_unified_zero_minor = 45 - - # Start with the hardcoded versions of the documentation that were managed while the metapackage - # still existed, so are based on tags that don't exist in the Qiskit package repo. - versions = ["0.19"] + [f"0.{x}" for x in range(24, first_unified_zero_minor)] - - proc = subprocess.run(["git", "describe", "--abbrev=0"], capture_output=True, check=True) - current_version = proc.stdout.decode("utf8") - current_version_info = current_version.split(".") - if current_version_info[0] != "0": - raise Exception("TODO: handle major versions") - versions.extend( - f"0.{x}" % x for x in range(first_unified_zero_minor, int(current_version_info[1]) + 1) - ) + # For qiskit 1.0 the docs won't use this mechanism anymore + # so just build out the historical version list for the 0.x series + versions = ["0.19"] + [f"0.{x}" for x in range(24, 46)] config.html_context["version_list"] = versions diff --git a/docs/deprecation_policy.rst b/docs/deprecation_policy.rst index b25d93ecaf55..997aa8172fa8 100644 --- a/docs/deprecation_policy.rst +++ b/docs/deprecation_policy.rst @@ -211,7 +211,7 @@ It is important to warn the user when your breaking changes are coming. for the function so that it shows up in docs. If you are not using those decorators, you should directly add a `Sphinx deprecated directive -`__ :: +`__: .. code-block:: python diff --git a/docs/index.rst b/docs/index.rst index 55ae6cd3b7a5..b82e864ccfca 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,7 +3,7 @@ Qiskit |version| documentation ############################## Qiskit is open-source software for working with quantum computers -at the level of circuits, pulses, and algorithms. +at the level of circuits and pulses. The central goal of Qiskit is to build a software stack that makes it easy for anyone to use quantum computers, regardless of their skill level or @@ -12,7 +12,7 @@ them on real quantum computers or classical simulators. Qiskit is already in use around the world by beginners, hobbyists, educators, researchers, and commercial companies. .. qiskit-card:: - :header: Starting on December 1, 2023, Qiskit Documentation will only live on IBM Quantum. Learn more -> + :header: Starting on November 29, 2023, Qiskit Documentation will only live on IBM Quantum. Learn more → :card_description: We are reorganizing Qiskit documentation on IBM Quantum to better support your research and development workflows. :image: _static/images/1xp.png :link: https://medium.com/qiskit/important-changes-to-qiskit-documentation-and-learning-resources-7f4e346b19ab diff --git a/docs/legacy_release_notes.rst b/docs/legacy_release_notes.rst index b8a64228c0f9..13b557bcfef1 100644 --- a/docs/legacy_release_notes.rst +++ b/docs/legacy_release_notes.rst @@ -520,7 +520,7 @@ Transpiler Features - A new method :meth:`~qiskit.dagcircuit.DAGCircuit.find_bit` has been added to the :class:`~qiskit.dagcircuit.DAGCircuit` class, - which returns the bit locations of the given :class:`.Qubit` or + which returns the bit locations of the given :class:`~.circuit.Qubit` or :class:`.Clbit` as a tuple of the positional index of the bit within the circuit and a list of tuples which locate the bit in the circuit's registers. @@ -1597,7 +1597,7 @@ Bug Fixes .. releasenotes/notes/fix-bit-copy-4b2f7349683f616a.yaml @ b'163d1bd7835d58eaf8842c594b3696fb99c8442f' - Fixed an issue with copying circuits with new-style :class:`.Clbit`\ s and - :class:`.Qubit`\ s (bits without registers) where references to these bits + :class:`~.circuit.Qubit`\ s (bits without registers) where references to these bits from the containing circuit could be broken, causing issues with serialization and circuit visualization. Fixed `#10409 `__ @@ -4326,8 +4326,8 @@ Bug Fixes .. releasenotes/notes/fix-deprecated-bit-qpy-roundtrip-9a23a795aa677c71.yaml @ b'3dbbb32e762850db265c7bb40787a36351aad917' -- The deprecated :class:`.Qubit` and :class:`.Clbit` properties :attr:`~.Qubit.register` and - :attr:`~.Qubit.index` will now be correctly round-tripped by QPY (:mod:`qiskit.qpy`) in all +- The deprecated :class:`~.circuit.Qubit` and :class:`.Clbit` properties :attr:`~.circuit.Qubit.register` and + :attr:`~.circuit.Qubit.index` will now be correctly round-tripped by QPY (:mod:`qiskit.qpy`) in all valid usages of :class:`.QuantumRegister` and :class:`.ClassicalRegister`. In earlier releases in the Terra 0.23 series, this information would be lost. In versions before 0.23.0, this information was partially reconstructed but could be incorrect or produce invalid circuits for @@ -7031,7 +7031,7 @@ Bug Fixes - QPY deserialisation will no longer add extra :class:`.Clbit` instances to the circuit if there are both loose :class:`.Clbit`\ s in the circuit and more - :class:`.Qubit`\ s than :class:`.Clbit`\ s. + :class:`~.circuit.Qubit`\ s than :class:`.Clbit`\ s. .. releasenotes/notes/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml @ b'e0befd769fc54e9f50cdc4b355983b9d1eda6f31' @@ -9062,7 +9062,7 @@ Bug Fixes .. releasenotes/notes/0.22/denselayout-loose-bits-3e66011432bc6232.yaml @ b'618770367f7a5a3a22fd43ea9fcfb7f17393eb6a' - Fixed an issue in the :class:`~.DenseLayout` transpiler pass where any - loose :class:`~.Qubit` objects (i.e. not part of a :class:`~.QuantumRegister`) + loose :class:`~.circuit.Qubit` objects (i.e. not part of a :class:`~.QuantumRegister`) that were part of a :class:`~.QuantumCircuit` would not be included in the output :class:`~.Layout` that was generated by the pass. @@ -9071,7 +9071,7 @@ Bug Fixes - The :meth:`.Operator.from_circuit` constructor method has been updated so that it can handle the layout output from :func:`~.transpile` and correctly reverse the qubit permutation caused by layout in all cases. - Previously, if your transpiled circuit used loose :class:`~.Qubit` objects, + Previously, if your transpiled circuit used loose :class:`~.circuit.Qubit` objects, multiple :class:`~.QuantumRegister` objects, or a single :class:`~.QuantumRegister` with a name other than ``"q"`` the constructor would have failed to create an :class:`~.Operator` from the circuit. @@ -10274,7 +10274,7 @@ Upgrade Notes - The data type of each element in :attr:`.QuantumCircuit.data` has changed. It used to be a simple 3-tuple of an :class:`~.circuit.Instruction`, a list - of :class:`.Qubit`\ s, and a list of :class:`.Clbit`\ s, whereas it is now + of :class:`~.circuit.Qubit`\ s, and a list of :class:`.Clbit`\ s, whereas it is now an instance of :class:`.CircuitInstruction`. The attributes of this new class are :attr:`~.CircuitInstruction.operation`, @@ -10651,7 +10651,7 @@ Bug Fixes .. releasenotes/notes/0.21/fix-reverse_bits-with-registerless-bits-6d17597b99640fb0.yaml @ b'0f377f7a2cdbd7eaa46e8e2b5de974c8c22b9612' - Fixed :meth:`.QuantumCircuit.reverse_bits` with circuits containing registerless - :class:`.Qubit` and :class:`.Clbit`. For example, the following will now work:: + :class:`~.circuit.Qubit` and :class:`.Clbit`. For example, the following will now work:: from qiskit.circuit import QuantumCircuit, Qubit, Clbit @@ -10954,7 +10954,7 @@ Bug Fixes - Fixed an issue with the visualization function :func:`~.dag_drawer` and method :meth:`.DAGCircuit.draw` where previously the drawer would fail when attempting to generate a visualization for a :class:`~.DAGCircuit` - object that contained a :class:`~.Qubit` or :class:`~.Clbit` which wasn't + object that contained a :class:`~.circuit.Qubit` or :class:`~.Clbit` which wasn't part of a :class:`~QuantumRegister` or :class:`~ClassicalRegister`. Fixed `#7915 `__. @@ -12350,7 +12350,7 @@ Upgrade Notes .. releasenotes/notes/0.20/bit-slots-17d6649872da0440.yaml @ b'a2d13f55aad6c670f71a4613516b8891e02ece63' -- The classes :class:`.Qubit`, :class:`.Clbit` and :class:`.AncillaQubit` now +- The classes :class:`~.circuit.Qubit`, :class:`.Clbit` and :class:`.AncillaQubit` now have the ``__slots__`` attribute. This is to reduce their memory usage. As a side effect, they can no longer have arbitrary data attached as attributes to them. This is very unlikely to have any effect on downstream code other @@ -13180,7 +13180,7 @@ Bug Fixes - Fixed QPY serialization of :class:`.QuantumCircuit` containing subsets of bits from a :class:`.QuantumRegister` or :class:`.ClassicalRegister`. Previously if you tried to serialize a circuit like this it would - incorrectly treat these bits as standalone :class:`.Qubit` or + incorrectly treat these bits as standalone :class:`~.circuit.Qubit` or :class:`.Clbit` without having a register set. For example, if you try to serialize a circuit like:: diff --git a/docs/maintainers_guide.rst b/docs/maintainers_guide.rst index e8c77125395f..ed2a6b508cdf 100644 --- a/docs/maintainers_guide.rst +++ b/docs/maintainers_guide.rst @@ -11,7 +11,7 @@ Stable Branch Policy ==================== The stable branch is intended to be a safe source of fixes for high-impact -bugs and security issues that have been fixed on master since a +bugs and security issues that have been fixed on ``main`` since a release. When reviewing a stable branch PR, we must balance the risk of any given patch with the value that it will provide to users of the stable branch. Only a limited class of changes are appropriate for @@ -29,8 +29,8 @@ change: also refactors a lot of code, it's probably worth thinking about what a less risky fix might look like. - Whether the fix is already on ``main``: a change must be a backport of - a change already merged onto master, unless the change simply does - not make sense on master. + a change already merged onto ``main``, unless the change simply does + not make sense on ``main``. Backporting diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 8ba7a73df89d..d6189e1ce0b4 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -34,17 +34,3 @@ Advanced circuits :glob: tutorials/circuits_advanced/* - -Operators -========= - -.. deprecated:: 0.24.0 - The operators tutorials rely on the ``opflow`` module, which has been deprecated since - Qiskit 0.43 (aka Qiskit Terra 0.24). Refer to the - `Opflow migration guide `_. - These tutorials will be removed in the future. - -.. nbgallery:: - :glob: - - tutorials/operators/* diff --git a/docs/tutorials/circuits_advanced/06_building_pulse_schedules.ipynb b/docs/tutorials/circuits_advanced/06_building_pulse_schedules.ipynb index 20ba9aa7d2e6..c567d76de14b 100644 --- a/docs/tutorials/circuits_advanced/06_building_pulse_schedules.ipynb +++ b/docs/tutorials/circuits_advanced/06_building_pulse_schedules.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2023-08-25T18:25:49.766612Z", @@ -31,7 +31,7 @@ "ScheduleBlock(, name=\"my_example\", transform=AlignLeft())" ] }, - "execution_count": 1, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -81,7 +81,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2023-08-25T18:25:50.235098Z", @@ -106,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2023-08-25T18:25:50.243572Z", @@ -151,7 +151,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2023-08-25T18:25:50.460776Z", @@ -185,7 +185,7 @@ "\n", "### Pulses\n", "\n", - "A `Pulse` specifies an arbitrary pulse _envelope_. The modulation frequency and phase of the output waveform are controlled by the `set_frequency` and `shift_phase` instructions, which we will cover next.\n", + "A `Pulse` specifies an arbitrary pulse _envelope_. The modulation frequency and phase of the output waveform are tied not to the pulse object, but rather to the `Channel` in which the pulse is played. The `Channel`'s frequency and phase are controlled by the `set_frequency` and `shift_phase` instructions, which we will cover next.\n", "\n", "The image below may provide some intuition for why they are specified separately. Think of the pulses which describe their envelopes as input to an arbitrary waveform generator (AWG), a common lab instrument -- this is depicted in the left image. Notice the limited sample rate discritizes the signal. The signal produced by the AWG may be mixed with a continuous sine wave generator. The frequency of its output is controlled by instructions to the sine wave generator; see the middle image. Finally, the signal sent to the qubit is demonstrated by the right side of the image below.\n", "\n", @@ -200,7 +200,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2023-08-25T18:25:50.468209Z", @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2023-08-25T18:25:50.474488Z", @@ -245,12 +245,12 @@ "outputs": [ { "data": { - "image/png": "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IiIiIiIiIoMwuUBEREREREREBmFygYiIiIiIiIgMwuQCERERERERERmEyQUiIiIiIiIiMgiTC0RERERERERkECYXiIiIiIiIiMggTC4QERERERERkUGYXCAiIiIiIiIigzC5QEREREREREQGYXKBiIiIiIiIiAzC5AIRERERERERGYTJBSIiIiIiIiIyCJMLRERERERERGQQJheIiIiIiIiIyCBMLhARERERERGRQZhcICIiIiIiIiKDMLlARERERERERAZhcoGIiIiIiIiIDMLkAhEREREREREZhMkFIiIiIiIiIjIIkwtEREREREREZBAmF4iIiIiIiIjIICaVHQARERGVr4cpEshFyfeztRCwNCv7eIiIiOjVw+QCERHRS06uAGLuGessF6VILABASiYg0VHmYK2Ai30pGyYiIqJXDpMLREREL4GneUBisvbRjEKUPoFQJAHoajYtywjZudpLzU0FXB2YeCAiInqdMLlARET0glAUkSQoUAA5ubr6EVS8AjlQINcej5ERIFfoTi4Yc8YnIiKiVw6TC0RERC+ItCwJEp+8/E/eWTkS3IjXPkxDaiJQq5qigiMiIiKi8vbyf4MhIiIiIiIiokrFngtEREQV6Em6BClZ2ocTyOUVHEwlyJdLcOu+7t82argoYPTijP4gIiIiPTG5QEREVIEK5EBu3mv89Cxe8/MnIiJ6RTG5QEREVMbyC55NzqhNgYIP1kXJywckOi6R1ATs1UBERPSCYnKBiIiojN1PMUJGNp+CS+P2fe0TQQKAt6scZqYVGAwRERHpjRM6EhEREREREZFB2HOBiIioFPILAB0jH3QOiSDD5MsBowLtZSZGgBF/MiEiIqo0TC4QERGVQuwjI+Tlc+hDRbr7SPeQCXcnBWwtmdUhIiKqLMzxExEREREREZFBmFwgIiIiIiIiIoNwWAQREZEOt+/rzsHnF3BIxIvkYaoEj9O1vyb2VgIO1hwyQUREVJ6YXCAiItLhaR4TCC+L/AIJ8nWUFXCGTSIionLHYRFEREREREREZBD2XCAioteWQgFk5VZ2FFTe8vIlyMjR3ntBagyYmVZwQERERK8gJheIiOi1lS8venlDejWkZUmQlqX9dba1FHB3UlRwRERERK8eDosgIiIiIiIiIoMwuUBEREREREREBuGwCCIieqXl5AFP0rXn0uXsDf/ay84F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/twEi8V5//0piDp9GqNGjni2FGX1Gnj8OAlHDh/GppDQIud3WLx4MSZOnIBpH0xFq9at0fyt5rC2scGTJ49xJioKFy5cQKvWrVX127QNwLFjxzB92gdo3aYtEhMSsGPHL6hRowayc3JU9aytrdGpUyf89NN2SCQSuHu448SJE0gplFwBgLi4OIwbOwZBQUGoUcMbxibGOH7sOJ48eYIuXbqU6DoQERHRv5hcICIiek6zt97CggWfYePGjfjyy2WoVq0apn4wDYmJCXonBsqiDX01bdoUoZu3YP336/DzTz8hOzsbjk5OqF+vPvr376+qV69ePUyaNBk7dvyCyFOnoFAocODg76hWwuRC1apVsWXrNqxZ8y0OHjiArKwsODs7o1Wr1sXO7+Dg6IjQzVuwc+cO/PHHH1i37js8ffoUdnZ2qFOnDhYvWYLOnf99yO/VqxeePHmMnTt3IjIyEjVq1MB/Fi/BkSOHcf78ebW2Z8/5CAUFBdix4xeYmpoiKKgzpk2bjv79+qrquLi4oEuXrjh79gz2798PYxMTVPfywrIvl6Njx04lug5ERET0L4koqv/i/0tPT4etrS3uJSRyySYiIiIiIiKiF1R6ejrcq7khLS2tQp/fOecCERERERERERmEyQUiIiIiIiIiMgiTC0RERERERERkECYXiIiIiIiIiMggTC4QERERERERkUGYXCAiIiIiIiIigzC5QEREREREREQGYXKBiIiIiIiIiAzC5AIRERERERERGYTJBSIiIiIiIiIyCJMLRERERERERGQQE30qCSEAABkZGeUaDBERERERERGVnvK5XfkcX1H0Si4og6vt61OuwRARERERERGR4Z48eQJbW9sKO55E6JHOUCgUSExMhLW1NSQSSUXEVSbS09Ph4eGB+Ph42NjYVHY4RBWK9z+9znj/0+uO7wF6nfH+p9ddWloa3njjDaSkpMDOzq7CjqtXzwUjIyO4u7uXdyzlxsbGhh8s9Nri/U+vM97/9Lrje4BeZ7z/6XVnZFSxUyxyQkciIiIiIiIiMgiTC0RERERERERkkFc6uSCTyTB//nzIZLLKDoWowvH+p9cZ73963fE9QK8z3v/0uqus94BeEzoSEREREREREenySvdcICIiIiIiIqLyx+QCERERERERERmEyQUiIiIiIiIiMsgrmVzIycnBvHnzUKtWLZiZ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wtfXF5MmTcLff/+td5wluYbahISEqLr4x8XFqb1egYGBqnq65lxQDqcoKCjAokWLULNmTZibm6N27dpq53z8+HG0a9cONjY2sLe3x3vvvaczxoKCAqxduxYtWrSAjY0NzM3N0bBhQ3z99dcoKCjQ+9oAwMaNG6FQKDB48GCddX799Ve0aNECFhYWcHR0RL9+/bS+Bsr5L+Li4lTnrvzz/JCS3r17w9zcHOvXry9RvERERGWJwyKIiEhvHh4eAIBdu3bho48+KvbXWZlMhuDgYHz11VfYtm0bJk2apFFH+UA0duxY1bZZs2Zh+fLlkMlkaNu2LZycnPDgwQNcvnwZt27dUrUTHByMP//8E7dv30bnzp3h4uKiasPKykr1/3PmzMHSpUthamqKpk2bwtXVFZcuXcLKlSvx66+/4tSpU6hatapGbHv27MGaNWvQokULdOnSBVFRUfjhhx9w8+ZN7Ny5Ey1atIBcLkebNm0QGxuLM2fOoEuXLjh37hzq16+v1zXNyspCt27dcOLECVhaWqoSDLGxsdi2bRtsbW3RpEkT9OrVCy4uLti9ezeePHmi0f09MzMT27dvh42NDd555x3V9vv376NTp064evUq7O3tERgYCJlMhn/++Qffffcd3nzzTdSqVavYOEt7DQurWbMmgoODERoaCktLS/Tv319VVpLeLgMHDlQlELy9vREREYGRI0cCAKytrTF48GA0b94cnTt3xunTp7FlyxbcuXMHJ06cUJu/ICcnB927d0dYWBgcHBzQvHlzmJmZ4cyZM5g2bRrCwsKwZ88eGBnp91vM/v37AUAtUVLYd999hwkTJkAikaBNmzZwdXVFVFQUmjVrhh49eqjVdXFxQXBwMHbu3ImsrCwEBwerypycnNTqWllZwd/fHydPnsQ///yDGjVq6BUvERFRmRJERER6un37tjA3NxcAhLW1tQgODhbr168X0dHRoqCgQOs+MTExQiKRiAYNGmiUpaWlCQsLC2Fvby9ycnKEEELk5OQImUwmrK2txT///KNWPz8/X5w4cUJtW3BwsAAgwsLCtB7/l19+EQBEvXr1xM2bN1XbFQqFmDdvngAg3nnnHa1tGhkZif3796u2p6eni3r16gkAok6dOmLYsGEiLy9PVf7pp58KAOK9997TGos2o0aNEgBE27ZtxaNHj9TKHjx4IKKiolR///jj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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAvIAAACgCAYAAABuQoiZAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAApOklEQVR4nO3de5xVVf3/8dcbhpsIKqJopmJqkVmZ4gVBRTN/KqVl3vIGKuYlNFHD8CuGZpl3U0vQBNRSS8UrmikCCprKxbykKBUoCMhNUS6isH5/rHWGzebMzBkYZubI+/l4nMfMXnvtvdfeZzF8ztqfvY5CCJiZmZmZWXlp0tANMDMzMzOz2nMgb2ZmZmZWhhzIm5mZmZmVIQfyZmZmZmZlyIG8mZmZmVkZciBvZmZmZlaGagzkJQ2UFCQ9WWTd/ZJGr5OWrWOSeqXzKrw+kPSkpF0bum11RdJBks6tRf3CNdmwDo7dQdJ1kt6WtFTSJ5ImSDpf0kZru/91RVL3XL/4WNJbkm6T9O0GalM/Sd2LlAdJfeq/Rau04SxJIyTNS+3pXqTOUZIekTQj0w9+UqTelpKGZupNknR8ie04XNJrqa/9W9Ixa3g+O2fPQ1Lz9DdwlzXZn5mZ2bpUmxH5gyTtvs5a0nAOALoApwObAaMkfalhm1RnDgLOrUX9EcRrsXhtDiqpEzAJOAy4GTgE+DHwGHBhKmvsjidei8OB64FvAuMlndoAbekHdC9S3gW4r36bspqTgHbAah/0M84DPgH6EvvEKOBuSWcXKkhqAjwC7Ec838OBfwJ/lvSj6hogqRvwQNrvIcR+fI+kg9bwnLKaA78CdqmDfZmZmdWpihLrzQemA/8H/HCdtaZhvBxC+ARA0nhgGjGIu3pNdiapVQhhSR22b52T1BRoGkKYA8ypg13eDcwFuoUQFmbKn5R0LdCjDo6xrr0aQng9/f6MpNuAIcAtksaEEKaszc4ltQwhLF2bfYQQ/rk229eRvUMIKyTtDKw2yp78IIQwN7P8TPqwfB5wUyr7KtAZOCyE8GgqGylpT+BY4MFq2jAAeDaEcE5aHiXpG8AlwD9qf0pmZmblodQR+QD8FjhM0jerqpRujQ+R9F9JS1JaxeWSmmfqdEy3ro9Nt9EXSpou6YS0vp+k9yXNkXRlGqnLHmPndCv/4/S6T9IWa3Duq59kCO8RA9mO6Vi/S7frP0lt/Ev+WJKmSrpW0gBJ04GFqbxLSid4X9IiSa/k0wQyqSy7ShotaXGqt6uk1un6fJSuZ7FUhMMljU/pBLMkXSWpWVo3EDgf2DaTJjIsrRuWtvuhpDeApcCeKpJaI6lV2u80SZ9K+p+kK6q6hpL2A74D/DIXxBeu8cIQwj2Z+p0k3SvpvXT+b0g6N/u+F2tX5tpfk1nuJum51KcWpmt5VFVtrY0QwgriiPJyoHfmmKult6RUjLmZ5UL790jv8xLgF2ldtX1M0lRgU+BXmfexezXH7iPpnfReTZHUt1jbJH1H0j/TNZ8kaZ+1uC411ZlbpHgSsHlmuVn6+VGu3oeAqtq3pBbA/sDfcqvuBbqohjQuxdSg99K/0UeBLXNVPk4/h2auf8fq9mlmZlZfapNacx/wNnFUvirtiaP35wEHE0e1T2blqFvWlcBMYsrFc8AdiqO1ewCnADcQb7EfXdhA0g7AOKAlcCLQC/gG8KgkZepNLQSttSGpDTFNYFYq2pz4AaYHMUXlK8TRxKa5TY8jpgScBRRyc7dNbe0N/IB4639osYAcuAO4h3gtBNwP3A68DxwJvAjcKenLmbYeDQwHXiKmK1wK/BQoBNl/Io6MzyKmYHQBfp05ZkfgqlT/UOB/Ra6HgIeBM4E/pHq/Ir7PVdkX+Bx4ppo6WVsBk4nX7lDgtnQuF5a4faGtbYmpO/8lXscjgbuAjWuzn+qEEBYA44G91nAX9xDbeGj6CTX3sR8Rg9vbWfk+Tiy2c0mnEf+tPULsc/cB10r6Za7qBsQ+N5h4rT4FHpS0QWZfAyWFNTzPUuwN/Duz/Dqxn18maUdJbSX1AroCg6rZz/bEDwFv5crfJP59+2pVG0o6nNivHwOOAF4j3nXJOiD9vJyV139mNe0xMzOrPyGEal/AQGBu+r0XcUTyq2n5fmB0NdtWEIPcpUDzVNaROMI/NFOvLfAZ8A4xxaNQ/hLw18zyXcSgr3mmbMfUph6ZsinA7TWcV6/Ujo1SO7cG/koMQncpUr8pMegMwL6Z8qnE/9hbVnMspWMMBp4p0oaembJDU9mQTNlG6fqcmdnftOw1TOWnAEuATdPyNcDUIu0Zlo6xSxXXZMO0/P/S8mE19ZPMPm4BZlZx/SrSq2kV2xau00XAf6tqV+7aX5N+75zqtCm1rVW0oXvaz85VrL8HeDOzHIA+Vf2bybX/5zUcu6o+NhcYWKR+5bGJQeuMIn3ij8QPAi0zbQvAAZk6u6SygzNllwCf1+K67Zz20b2Eut8FVgC9cuWbAM+m/QRgGXB8DfvqWkVf3iGVH1TNti8BT+TKbsueB7BhWu5V03n55ZdffvnlV32/ajv95J+Bd4H+xVYqOldx1oglxODzL0ALYJtc9ZGFX0JMwZgDjAkhLM/UmUIMbAoOJObKrpBUIamCOJI8lRjIFfa3Qwih1IcSP0ztfJc4+nZKCOGVdD6HSHpe0kfEAH962iY/yjcy5PKdJW0i6UZJ09L+PyOOmBcbIRyZ+b2Qe105oh1C+Ih4fQrX4qvE6/m3wnVI1+IZ4t2KnUs47xmF86zGAcD8EMIjJeyvQMTAJ+8jVl6H2ZWVpZaSLpU0hTgy/BnwG2C7dE6l+g/xgcq7FVOONq7FtrVRZZpHCUastrPS+1hNvgx8idUffv0r8YNyNiXuM2B0ZrkwMl55xyeEcFkIoTbXvyQpLeVu4OEQwrBMeRPiB/VNiXe19ifelbtd0sEl7Drf51RFeeF4TYkpYA/nVg0v4VhmZmaNQq0C+RDC58R0jBMkbVukyrnAtcRg+3BimszP0rqWubof5paXVVGW3a49MeXis9zrK8QR9TWxL/FDQEegQwjhTgDFGXoeIQZWJxJvqRdSKvLnMpvVDSMGJFcTZ4/ZnXjbPr8trHrey4qUFcoL2xZSWx5n1etQSI8p5VoUa3PeptQ+jWAGsFnKXc7ah3gNbsuVXwlcANxKvBuxOzGNAYpfq6JCTHs5iJhm8TdgjuKzFF+pZftrshWlXbtiVtmuln2sJoXc7nzbCsvtMmULQya3PYRQ6HO1PWatSGoHPEH80HxCbvX3ielFPwwh/C2EMDqE0I/4t+Sqana7IP3cOFdeWP6wiu02I979+SBXnl82MzNrtNZkxG0IcDHFc5iPAu4LIVTm0UvaaQ3bVsx84n/sfyqyrtgDdaWYFNKsNTk/Io6CHxNCiHkfxT+8QG7UT1JLYlDSJ4QwKFNeV1/ANT/9/CnxocG81fLdiygl/3keqz/8V5Nnif1qf+DvlQcLYRKApO/n6h8F3BRCqAzWJOVntSnc7WieK98kuxBCeAE4WFIr4t2b64ijv2ua074KSZsQP/TdkCn+tEi72lFc/prXpo/VpPCBa/NceYf0cz4NKOXfP0a8Vj1CCItyVToBi0MI7+TKC9OYVuU/xA+xnYAxuf2tID7XU8wc4h2Q/PXKL5uZmTVatQ4sQwifEnOvT2H1IK8VMbDJKukLXUo0kpg2MiGEMD73mlqHx4F4Lp8VAqyk1HNpQcx3rrwW6UHa6gKS2phMHPnuWOQ6jA8hzEv18nc0amsk0K5I8F2lEMIYYvB1RTrnmqzSZ1LKw7G5OoV0k69n6u1JTBkp1oYlIU5hOASokw+S6UPY9cT39fZc276eq3cApSm1j5XyPk4nPhydn6XnaOJMSq+V2KY6l1Kk7iM+z3JICKHYqPc0YANJX8uV70ZMnSsq/T0axernfQzwQkpLK7bdcuAV4p3DrCNyy/Vyt8LMzGxNrGkO7GDiA4l7s+oo2FPAOZJeJI6UHU986KyuDCQ+oDZC0hDiKPxWwPeAYSGE0QAp33pMLfLki3kKOFfSDcCjxHPNpwMUFUL4SNLLwCWSFhJHBn9JzBMvGnzWRojzdp8P3JVma3mCGHB8hTjP/5EhhMXEmTw6pNk/Xic+gDm1Fod6ivhFP3dLuow4W8qWxAcxT69mu+OI+foTJd2Yjt2UGMgdQ8xlzx7jZ+k9m09Mxcqn5bxE/OByo6QBxBHvfqSpPqFyFP8U4CFi6sZWxC/5eiZTZyDwqxBCKXnu31Kc7rIlMV/9ZOJo/Blh1TnkH0ztn0ScMac3pb/Hpfaxt4Aekv5OvHaTQwgfZyukPjEQGCxpXtr3fsQZhy7KP8NRE0mXAJfUlCcvqZCWVkjn2k9Se+JD1uNT2R+JaVM/J34wzN4hmZSC8ceJ79tDqa/NId7VOpqV6XlIOon4AW37EMK0VPxrYHS6jg+lYx1KnDmrOr8Fhku6hfg+7pffJoSwTNL/gKMlvU68O/RqJh3JzMys4dT0NCy5GTgy5RcRUwVGZ8o2BIYSA7L5xBSY75OZBYSVs9Z8P7e/qaQZSDJlw4DxubJOxNly5hNnaJlC/GDx5dy+htVwXr0oMhNKrk4/4D1gEfA0MRBdZZaSYu1O5TsQg8hFxAClX/5aFmtDLa/PIcSpOxcRg9pXiPnlFWl9y/R+fJD2Oayq61pNe1oR78BMJ46c/w/4TQn9Zgtiass7xODnE+IHgUuB9pl6HYhB1EJiPvdVwGlF2rE78DLxW2cnEWcrqbwmwNdSv3gvtXM6cdrCdpl9XAV8UEO7u7Ny1pSQru1kYm7/t4vU35A4leN84lSfF5fyPteyj+1G/JbTRaw6o0qxGXP6EP9NLCN+sOhb4r/n/DEHAqGE93lY7noVXsMydaZWUScQ7ypl/83cR7yz8AnwL+KHMRW5lh1z7fgh8QPjp8QPPsfW1PbM9Zqe+tXjxOcsVpl9J5W9SuzHqx3bL7/88ssvvxrqpRBKSZU2K3+SxhCn/7y0odtiZmZmtrYcyNt6IeVpzwY6hRDmNHR7zMzMzNaWA3kzMzMzszJU7YNsm7ZvH7bdJv89TmZmZmZmVhuTJk2aG0LYrC73WW0gv+022zDm2efq8nhmZmZmZuudtm02nFZzrdqpqy8oMjMzMzOzeuRA3szMzMysDDmQNzMzMzMrQw7kzczMzMzKkAN5MzMzM7My5EDezMzMzKwMOZA3MzMzMytDDuTNzMzMzMqQA3kzMzMzszLkQN7MzMzMrAw5kDczMzMzK0MO5M3MzMzMypADeTMzMzOzMuRA3szMzMysDDmQNzMzMzMrQw7kzczMzMzKkAN5MzMzM7My5EDezMzMzKwMOZA3MzMzMytDFQ3dADMzK90b05qWVO8b2y5fxy0xM7OG5kDezKyRKjVoL3VbB/dmZl8sDuTNzBrY2gTsa3scB/dmZuXLgbyZWT2qr6C9VFW1xwG+mVnj54ddzczMzMzKkEfkzczWkcY2+l4bTsMxM2v8HMibmdWBcg7aS+Xg3syscXFqjZmZmZlZGXIgb2ZmZmZWhpxaY2ZWS+tDGk2pnG5jZtZwPCJvZmZmZlaGPCJvZlYNj77Xnkfpzczqh0fkzczMzMzKkEfkzcwSj76vOx6lNzOrex6RNzMzMzMrQw7kzczMzMzKkFNrzGy94xSaxsHpNmZma8cj8mZmZmZmZciBvJmZmZlZGXJqjZl9oTmNprxU9X455cbMbHUekTczMzMzK0MekTezLwyPvn9x+cFYM7PVeUTezMzMzKwMOZA3MzMzMytDTq0xs7LkNBpzuo2Zre88Im9mZmZmVoY8Im9mjZ5H361UHqU3s/WJR+TNzMzMzMqQR+TNrFHx6LvVNY/Sm9kXlUfkzczMzMzKkEfkzaxBeOTdGpJH6c3si8Aj8mZmZmZmZcgj8ma2znn03cqBR+nNrNx4RN7MzMzMrAx5RN7M6pRH3+2LpKr+7JF6M2sMHMib2Rpz0G7rK6fhmFlj4NQaMzMzM7My5BF5MyuJR9/NqudRejOrbw7kzWw1DtrN6oaDezNblxzIm63nHLSb1S8H92ZWVxzIm30BOTg3Ky+1+TfroN/MChzIm5U5B+1m6xeP6JtZgQN5s3rkoNvM1oW6/tviDwZm5cGBvFk98n+OZmZmVlc8j7yZmZmZWRlyIG9mZmZmVoYcyJuZmZmZlSEH8mZmZmZmZciBvJmZmZlZGXIgb2ZmZmZWhhzIm5l9QY0YMYIzTj+9oZthZmbriOeRN7P12iGHHMz8efNo0qQJrVq1olu3ffhl//5ssMEGDd20Ks2YMYMehx7C+AkTqaio+s94jx496NGjxxod44033mDQLbfwyiuTCMDmm23G/gccQM+evWjbtu0attzMzOqSR+TNbL33+xtv4oV/vsg99/6VN954ndtuvbVW24cQWLFixTpq3Zr5/PPP13jbV155hd6nnsIu39mFhx5+hLFjx/GHP95C06ZNmTx5ch220szM1oZH5M3Mkg4dOtC1WzemTJnCwoUL+b+LLuL111/j888/Z5ddvsPFAy6mQ4ctADj11FPYZZddGD9+PG+9+Sb33f8AEydO5I5hQ5k9ezabbLIJJ598CkcedRQAL7/8Mv93UX9+ctxx3HnHHTRt2pSL/u9imjVrxtVXXcWHHy7gpJ496d37NABWrFjBsKFDGT78AT7++GP22HNPLr54ABtttBGnnnIyAPt06wrAoMG3MnXqVIYPf4Cdd96ZRx95hKOPOYatt96GB4cPZ9gddwAwZcoUrr76Kt7897+pqKjguOOPrzxe1g3XX8/hh/+QU0/tXVm25ZZbctZZP6tcfu+997js0oG8/fbbSKLL3nvTv/9FlaP1u3z7Wzzy6GNss802AAwYcDEdOnSgT5+zWbBgAZcMGMCkVybRRGL77bfn9iFDadLEY0tmZrXhv5pmZsmsWbMY+9xYOnXqxIoVKzj8h4fz+BN/5+9P/oMWLVtwxRVXrFJ/xGOPMWDAJYx7/gW23HJL2rVrx4033cy451/g0st+zTXXXM2bb/67sv68efNY9uky/vHU05x51ln8+rJLGTHiMe65916GDB3GrYMHM336dADuvvsvjBr1DLcPGcJTT4+kbZu2XPHb3wBw+5ChADw3dhwv/PNFvv3tbwPw+muv8eWtvswzo0avFqAvWrSIM07/KV337spTT4/k0cdGsOcee652DZYsXsyrr/6L7x54YLXXKoTAKaf25qmnRzL8wYeYPWsWgwbdUtJ1vuvOO+nQoQOjRo1m5DOjOPucc5BU0rZmZraSA3kzW+/1PffndOvWlZN79WS3zrtxau/ebLzxxhx44Pdo1aoVrVu3pnfv05gwfvwq2x122OHssMMOVFRU0KxZM/bdd1+23nprJNG5c2f26tKFiRMnVtavqKig92mn0axZMw4++BAWLFjA8ccdT+vWrdlhhx3YfvvtefvttwF44P776XP22XTosAXNmzfnjDPP5Omnn642ZWazzTbjJ8cdR0VFBS1btlxl3bPPjmHTTdtzUs+etGjRgtatW/PNb31rtX0s/HghK1asoH379pVl119/Hd26dWWvPfeoTDvaZptt6NKlC82bN6ddu3accOJJTBg/oaTrXVFRwdy5c5g5cybNmjVj1113cyBvZrYGnFpjZuu962/4PXvttdcqZUuWLOGaq6/m+efHsXDhQiCOai9fvpymTZsC0GGLDqtsM3bscwweNIhp06axIgSWLlnCjjvsWLl+o402qty2RYsWALTbdNPK9S1atGTJ4sUAzJw5k/P69kWZdJMmTZowb968Ks+jwxZbVLlu1qxZbL31l6u+CEnbNm1p0qQJc+fMYbvttgOgb9/z6Nv3PC7q35/Pl8cPEvPnzePKK69k4sSJLF68iBUrVpT8EGzPXr0YdMstnHlGnFHnxz8+klNOPbWkbc3MbCWPyJuZFXHXnXcyddpU7vrzXxj3/AuV6SwhhMo62VHkZcuWccH553NSz56MfGYUY8eOo9s++xAIq+27FB06dODmP/yRsWPHVb5eenk8HTp0qHL0WlQ9qr3FFlvw3nvTazxuqw02YOdvfpORI0dWW+/GG29Egvvuv59xz7/Ab357xSrXpmXLlixdurRyed7cuZW/t27dmvMvuIARjz/B72+8ibvuupMXX/xnjW0zM7NVOZA3Myti0eJFtGzRgjZt2vDRRx8xeNCgaut/9tlnLFu2jE02aUdFRQVjxz7HP194YY2Pf+RRR3PzzTfx/vvvAzB//nxGjRoFwCabbEKTJk2YMb3mwLxg3333Y968ufz5z3exbNkyFi1axGuvvlq0bt9z+/LQQw8y5PbbmZ/uAMyePYsZ78+orLNo8SJabbABbdq0Yfbs2dxxx7BV9vG1Tp144vHHWb58OePGjWXChJVpN8+OGcO7775LCIENN9yQpk2b0qRJ05LPxczMIgfyZmZFHH/8CSz99FO677cvJ55wAl27dq22fuvWrel34S/p94sL2Gefbjzx+BPst1/3tTj+8XTfrztnnnE6e3fZi5NOPIHXXouBd6tWrejd+zR69epJt25defXVf9W4v9atWzNo0GCeHTOG7x6wP4f94Pu8/PLLRet+Z9ddue22PzFh4gQOO/wwunXryllnnknnzp35yU+OA+D008/grTffpFvXvTn77D5894DvrrKPfv0uZMyYMezTrSuPj3ic/fffv3LdtHff5fTTf0qXvfbkpBNP4Oijj2H33Xdf00tlZrbeUvZWaN6uu+4axjz7XD02x8zMzMzsi6dtmw0nhBA61+U+PSJvZmZmZlaGHMibmZmZmZUhB/JmZmZmZmXIgbyZmZmZWRlyIG9mZmZmVoYcyJuZmZmZlSEH8mZmZmZmZciBvJmZmZlZGar2C6EkzQGm1V9zimoPzG3gNlh5cF+xUrifWKncV6wU7idWqq+FENrU5Q4rqlsZQtisLg+2JiSNr+tvwbIvJvcVK4X7iZXKfcVK4X5ipZI0vq736dQaMzMzM7My5EDezMzMzKwMlUMgf2tDN8DKhvuKlcL9xErlvmKlcD+xUtV5X6n2YVczMzMzM2ucymFE3szMzMzMchplIC9pJ0kjJS2W9L6kyyQ1beh2WcORdJSkRyTNkPSJpAmSfpJZ311SqOL1ZEO23eqXpF5V9IMzqqh/Q1p/TX231RqWpGMlTUx/U2ZIulPSl3J1zpI0QtK81E+6N0xrrb5I2kHSYEn/krRc0ugidSTpIknvSVoi6VlJuxSpV2Mfs/JUUz+R1FzS3yT9N/WROZKekLRbNfvcKvWVIGnDUtrR6AJ5SZsATwMBOBy4DDgfuLQh22UN7jzgE6AvcBgwCrhb0tlp/USgS+51TFr3RP021RqJA1i1PwzPV5C0E3AKsLB+m2YNTdJhwD3A88T/ay4E9gUek5T9v/EkoB3gAYH1xzeAQ4G306uYXwIDgCuBHxD/f3pa0haFCrXoY1aeauonTYmx7BVAD+A0YAPgGUlfqWKfVxP7UskaXY68pP5AP2DbEMLCVNYPGAhsUSiz9Yuk9iGEubmyu4EuIYTtqtimH/Ef0NYhhPfroZnWCEjqBQwF2oQQqv2DKOlp4AXgROD+EMIF676F1hhIuhfYMYSwW6bsMOBhYKcQwpuprEkIYYWknYHXgP1DCKMbos1WPwrvefr9fqB9CKF7Zn1LYDZwbQjhslTWGpgKDA4hXJzKSupjVp5q6idVbLMhMA/oH0K4LrduH2Lf+C0xoK/x/zBohCPywCHAk7mA/V6gFbBfwzTJGlo+iE8mAZtXs9mxwBgH8VaMpCOBrwO/a+i2WINoBnyUK/sw/VShoPAfta0/SnjP9wbaAn/LbLMIeJQYwxSU1MesPK3h34ZFwFKgebYwpY/fRMxCqdW3BDfGQL4T8Fa2IITwLrA4rTMr2Bv4d7EVknYEvkO8rWnrp/9I+lzSZEmnZ1dIagVcC/wy/Qds658hwD6STpLUVtJXgcuBUSGEon9XzJJOwHLgnVz5m6wap7iPWeF5ioqUdnUVse/kY5MzgJbAH2q7/8YYyG/Cyk+sWQvSOjMkfZeYc1hVp/8J8BnwQL01yhqLmcTc1ROJuasvAoMk9c3U6Z/q/bn+m2eNQQhhBNCLOK/zR8BkYk7rEQ3YLCsPmwCfhBCW58oXABtIag7uY1bpQmI8MhPoCRwaQphWWClpU+DXwHkhhM9qu/PGGMhDfDggT1WU23pGUkfgbuDhEMKwKqodC/wjhDC/vtpljUMI4ckQwuUhhH+EEJ4IIZxEvAV+saQmkrYDLgDODY3tISGrN5L2BwYBvwf2J/7NaAc8KM+SZjWrKk6pXOc+ZskwYHfiRB0TiA8775RZ/xvgxRDC42uy84q1bl7dWwBsXKR8I4qP1Nt6RFI74iw07wInVFHn28Tc59/UY9OscbsfOBroSHwA+gngLUkbp/VNgBZp+SMH+OuFa4FHQggXFgokvUJM7TycIrMcmSULgDaSmuZG5TcGFmdGVd3HjBDCLGAWgKQngDeIsx6dJOkbxJnT9s38f7RB+rmRpOUhhCXV7b8xjsi/RS4XXtLWQGtyufO2fpG0AfAY8SGRHtXkNh8LLCE+/W2WFYCvEW9tL8i8tgb6pN+3arDWWX3qBLySLQghTCb+7di+IRpkZeMtYorMDrny/DN+7mO2ihDC58TZrwrTT+5IfCj6BVb+f1RIGZ5OfAC2Wo1xRP4J4BeS2oQQPk5lxxA7/piGa5Y1JEkVwH3ETt81hPBBNdWPAR4tZdomW2/8mDgTwDSgN5D/oo17iX9fbgHm1G/TrIFMA3bNFkj6OnGGtKkN0SArG88Tv3viKOLDq4WBph8Q8+EL3MdsFWnq0l2BcaloLDHtKutgYl79ocB/a9pnYwzkBwHnAMMlXUn81DIQuM5zyK/X/kjs1D8H2knaK7NuUgjhU4BUvh3xC6RsPSTpAeAl4FXiqNkx6XVOmi5sfJFtlgLveX7w9cog4HpJ7xMHkDoAlxADrMpcVUmdiSlZW6ei/SS1B6aGEFbrS1b+UlB+aFrcCmibpqsFeDyEsFjS74ABkhYQR+HPI2Y5ZEdQS+pjVp5q6ifE9KlDgL8D7wNbAmeln9dB5dTao3P77Zh+fa6UAclGF8iHEBakGUluJs7J+iFwPTGYt/XXQenn74us246VoxvHEmcH8Le5rr8mE3MOtyY+fPZv4KQQwl0N2iprbG4ElgFnEqd++5A4OtY/l7bXhzjTRMHA9PMO4owk9sWzOfEOcFZhufD/ze+IgXt/YFPiAMH3QgizM9uU2sesPNXUTyYTn+W7jjjT0UziLGqdQwhv1FUjGt03u5qZmZmZWc0a48OuZmZmZmZWAwfyZmZmZmZlyIG8mZmZmVkZciBvZmZmZlaGHMibmZmZmZUhB/JmZmZmZmXIgbyZNQhJvSRNkPSxpAWSJkm6ro6PsYekgXW5z8ZM0kBJc+tgP19N+9o4V95LUpCU/2bceifpUUm/qqHO91N7O6blzdN5dczV6yxpnqSN1l2LzczqngN5M6t3kvoDfwKeBI4ATgIeBg6r40PtAVQb7FlRXyVet41z5SOALsDi+m5QlqQ9iV9rflNNdXM2J55Xx2xh+obWV4C+ddA8M7N60+i+2dXM1gt9gMEhhIsyZY9KurShGmQ1CyHMAeY0dDuAc4CHQwjz63CfQ4FrJF0eQvi8DvdrZrbOeETezBrCxsCsfGHIfNW0pJclDc3XkXSHpInp92aSrpH0rqRPJb0v6UFJzSX1Io3YpvSKIGl0Zj87SxqRUns+lnSfpC0y67unbb4r6WFJiyS9I+kgSU0lXS1prqQZks4r5aQlnSbpNUlLJc2WdL+kjST1kLRC0na5+tul8sMyZT+S9JKkJSkd5HFJ21ZzzHaSBqfjLZX0fBrRrqp+d+DRtPi/dA2mpnWrpNZI6piWj5U0VNJCSdMlnZDW90vvyRxJV0pqkjtWte9BFe1rA/wIuD9XrpQ280Ha151A28z6jsBraXFUoU9kdvEI0A74f9Ud38ysMXEgb2YNYSJwtqSekjatos6fgKOy+djp9x8TR08B+gPHAwOA7wHnAh8BTYlpINemel3S66y0nx2AcUBL4ESgF/AN4l0B5doxGBhLDB6nEQPIm4E2wHFp+VpJe1V3wpIuTvsaA/wQODO1dUPg78D7QM/cZr2II+CPp32cCAwH/gMcDZwMvA1sVsUxWwBPp2vzi3TcOcDT1QTME4EL0u9HEK/bj6o7N+BKYCbxvXkOuEPStcTUplOAG4B+qc2FttXmPcjaG2gFPJ8rPwe4BLgVOBJYAlyVWT+T2FcAfsbKPgFACGEh8AZwYA3nambWeIQQ/PLLL7/q9QV8C/gvEIAVxADqMqBtpk5bYBFwcqbsFOBTYNO0/BhwbTXH6UMa6M+V3wVMBppnynYElgM90nL31L5fZerslMqeyZQ1Id5duLKadmxMzCu/rpo6lwP/A5SWBUwFrskcZwYwvJp9DATmZpZPBZYBO2bKKogfBK6uZj/fT+fZMVfeK5VvmJY7puWhufftM+AdoGmm/CXgr7V5D6po20XAnFxZU+IHoVty5U9lzwPYOS13r2Lfw4BxDf3vwy+//PKr1JdH5M2s3oUQXgW+Tny49Y/EoHUAML4wAh/iCOn9xOCxoBfwSAhhXlp+BeiVUji+VcNIbtaBwIPACkkVkiqIQfRUoHOu7sjM71PSz2cy57KC+KFkq2qO14U4irxaqlDGEGBb4gcIiA9zbpvZ5mvAl2rYR96BwARiikzhPCHeFcif59qovEbpfZsDjAkhLM/UmcKq16g270HWFkB+Zp6tgS2JD0xnDa/FOZD2W21qj5lZY+JA3swaRAjh0xDCoyGEPiGEnYDexBHZUzPVbgf2kbS9pO2BfYgBb8HlwB+IKTP/At6T9PMSDt8euJA4cpx9fYUYFGZ9mGnzsnxZsoyYIlKVQvrQzKoqhBD+C4wmpsuQfr4UQnij1H0U0R7Yi9XP82RWP8+18WFueVkVZdlrVJv3IKsl8a5MViH4/iBXnl+uyadU/z6amTUqnrXGzBqFEMLtkq4COmXKnpX0DjF3XMT0iX9k1i8l5kVfImlH4AzgBkmTQwh/r+Zw84mjwX8qsm6t52EvonAHYcsa9v8n4DbF6TmPAM6vYh+lmg+MJ+bj5+WD4fq2pu/BfFafFrPw4PTmufL8ck02Tvs3MysLDuTNrN5J2jyE8EGubDNgI2B2rvoQ0kOqwJ25dI1KIYR3JF1AfJBxJ+IDpMvSvlumoL9gJDFfekIIIay2s7r3AvHhy56sfJC0mOHEOwz3Eu+Y3ptZN5mYI9+TlbPK1GQkcBDwbv5616Bw52Fdjk6v6XswGfiSpBYhhMKHkfeIwfzhxPe94IjctjWdV0fiw8NmZmXBgbyZNYTXJD1MHF3/gJgLfgHxgdA7cnXvIKbQVBAfRqwk6UFiDvgkYqB8ZKr3bKryVvr5c0nPAAtDCJOJD4W+BIyQNIQ4ArwVcXaXYSGE0XV0ngCEED6U9GvgN5KaE2ehaQH0AC4NIcxI9ZZK+gvxw8g9IYQPM/tYIakf8JdU5x7ig5sHpLrjixz6TuJditGSriHm8m9KnE1mVgjh+iqaPDn9PF3SvcDiEMJrVdRdUwNZs/dgHNAM+CbxbgMhhOXpbs41it9s+xxxBp2v57Z9l/SBStJHwGe569aZOAOPmVlZcI68mTWEy4ijnzcSg/lfE2eu2SOE8L9sxRDCLOBF4mwik3P7eZ44peLdxAcddwN+nAnOngOuBn6e9jE47fNtYu74YuJ0hU8AlxLTTaawDoQQriCmuByY2jqYmMrxca7qQ+nnkFw5IYS7iQFqJ+KDwHem34t+SVO6C7E/cfaWS4nX+vfEZxFeqqat04gfrI4gBs6l3gEo2Zq+B2m714FDcqtuAH5L/ODyAHFaz365bZcCpxH7yRjg5cI6Sd8hTuNZ2wdkzcwajOrnrrKZ2ZqR1I6YUtInhHB7Q7dnXUsjy8cA26UZcSxHUl/g1BDCznW4zyuA3UMInkfezMqGR+TNrFGS1CZ9A+nNxFHrexq4SeuUpK9J+hFx1P4mB/HVuhXYTFKdBN2SWhNH6i+vi/2ZmdUX58ibWWO1GzCK+G2qJ4UQFjdwe9a1wcCewCPElCOrQghhkaSeQOs62uU2wGV1/WyEmdm65tQaMzMzM7My5NQaMzMzM7My5EDezMzMzKwMOZA3MzMzMytDDuTNzMzMzMqQA3kzMzMzszLkQN7MzMzMrAz9fwk+3waDLCf9AAAAAElFTkSuQmCC\n", "text/plain": [ - "
" + "
" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -272,7 +272,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2023-08-25T18:25:51.432148Z", @@ -284,12 +284,12 @@ "outputs": [ { "data": { - "image/png": "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23379omePXsKe3t7IZVKhb29vWjatKmYPn26uHz5ssZ9FeNyCSGEGDFihLL9hQsXNG4zadIk5TZ///23xm3Wr18vAAh/f/9ixaFJbm6u2Llzp3jvvfdEjRo1hLGxsTA2NhY1atQQvXr1EqtWrRIJCQlq7dLS0sRXX30l6tatK2QymahRo4YYO3asePbsmfD39xcARFBQkFq7wMBA0bx5cyGTyYSNjY0YOHCguH//vsbX8sqVK2LKlCmiefPmwt7eXshkMuHk5CR69eoljh07prbvTz75RAAQmzZt0vn8FbEqfiQSiTA3NxdOTk6iZ8+eYtGiReLp06cF7uPmzZvi3XffFebm5sLU1FR06NBBHD9+XAQFBWl8vQq6PgpRUVFiyJAhwtHRURgbG4t33nlHLFiwQGRnZ2t9T9y5c0f4+fkJW1tbYWBgoPYeBCCcnJw0Hi8wMFB07txZWFpaCiMjI1GnTh0xffp08eLFC7VtC3rfCaH9Pevl5VVgO23yvz6afl59jyuOX5Q2QuS9F9atWyc6dOggrKysRKVKlYSdnZ3o0aOHOHTokMbYCjunf/75R/Tv31/Y2toKY2Nj0bBhQ7F06VKRk5NTpGsghBDPnj0T48aNEw4ODkImkwlXV1exePFikZubq/W1vXjxoujatauwtLQUEomkWNefiIiIiEgXEiGKMKU/0WvMzc0NmZmZ+Pfff2FoaFje4RAREREREb01SndgP1EZefbsGf755x9s2bKFiQUiIiIiIqIyxp4LRERERERERKQXnXou5ObmIjo6Gubm5pBIJKUdExEREREREREVgxACycnJqFatGgwMSmyByELplFyIjo4ukaXoiIiIiIiIiKj0RUVFoXr16mV2PJ2SC+bm5gCAm7fClf9PRERERERERK+X5ORkvONav8yf3XVKLiiGQpibm8PCwqJUAyIiIiIiIiIi/ZT1lAZlNwCDiIiIiIiIiN5ITC4QERERERERkV6YXCAiIiIiIiIivTC5QERERERERER6YXKBiIiIiIiIiPTC5AIRERERERER6YXJBSIiIiIiIiLSC5MLRERERERERKQXJheIiIiIiIiISC9MLhARERERERGRXphcICIiIiIiIiK9MLlARERERERERHphcoGIiIiIiIiI9MLkAhERERERERHphckFIiIiIiIiItILkwtEREREREREpBcmF4iIiIiIiIhIL0wuEBEREREREZFemFwgIiIiIiIiIr0wuUBEREREREREemFygYiIiIiIiIj0wuQCEREREREREemFyQUiIiIiIiIi0guTC0RERERERESkFyYXiIiIiIiIiEgvTC4QERERERERkV6YXCAiIiIiIiIivVQq7wCIiIiodKW8BCA016VlSJCdq7nOWAoYVdLcUFoJkElLJj4iIiKq+JhcICIiquByBZCYKtFa/yTeALlaEgjFZSoXsDTRkngwFDCTl+zxiIiI6PXG5AIREVEFJwQQ/bxsRzqmpkuQmq45oWEmFzCTl3A2g4iIiF5rTC4QERFVAOmZwINYw/IOQyepLyW49UhzrFJDAZeqTDwQERG9aZhcICIiqgCEAHJyyjsK3RQUq4H20RtERERUgTG5QERE9JpISJUg5sWbvZBTVrYEN6O098CoXz2HCQgiIqIKiMkFIiKi14QQKPGJF19Hb8M5EhERvW2YXCAiIipDqS/zln/U5GUmv7J/nqT9GlibCVSqGNNOEBERvXWYXCAiIipDKekSPEt6s4c+6CM2Qfu1MZfnMLlARET0muKnGyIiIiIiIiLSC3suEBERlbDnyRKtQxzSM8s4mDdIbKIBDLV8LWJjlgu5rGzjISIiov8wuUBERFTCUl9KkJzG+RNKWkHX1MxYArlMlGE0RERElB+HRRARERERERGRXthzgYiIqBgSUiXI0bKkYmZ22cZCQMpLCbK1vB5yIwETDpkgIiIqVUwuEBERFUNcogSZWRz68LpISJEA0Px6VLbKhQmHTBAREZUqDosgIiIiIiIiIr2w5wIREZEWgl92vxlEwa+lhB1QiIiI9MbkAhERkRY3HhqWdwhUAuISDRCXqLnO0lSgup2WyRqIiIhIZxwWQURERERERER6YXKBiIiIiIiIiPTCYRFERPTWysnNW1KS3l4ZWcDzZM33gKEBYGXKiTeIiIh0weQCERG9tbJzgCcv2InvbfYyU4InLzQnF4yNBJMLREREOuInKiIiIiIiIiLSC5MLRERERERERKQXDosgIqI3WupL4GmC5lx6Lnu8UwEysiW490TzvSORALWqcAlLIiIiBSYXiIjojZaTK0F6BidtpKITudB670jY95OIiEgF/2kkIiIiIiIiIr2w5wIREVV4Obl5P9rqiEqcADKztVdLDfOGThAREb0tmFwg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AwAQevmysiwnJwe7du1U2S4lJQXZ2dkqZXXr1oWBgQEyMzOLcjpERET0GuGcC0RERGVMKpWiYcOGCA0NhZGRERo0aKBS7+Hujk2bNgEAmniWXHJBKpVi2bJlGDduLEYMH45OnTvDs4kn5HI5YmNjERwSjJiYGLRr317Zpn2HDvjl558xe9YsuHu4I+JOBA4ePKA2VKNOnTpwc3PDTz8FIDEpEZYWljh85LDKHA0AcOHCBSyY/z26+vjAyckJOdk5CAwMhIGBAbp06VJi50pERERli8kFIiKicuDRpAlCQ0Pxzv8fBqFS59EEmzZtgqmpKerVq1+ix3VydsaO3//Atq1bcfz43zh96hSysrJga2uLRo0bY+zHY9HB678lLkeP/ggv09Nx6NAh/PXXEbi6voPlK1YgICBAbd/fz1+Ab7+dh/W//gpzc3P49e2L5s1bYOzHY5Tb1K9XD23atMWJkBDExsbC2NgY9erVw8r/+z+4ubmX6LkSERFR2ZEI8WpnR3VJSUmwtLTEo8fRyjGVRERERERERPR6SUpKQnXHakhMTCzT53fOuUBEREREREREemFygYiIiIiIiIj0wuQCEREREREREemFyQUiIiIiIiIi0guTC0RERERERESkFyYXiIiIiIiIiEgvTC4QERERERERkV6YXCAiIiIiIiIivTC5QERERERERER6YXKBiIiIiIiIiPTC5AIRERERERER6aWSLhsJIQAAycnJpRoMERERERERERWf4rld8RxfVnRKLiiCe8e1fqkGQ0RERERERET6e/78OSwtLcvseBKhQzojNzcX0dHRMDc3h0QiKYu4SkRSUhJq1KiBqKgoWFhYlHc4RGWK9z+9zXj/09uO7wF6m/H+p7ddYmIiatasifj4eFhZWZXZcXXquWBgYIDq1auXdiylxsLCgn9Y6K3F+5/eZrz/6W3H9wC9zXj/09vOwKBsp1jkhI5EREREREREpBcmF4iIiIiIiIhIL290ckEmk+Gbb76BTCYr71CIyhzvf3qb8f6ntx3fA/Q24/1Pb7vyeg/oNKEjEREREREREZE2b3TPBSIiIiIiIiIqfUwuEBEREREREZFemFwgIiIiIiIiIr28kcmF9PR0zJ49G/Xq1YOxsTGqVauGkSNH4vHjx+UdGpFe0tLSsHfvXowaNQr169eHsbExTE1N4e7ujnnz5iElJUWtjUQiKfSnU6dO5XA2RMXj7e1d4P18+PDhQvfRpUsX5faPHj0qg6iJSs7FixcxcOBAVKtWDVKpFFZWVmjfvj3Wr1+PV6fSCg8Px7JlyzB48GC4uLgo7/vIyMjyCZ5IB5cvX8aCBQvQr18/VK9eXXnfFmbDhg1o0aIFzMzMYGNjgx49euDMmTNat8/IyMDChQvh6ekJMzMzyGQy1KpVCx999BHu3btXkqdEpLOi3v/79++Hv78/GjduDDs7O0ilUtjb26NHjx4IDAzU+bjffvut8li//fZbsWJ/4yZ0fPnyJTp27Ihz586hatWqaN++PSIjI3HhwgVUrlwZ586dQ+3atcs7TKJiWbt2LT766CMAwDvvvINGjRohKSkJZ86cQXJyMlxdXRESEgJ7e3tlm+HDh2vd34EDB/Ds2TPMnj0bc+fOLe3wiUqEt7c3QkJC0L9/f5iZmanVT5kyBY0bN9bafsOGDRgxYgQkEgmEEIiKikL16tVLM2SiErNr1y4MGjQIOTk58PT0RJ06dRAXF4eTJ08iOzsbQ4YMwZYtW5Tbf/bZZwgICFDbz/379+Hs7FyGkRPpzs/PD/v27VMrL+ixRXGvy+Vy+Pj44OXLl/j7778hhMDOnTvh5+ensn3+ZwYrKyu0adMGxsbGCA0NRWRkJMzNzREUFISmTZuW9OkRFaio9/97772H3bt3o2HDhqhZsybMzc0RGRmJ8+fPAwC++OILfP/99wUeMzw8HO7u7sjMzIQQAps3b8awYcOKHrx4w3z11VcCgGjdurVITk5Wli9ZskQAEF5eXuUXHJGeNmzYIMaMGSNu3LihUh4dHS2aNGkiAIjBgwfrtK/4+Hghk8kEAHH79u3SCJeoVHh5eQkA4v79+0VuGxsbK2xsbISPj49wcnISAERUVFTJB0lUCrKysoS9vb0AILZs2aJSd+PGDWFjYyMAiOPHjyvL165dK2bMmCF27twpIiMjRf369Yv9/iEqKwsWLBCzZs0S+/fvFzExMcrPK9ocPXpUABC2trYqn2nOnDkjjIyMhJWVlYiPj1dpExAQIACI5s2bi4SEBGV5dna2mDhxogAgOnToUOLnRlSYot7/oaGh4tmzZ2rl586dE2ZmZkIikYjr169rbZ+bmys6dOggqlSpIvr06SMAiM2bNxcr9jcquZCRkSEsLS0FABEaGqpW7+bmJgCIS5culUN0RKXrzJkzAoCQyWQiIyOj0O1/+eUXAUC0atWqDKIjKjn6JBeGDBkijI2NRUREBJMLVOGEhYUJAKJ+/foa6z/55BMBQCxcuFDrPphcoIqosIer7t27CwBi2bJlanWK98XixYtVyvv37y8AiG3btqm1efHihQAg5HK53rET6auw+78go0aNEgBEQECA1m0UzwS//fab8Pf31yu58EbNuXD69GkkJibCxcUFTZo0Uat/7733AAB//vlnWYdGVOrc3d0B5I0ffP78eaHbK8ZSffDBB6UaF9Hr4vDhw9i6dSu++uoruLi4lHc4REUmk8l02s7W1raUIyF6faSnp+P48eMA/vusn5+2z/+6vJ/4XqKKTiqVAgCMjIw01j958gTTp09H586dMXToUL2P90YlF65duwYA8PT01FivKL9+/XqZxURUVhQTD0mlUtjY2BS47cOHD3Hy5ElIpVIMGjSoLMIjKnHr1q3D+PHjMXHiRPz00094+PCh1m1TU1Mxbtw4uLq6Yvr06WUYJVHJqV27NlxcXBAeHo6tW7eq1N28eRO//fYbrK2t0bdv33KKkKjshYeHIyMjA5UrV9Y4f462z/8+Pj4AgKVLlyIxMVFZnpOTg9mzZwMARo0aVVphE5W6sLAw7NixA1KpFF27dtW4zSeffIL09HSsWrWqRI5ZqUT28ppQfLDUNjGXovzBgwdlFhNRWVFM2OXr61toNn7Lli0QQqB79+7MylOF9b///U/l96lTp2LWrFmYNWuW2razZ89GZGQkgoODtWbviV53hoaG2LhxI959910MHToUS5YsQd26dREbG4uTJ0+iQYMG2LBhQ6EJZqI3SWGf/01NTWFlZYX4+HgkJyfD3NwcADBs2DAcPnwY27dvh7OzM9q2bQtjY2NcvnwZT58+xbRp0zT+e0L0uvrzzz+xa9cuZGVl4eHDhzhz5gykUinWrFmjscdmYGAg/vjjD8ydOxd169YtkRjeqOSCYhk+ExMTjfWmpqYAgOTk5DKLiagsHDx4EOvWrYNUKsW3335b6PYcEkEVWYcOHTB69Gi0adMGVatWRVRUFHbu3In//e9/mD17NiwsLPDpp58qtw8NDUVAQAD8/f3h5eVVjpET6a9t27YICQlB3759ERoaitDQUAB5XV67du3KFbHorVPY538g7xkgISFBJblgaGiI3377DTVr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", + "image/png": "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\n", "text/plain": [ - "
" + "
" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -310,12 +310,12 @@ "source": [ "#### Pulse library functions\n", "\n", - "Our own pulse library has sampling methods to build a `Waveform` from common functions." + "It is possible to convert `SymbolicPulse` objects into `Waveform`s via the `get_waveform()` method. Using our own pulse library you can build `Waveform` from common functions." ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2023-08-25T18:25:51.575812Z", @@ -328,18 +328,19 @@ "outputs": [ { "data": { - "image/png": "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UadOHWFpaSm8vLzExx9/LPLy8gye2/j4eDFw4EDh4uIizMzMSnX8iYiIiIhKQiJEBUw9T1SO+vfvjxMnTiAhIcGoV/wRERERERFR2Xrx3ylHVASlUomjR49i1qxZTCwQERERERFVEvZcICIiIiIiIiKTGDWho0qlQmJiIuzs7CCRSMo7JiIiIiIiIiIqBSEEMjMz4e7uDjOzihusYFRyITExER4eHuUdCxERERERERGVgXv37qF27doVtj2jkgvqsezXrsdxXDsRERERERHRCyozMxONfBtW+LO7UckF9VAIOzs72Nvbl2tARERERERERGSaip7SgG+LICIiIiIiIiKTMLlARERERERERCZhcoGIiIiIiIiITMLkAhERERERERGZhMkFIiIiIiIiIjIJkwtEREREREREZBImF4iIiIiIiIjIJEwuEBEREREREZFJmFwgIiIiIiIiIpMwuUBEREREREREJmFygYiIiIiIiIhMwuQCEREREREREZmEyQUiIiIiIiIiMgmTC0RERERERERkEiYXiIiIiIiIiMgkTC4QERERERERkUmYXCAiIiIiIiIikzC5QEREREREREQmYXKBiIiIiIiIiEzC5AIRERERERERmYTJBSIiIiIiIiIyCZMLRERERERERGQSJheIiIiIiIiIyCRMLhARERERERGRSZhcICIiIiIiIiKTMLlARERERERERCZhcoGIiIiIiIiITGJR2QEQERFR+UpKk0ApSt7OwVrAxqrs4yEiIqKXD5MLREREVZxSBcTdNzdYL0qRWACAtCxAYqDO2U4FV6dSrpiIiIheOkwuEBERVQFP84HEx/pHMwpR+gRCkQRgaLVPss2Qk6e/Vm4p4ObMxAMREdGrhMkFIiKiF4SqiCSBQgXk5hnqR1DxFEpAodQfj5kZoFQZTi6Yc8YnIiKilw6TC0RERC+IJ9kSJKZW/Sfv7FwJrt/TP0xDaiHQoJaqgiMiIiKi8lb172CIiIiIiIiIqFKx5wIREVEFSs2QIC1b/3ACpbKCg6kEBUoJbjw0/N1GPVcVzF6c0R9ERERkJCYXiIiIKpBCCeTlv8JPz+IV338iIqKXFJMLREREZaxA8WxyRn0UKj5YFyW/AJAYOERSC7BXAxER0QuKyQUiIqIy9jDNDJk5fAoujZsP9U8ECQDebkpYWVZgMERERGQ0TuhIRERE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", + "image/png": "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\n", "text/plain": [ - "
" + "
" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "gaus = library.gaussian(duration=num_samples, amp=amp, sigma=sigma, name=\"Lib Gaus\")\n", + "gaus = library.Gaussian(duration=num_samples, amp=amp, sigma=sigma, name=\"Lib Gaus\")\n", + "gaus.get_waveform()\n", "gaus.draw()" ] }, @@ -529,7 +530,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2023-08-25T18:25:51.956307Z", @@ -541,12 +542,12 @@ "outputs": [ { "data": { - "image/png": "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5Ihat26tunXravbs2WrRokWyMWQcPScAAAByPYN0kWScnJyMMcacPHnSeHp6Gi8vL3P69GnbNKNHjzaSzIgRI+zm3b59u/nrr7+SLXP58uXG1dXVBAUFmaSkJLu60qVLG0mmePHiZv/+/bbyXbt2GTc3N+Pp6WnKlCljpk2bZqu7du2aadGihZFkPv/8c7vljR071kgyzZo1MydOnLCb55lnnjGSzOuvv243z6FDh4wkk97TJT3ztGvXzkgyL774orl69aqt/OTJk6ZOnTpGkvnvf/9rN09ERISRZCIjI1Ncd+nSpdMVrzHGJCYmmkWLFpnr16/blZ8+fdoWx9q1a+3qvvjiixSP94gRI4wk88UXX9iVt2zZ0kgy/fv3Nzdu3LCVL1++3Li4uBhJJiwsLMVlSTKTJk2yq3vppZeMJPPUU0+leTsHDBhgJJnHH3/cXLhwwVYeHx9vOxa3n1PGGDNr1iwjyYSGhppr164ZY4zp2bOnkWReeumlZOtYvXq1OXnypF3ZzZs3zXvvvWckmT59+tjV3X6etWjRwly6dMlWZ93H5cuXN35+fmbz5s22umPHjpkiRYoYSebAgQN2y7R+fnx8fMyWLVts5RcvXrR9Rl588UW7eVI7bhnZZxlhXf+d59Odbt68adzc3Iwks2fPHlt5WFiYkWQOHTqUbB7rPr7b+TVmzJgU15fR43nnum5nPT63++eff4yHh4dxcnIyP/zwg926rOd6nTp17Oaxnh+SzNChQ83NmzdtdRMnTjSSTNOmTZOt3/odcq99nRb79++3HY/bzzVjjAkNDTWS7Lbndh999JGRZF555RVb2csvv2wkmZdffjnFeaKjo40kU6tWrTTF16VLlxS/P6wWL15sJJlHH300TcubP3++kWRKlSpld66dOnXKVK9e3XY8bq9LyzkBAABwv6LnigOKFi2qF154QQkJCRozZsw9p69Ro4aqVauWrDw8PFzdunXTgQMHUu198P7779t6aki3Hgto166dLl++rJIlS6p///62OldXV7344ouSbnW/trpx44bGjh0rLy8vzZ8/3+7uqqurqyZPnqyAgAB9+umndnfgXVxcVKlSJVWqVOme25gR0dHRWrZsmerWrasJEybYdZsvWrSoPv30U0nStGnTsmT9t7O+QcPFxcWuvHDhwrYBJ3/44YcML3///v365ZdfVKBA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uNDhuBo1aiRJWrFiha0bfmav416aN28uJycnLVmyRImJiWme78iRIxowYIDc3Nz09ddfa86cOXJ3d9fgwYN16NAhu2nvth9//fVXnTp1yrGNSKeU9uvu3bsVHR2t/Pnz3/WHsZTxfZZVdu/ebXtt953JDmsCb+/evcnmy+j3TEaOpzXJk97PYdOmTSUpxUfzrl+/rm+++cZuupywbNkyPf3003J2dtaiRYvu2ROwffv2kqSffvop2aNJp06d0rp16+Tn52e3nMaNG8vX11cHDhxQdHR0smV+++23kqQOHTqkKeY2bdooX758WrdunU6fPm1Xd+3aNf30009ycnJSu3bt7rksFxcX1a9fX1LKn62VK1em2MZk9JwAAAC4H5BcyST+/v4aMmSIrl27ppkzZyarL1++vPLly6fVq1fbDYh69epV9e/fP1uSHW5ubho2bJguXryoRx99NMW7zceOHdNXX32VrKxy5cqqXLlylsRVv359tWrVShs2bNDAgQMVHx+fbJpt27Zp+fLlaV5m8eLFderUKV24cCFdsVgHUp0+fbpd+bfffqsvv/wyXctKSYUKFdSyZUudP39er7/+ut1jGj///LPmz5/v8DrupUSJEurbt69iYmLUo0ePFH8Y79+/X999953t76SkJD399NO6cOGC/vWvf6lmzZqqWrWqxowZo/j4eD311FO2wU2l/+3HOXPm2CWRjh07Zvdmq+wyefJkbd261fb35cuXNXjwYBlj1KdPn3sOQJuRfSbdGgjXYrGk+bGde7lx44bmzp2rpk2bKiEhQU8//XSyH8NhYWGSpPHjx9uNr7F69WpbQia9MnI8CxUqJBcXFx04cMDu3LiXZ555Rh4eHpo/f76WLl1qK09KStJbb72lY8eOqXbt2vdMaKTFm2++qcqVK+s///lPmufZsGGDHnvsMRljtGDBghQHWL5TvXr11LhxY50+fVqvv/66rfzGjRsaMGCAEhMTNWTIELu3lLm6umrQoEGSpIEDB9rt9wkTJmj79u0KCwtL9mai//znP6pcubLefPNNu/JixYqpR48eun79ugYMGGCX4Bg2bJjOnDmjXr16qUiRImnaDy+88IIkacSIEXa9VM6ePZvqG6cyek4AAADcD3gsKBMNHTpUkydPTjE5UKRIET3zzDOaMWOGgoOD1aJFC3l4eGjdunW6efOmevfu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Wc/l2NhYvfnmm+rfv78OHDigs2fPaubMmfLw8NBHH32kKVOmJJt37Nix+uijj5SYmKgVK1bom2++0a5du1S6dGm1atWKR+aQayxbtkwzZ86Ui4uLRo0a5dCybn+dc2b/bia5AuC+8c8//2j48OEKCwtT7969M7ychIQEPfroozp79qw6d+6s/v37Z16QQBabN2+e6tWrp8DAQG3cuFGXLl3S3r171bt3b40fP14tWrTQtWvXJN26KBg0aJAqVqyoN998M4cjB7JHWr7jrXf2/fz89N///lehoaHy9vZWy5Yt9eOPP8pisejbb79Nd5dwILtZz+UbN26obdu2mjJlisqVKyd/f3/17dtXH374oSRp9OjRdvNdu3ZNjz/+uIYOHaq33npLhw4dUnx8vFavXi13d3c9+eSTtnmBnLR792716tVLxhh9+OGHtrFXciOSK3lI/vz5JUmXL19OsT4hIUGS5O3tnW0xAZlp4MCBun79uqZPn57hZSQmJqpbt27asmWLmjRpkmzcCiA327dvnyIiIlSoUCEtWbJE9erVk5eXlypUqKBPPvlEjzzyiKKiovT5559Lkt566y0dPXpUU6dOpXcWHghp/Y63XjN169bNdofSqnr16qpbt64k6ddff83agAEHWc9lSerTp0+yeuvNqGPHjmn//v228tGjR2vhwoUaNGiQ3nvvPZUpU0be3t5q3ry5li5dKi8vL40cOVJnz57N8m0AUnPs2DG1adNG58+f1yuvvKIXX3zR4WXe/pnJ7N/NJFfykFKlSkmSjh49mmK9tbx06dLZFhOQmZYsWSJPT0/1799fzZo1s/174oknJN36AraWnTx5Mtn8SUlJioiI0H//+1+FhITop59+ossr7ivz589XYmKi2rRpY3dxYNW9e3dJ//tB+NNPP8nd3V2jRo2y+8w0a9ZMknTz5k3b39HR0dm1GUCWSM93vPVaqEyZMinWW8tPnz6dFaECmeb26/qUzmdPT08VKVJEkv35/NVXX0lK+W0ppUqVUv369XX58mX9+eefmRwxkDbnzp1T69atdfjwYfXp00fjxo3LlOX6+PjY3oKV2b+bGdA2D7F2kYqKikqx3lqenkF5gNzmwoULWrt2bYp1V69etdWl9Oq0wYMHa968eapYsaJWrFjBQIW471gb+9RejWkttz6DL9l/LlJirbtw4UImRQnk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hx/WlClTbNNZLBbbD8rmzZvLYrHY/t05Xsfy5cvVvn17FS5cWG5ubipXrpxeeeUVxcbGJovp9keNVq1apYceekje3t4qUqSInn32WduYMqdPn9bzzz+vEiVKyN3dXfXq1cvQ40mJiYmaPn26mjRpogIFCsjDw0Ply5dXnz599Oeff0qSvv32W1ksFvXs2TPV5Tz33HOyWCz64osv7MoTEhI0ZswY1alTRz4+PvLy8lLlypU1cOBA7d27N81xpmcfpmTWrFm2R1wOHz5sd7yaNWtmmy61MVesjxPduHFDo0aNUvny5eXh4aEqVarYbfPq1avVvHlz+fj4yM/PT08//XSqMd64cUPTpk1Tw4YN5ePjIw8PD4WEhOijjz7SjRs30rxvJOnzzz9XUlKSevTokeo0P/74oxo2bChPT0/5+/ura9euKR4D6/g3hw8ftm279d+dj1R17txZHh4emjFjRrriBQAgM/FYEAAAaRQYGChJ+u677/Tmm2/e8+68m5ubIiIiNGHCBM2dO1cDBw5MNo31B+Fzzz1nKxs2bJjGjRsnNzc3PfTQQypUqJBOnjyp7du3a//+/bblREREaP369Tpw4IDCw8MVEBBgW0b+/Plt///GG29ozJgxcnV1Vd26dVWsWDFt27ZNEydO1I8//qgNGzaoaNGiyWJbtGiRpkyZooYNG6pNmzb6448/9Nlnn2nfvn369ttv1bBhQ928eVNNmzZVTEyMNm7cqDZt2mjz5s2qUaNGmvZpQkKC2rVrp19//VVeXl62BEtMTIzmzp0rX19f1a5dW506dVJAQIC+//57xcbGJnv849KlS5o3b558fHz0+OOP28pPnDihVq1a6e+//5afn5+aNWsmNzc3HTx4UNOnT1eFChVUsWLFe8aZ0X14u/LlyysiIkKzZ8+Wl5eXHnvsMVtdeno7de/e3ZZACQoK0tq1a9W3b19Jkre3t3r06KEGDRooPDxcv//+u7766isdOnRIv/76q934JVeuXFH79u0VGRmpggULqkGDBnJ3d9fGjRv18ssvKzIyUosWLVK+fGm7F7dkyRJJsksU3W769Ol64YUXZLFY1LRpUxUrVkx//PGH6tWrpw4dOthNGxAQoIiICH377bdKSEhQRESEra5QoUJ20+bPn1916tTRunXrdPDgQZUrVy5N8QIAkKkMAABIkwMHDhgPDw8j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aeXKlSnOd7fz+3a7du2SdKsnDgAAOYHkCgAAafTOO+/otddeS7EnxLFjx/T8889Lkjp27Gj7UXg763ghEydOlJRyQuCff/7RrFmzdPnyZbvyq1evKjIyUtL/emRIUvHixSUp1R+177zzjpKSktS1a1dFR0cnq4+NjU020G52KV68uJ5++mldvXpVERERio2Ntas/ffq0Nm7cmGy+YsWKqWPHjtq6davWrl2rmjVrql69esmmq1evnpo3b67Tp0/rueeeU0JCgl19TEyMduzYcc84M3sfFi9eXKdOncrR8UFKlCihvn37KiYmRj169NCpU6eSTbN//3599913aVqel5eXQkNDdeLECR07dixZfZ8+feTm5qa5c+faBhqWpMTERL388svJjo3Vvc5vq02bNkmSwsLC0hQvAACZjceCAABIo0uXLmnSpEkaN26cKlasqKpVq8rd3V1Hjx7Vxo0blZiYqPLly+ujjz5KcX7reCFHjhxJ9mYbq3PnzqlPnz4aOHCg6tSpo5IlSyohIUG//fabzpw5ozp16ujRRx+1Td+hQwe9//77evXVV/Xzzz/besyMGTNG/v7+6tmzp/7++2/9+9//Vu3atRUSEqKgoCAZY3TgwAFt375d+fPnTzHRkx0mTZqkPXv2KDIyUqVLl9ZDDz0kHx8fHT58WFFRUXrhhRdUv379ZPP1799f33//vST7ty3d6auvvlLLli01b948rVixQk2aNJGbm5sOHDig6OhojR8/XjVq1LhrjJm9Dzt27KjJkyerVq1aatSokdzd3VWpUiW99tpraZo/s0yaNEkxMTH67rvvtHz5coWEhKhUqVJKSEjQzp07tX//fnXq1Eldu3ZN0/Lat2+vzZs3a82aNXryySft6sqWLavx48dr0KBBCg8P10MPPaSAgAD98ccfOn/+vJ588knNnTs32TI7duyotWvXqmXLlmrevLm8vLxUqFAhffDBB7ZpLl26pC1btqhy5cq2NyMBAJDtcvZN0AAA3D/OnDljvvrqK9OrVy9To0YN4+/vb5ydnU3BggVN48aNzdixY82lS5fuuoxevXoZSeaTTz5JsT4+Pt6MHz/etGvXzpQpU8a4u7sbf39/U6dOHTNx4kSTkJCQbJ65c+eaWrVqGQ8PDyPJSDKHDh2ym2bt2rWmW7dupnjx4sbFxcX4+/ubmjVrmkGDBpm1a9faTRsREWEkmcjIyGTrioyMNJJMREREivGXLl3apPfy4tq1a2bSpEmmXr16Jn/+/MbDw8MEBQWZPn36mD///DPFea5cuWJcXFyMh4eHOX/+/F2XHx8fb95//31Ts2ZN4+HhYfLnz28qV65sBg0aZPbt25fmbUvPPrybS5cumUGDBpnAwEDj7OxsJJmwsDBbfVhYWIrHUJIpXbp0isvM6DG7ceOGmT17tmnRooUpWLCgcXFxMcWLFzcNGzY07733ntmzZ0+at+uff/4xTk5Opl27dqlOs2jRIlO/fn3j4eFh/Pz8TKdOncyuXbvMiBEjjCTzxRdf2E2fmJhohg8fboKCgoyLi0uK++DLL780ksz48ePTHCsAAJnNYsw9HiAHAACZ4vLlyypRooRu3Lih48ePp/raWtzbvHnz1LNnT0VEROSpgWbvd126dNGSJUt05MgRBQQEZMs6w8PDtX79ev3zzz/y9/fPlnUCAHAnxlwBACCbTJkyRRcuXFBERASJFQckJiZqzJgxkqSBAwfmcDS43ahRo5SUlKRx48Zly/qioqK0cuVKDR06lMQKACBH0XMFAIAsFBsbq9dff12nTp3SsmXL5OnpqV27dtlehYu0+/HHH7V48WJt2rRJf//9tzp37qxFixbldFi4Q9++fbVgwQIdOnTI9ormrNK5c2f9/vvvOnDggPLnz5+l6wIA4G5IrgAAkIViYmJUtmxZubq6qkaNGho3bpyaNWuW02Hdl0aOHKn33ntPfn5+atu2rSZPnqyCBQvmdFgAAAAkVwAAAAAAABzBmCsAAAAAAAAOILkCAAAAAADgAJIrAAAAAAAADiC5AgAAAAAA4ACSKwAAAAAAAA4guQIAAAAAAOAAkisAAAAAAAAOILkCAAAAAADgAJIrAAAAAAAADvj/9nM4T6ZCOBgAAAAASUVORK5CYII=", + "image/png": "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\n", "text/plain": [ - "
" + "
" ] }, - "execution_count": 14, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -554,7 +555,7 @@ "source": [ "with pulse.build(backend, name='Left align example') as program:\n", " with pulse.align_left():\n", - " gaussian_pulse = library.gaussian(100, 0.5, 20)\n", + " gaussian_pulse = library.Gaussian(100, 0.5, 20)\n", " pulse.play(gaussian_pulse, pulse.drive_channel(0))\n", " pulse.play(gaussian_pulse, pulse.drive_channel(1))\n", " pulse.play(gaussian_pulse, pulse.drive_channel(1))\n", @@ -600,7 +601,7 @@ "source": [ "with pulse.build(backend, name='Right align example') as program:\n", " with pulse.align_right():\n", - " gaussian_pulse = library.gaussian(100, 0.5, 20)\n", + " gaussian_pulse = library.Gaussian(100, 0.5, 20)\n", " pulse.play(gaussian_pulse, pulse.drive_channel(0))\n", " pulse.play(gaussian_pulse, pulse.drive_channel(1))\n", " pulse.play(gaussian_pulse, pulse.drive_channel(1))\n", @@ -644,7 +645,7 @@ ], "source": [ "with pulse.build(backend, name='example') as program:\n", - " gaussian_pulse = library.gaussian(100, 0.5, 20)\n", + " gaussian_pulse = library.Gaussian(100, 0.5, 20)\n", " with pulse.align_equispaced(2*gaussian_pulse.duration):\n", " pulse.play(gaussian_pulse, pulse.drive_channel(0))\n", " pulse.play(gaussian_pulse, pulse.drive_channel(1))\n", @@ -690,7 +691,7 @@ "source": [ "with pulse.build(backend, name='example') as program:\n", " with pulse.align_sequential():\n", - " gaussian_pulse = library.gaussian(100, 0.5, 20)\n", + " gaussian_pulse = library.Gaussian(100, 0.5, 20)\n", " pulse.play(gaussian_pulse, pulse.drive_channel(0))\n", " pulse.play(gaussian_pulse, pulse.drive_channel(1))\n", " pulse.play(gaussian_pulse, pulse.drive_channel(1))\n", @@ -808,7 +809,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.17" + "version": "3.9.12" }, "vscode": { "interpreter": { diff --git a/docs/tutorials/operators/01_operator_flow.ipynb b/docs/tutorials/operators/01_operator_flow.ipynb deleted file mode 100644 index 0a6fb2254517..000000000000 --- a/docs/tutorials/operators/01_operator_flow.ipynb +++ /dev/null @@ -1,2822 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Operator Flow" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "\n", - "Qiskit provides classes representing states and operators and sums, tensor products, and compositions thereof. These algebraic constructs allow us to build expressions representing operators.\n", - "\n", - "We introduce expressions by building them from Pauli operators. In subsequent sections we explore in more detail operators and states, how they are represented, and what we can do with them. In the last section we construct a state, evolve it with a Hamiltonian, and compute expectation values of an observable.\n", - "\n", - "### Pauli operators, sums, compositions, and tensor products\n", - "\n", - "The most important base operators are the Pauli operators.\n", - "The Pauli operators are represented like this.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.220124Z", - "iopub.status.busy": "2023-08-25T18:25:55.218526Z", - "iopub.status.idle": "2023-08-25T18:25:55.802288Z", - "shell.execute_reply": "2023-08-25T18:25:55.801595Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "I X Y Z\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/1460376431.py:1: DeprecationWarning: The ``qiskit.opflow`` module is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " from qiskit.opflow import I, X, Y, Z\n" - ] - } - ], - "source": [ - "from qiskit.opflow import I, X, Y, Z\n", - "print(I, X, Y, Z)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These operators may also carry a coefficient." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.807526Z", - "iopub.status.busy": "2023-08-25T18:25:55.806057Z", - "iopub.status.idle": "2023-08-25T18:25:55.812872Z", - "shell.execute_reply": "2023-08-25T18:25:55.812293Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.5 * I\n", - "2.5 * X\n" - ] - } - ], - "source": [ - "print(1.5 * I)\n", - "print(2.5 * X)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These coefficients allow the operators to be used as terms in a sum." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.817408Z", - "iopub.status.busy": "2023-08-25T18:25:55.816257Z", - "iopub.status.idle": "2023-08-25T18:25:55.823245Z", - "shell.execute_reply": "2023-08-25T18:25:55.822662Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.0 * X\n", - "+ 2.0 * Y\n" - ] - } - ], - "source": [ - "print(X + 2.0 * Y)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Tensor products are denoted with a caret, like this." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.827766Z", - "iopub.status.busy": "2023-08-25T18:25:55.826620Z", - "iopub.status.idle": "2023-08-25T18:25:55.833020Z", - "shell.execute_reply": "2023-08-25T18:25:55.832438Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "XYZ\n" - ] - } - ], - "source": [ - "print(X^Y^Z)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Composition is denoted by the `@` symbol." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.837753Z", - "iopub.status.busy": "2023-08-25T18:25:55.836589Z", - "iopub.status.idle": "2023-08-25T18:25:55.843676Z", - "shell.execute_reply": "2023-08-25T18:25:55.843080Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "iI\n" - ] - } - ], - "source": [ - "print(X @ Y @ Z)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the preceding two examples, the tensor product and composition of Pauli operators were immediately reduced to the equivalent (possibly multi-qubit) Pauli operator. If we tensor or compose more complicated objects, the result is objects representing the unevaluated operations. That is, algebraic expressions." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For example, composing two sums gives" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.847477Z", - "iopub.status.busy": "2023-08-25T18:25:55.847011Z", - "iopub.status.idle": "2023-08-25T18:25:55.854313Z", - "shell.execute_reply": "2023-08-25T18:25:55.853730Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1j * Z\n", - "+ -1j * Y\n", - "+ 1.0 * I\n", - "+ 1j * X\n" - ] - } - ], - "source": [ - "print((X + Y) @ (Y + Z))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And tensoring two sums gives" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.858119Z", - "iopub.status.busy": "2023-08-25T18:25:55.857660Z", - "iopub.status.idle": "2023-08-25T18:25:55.865015Z", - "shell.execute_reply": "2023-08-25T18:25:55.864335Z" - }, - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.0 * XY\n", - "+ 1.0 * XZ\n", - "+ 1.0 * YY\n", - "+ 1.0 * YZ\n" - ] - } - ], - "source": [ - "print((X + Y) ^ (Y + Z))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's take a closer look at the types introduced above. First the Pauli operators." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.868723Z", - "iopub.status.busy": "2023-08-25T18:25:55.868250Z", - "iopub.status.idle": "2023-08-25T18:25:55.878351Z", - "shell.execute_reply": "2023-08-25T18:25:55.877739Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(PauliOp(Pauli('I'), coeff=1.0), PauliOp(Pauli('X'), coeff=1.0))" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(I, X)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Each Pauli operator is an instance of `PauliOp`, which wraps an instance of `qiskit.quantum_info.Pauli`, and adds a coefficient `coeff`. In general, a `PauliOp` represents a weighted tensor product of Pauli operators." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.882960Z", - "iopub.status.busy": "2023-08-25T18:25:55.881794Z", - "iopub.status.idle": "2023-08-25T18:25:55.894792Z", - "shell.execute_reply": "2023-08-25T18:25:55.894185Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "PauliOp(Pauli('XYZ'), coeff=2.0)" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "2.0 * X^Y^Z" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the encoding of the Pauli operators as pairs of Boolean values, see the documentation for `qiskit.quantum_info.Pauli`.\n", - "\n", - "All of the objects representing operators, whether as \"primitive\"s such as `PauliOp`, or algebraic expressions carry a coefficient" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.900504Z", - "iopub.status.busy": "2023-08-25T18:25:55.898811Z", - "iopub.status.idle": "2023-08-25T18:25:55.909636Z", - "shell.execute_reply": "2023-08-25T18:25:55.909017Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.2 * (\n", - " 1.1 * XY\n", - " + 1.4300000000000002 * XZ\n", - ")\n" - ] - } - ], - "source": [ - "print(1.1 * ((1.2 * X)^(Y + (1.3 * Z))))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the following we take a broader and deeper look at Qiskit's operators, states, and the building blocks of quantum algorithms." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## Part I: State Functions and Measurements\n", - "\n", - "Quantum states are represented by subclasses of the class `StateFn`. There are four representations of quantum states: `DictStateFn` is a sparse representation in the computational basis, backed by a `dict`. `VectorStateFn` is a dense representation in the computational basis backed by a numpy array. `CircuitStateFn` is backed by a circuit and represents the state obtained by executing the circuit on the all-zero computational-basis state. `OperatorStateFn` represents mixed states via a density matrix. (As we will see later, `OperatorStateFn` is also used to represent observables.)\n", - "\n", - "Several `StateFn` instances are provided for convenience. For example `Zero, One, Plus, Minus`." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.914460Z", - "iopub.status.busy": "2023-08-25T18:25:55.913286Z", - "iopub.status.idle": "2023-08-25T18:25:55.918398Z", - "shell.execute_reply": "2023-08-25T18:25:55.917810Z" - } - }, - "outputs": [], - "source": [ - "from qiskit.opflow import (StateFn, Zero, One, Plus, Minus, H,\n", - " DictStateFn, VectorStateFn, CircuitStateFn, OperatorStateFn)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Zero` and `One` represent the quantum states $|0\\rangle$ and $|1\\rangle$. They are represented via `DictStateFn`." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.922902Z", - "iopub.status.busy": "2023-08-25T18:25:55.921567Z", - "iopub.status.idle": "2023-08-25T18:25:55.928619Z", - "shell.execute_reply": "2023-08-25T18:25:55.928008Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DictStateFn({'0': 1}) DictStateFn({'1': 1})\n" - ] - } - ], - "source": [ - "print(Zero, One)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Plus` and `Minus`, representing states $(|0\\rangle + |1\\rangle)/\\sqrt{2}$ and $(|0\\rangle - |1\\rangle)/\\sqrt{2}$ are represented via circuits. `H` is a synonym for `Plus`." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.933273Z", - "iopub.status.busy": "2023-08-25T18:25:55.932223Z", - "iopub.status.idle": "2023-08-25T18:25:55.989875Z", - "shell.execute_reply": "2023-08-25T18:25:55.988998Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CircuitStateFn(\n", - " ┌───┐\n", - "q: ┤ H ├\n", - " └───┘\n", - ") CircuitStateFn(\n", - " ┌───┐┌───┐\n", - "q: ┤ X ├┤ H ├\n", - " └───┘└───┘\n", - ")\n" - ] - } - ], - "source": [ - "print(Plus, Minus)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Indexing into quantum states is done with the `eval` method. These examples return the coefficients of the `0` and `1` basis states. (Below, we will see that the `eval` method is used for other computations, as well.)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:55.993947Z", - "iopub.status.busy": "2023-08-25T18:25:55.993499Z", - "iopub.status.idle": "2023-08-25T18:25:56.036114Z", - "shell.execute_reply": "2023-08-25T18:25:56.035247Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.0\n", - "0.0\n", - "1.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(0.7071067811865475+0j)\n", - "(-0.7071067811865475+8.7e-17j)\n" - ] - } - ], - "source": [ - "print(Zero.eval('0'))\n", - "print(Zero.eval('1'))\n", - "print(One.eval('1'))\n", - "print(Plus.eval('0'))\n", - "print(Minus.eval('1'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The dual vector of a quantum state, that is the *bra* corresponding to a *ket* is obtained via the `adjoint` method. The `StateFn` carries a flag `is_measurement`, which is `False` if the object is a ket and `True` if it is a bra." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here, we construct $\\langle 1 |$." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.040370Z", - "iopub.status.busy": "2023-08-25T18:25:56.039585Z", - "iopub.status.idle": "2023-08-25T18:25:56.044683Z", - "shell.execute_reply": "2023-08-25T18:25:56.044082Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "DictStateFn({'1': 1}, coeff=1.0, is_measurement=True)" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "One.adjoint()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For convenience, one may obtain the dual vector with a tilde, like this" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.047797Z", - "iopub.status.busy": "2023-08-25T18:25:56.047303Z", - "iopub.status.idle": "2023-08-25T18:25:56.052085Z", - "shell.execute_reply": "2023-08-25T18:25:56.051460Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "DictStateFn({'1': 1}, coeff=1.0, is_measurement=True)" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "~One" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Algebraic operations and predicates\n", - "\n", - "Many algebraic operations and predicates between `StateFn`s are supported, including:\n", - "\n", - "* `+` - addition\n", - "* `-` - subtraction, negation (scalar multiplication by -1)\n", - "* `*` - scalar multiplication\n", - "* `/` - scalar division\n", - "* `@` - composition\n", - "* `^` - tensor product or tensor power (tensor with self n times)\n", - "* `**` - composition power (compose with self n times)\n", - "* `==` - equality\n", - "* `~` - adjoint, alternating between a State Function and Measurement\n", - "\n", - "Be very aware that these operators obey the [Python rules for operator precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence), which might not be what you expect mathematically. For example, `I^X + X^I` will actually be parsed as `I ^ (X + X) ^ I == 2 * (I^X^I)` because Python evaluates `+` before `^`. In these cases, you can use the methods (`.tensor()`, etc) or parentheses." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`StateFn`s carry a coefficient. This allows us to multiply states by a scalar, and so to construct sums." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here, we construct $(2 + 3i)|0\\rangle$." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.057090Z", - "iopub.status.busy": "2023-08-25T18:25:56.056276Z", - "iopub.status.idle": "2023-08-25T18:25:56.062034Z", - "shell.execute_reply": "2023-08-25T18:25:56.061369Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "DictStateFn({'0': 1}, coeff=(2+3j), is_measurement=False)" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(2.0 + 3.0j) * Zero" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here, we see that adding two `DictStateFn`s returns an object of the same type. We construct $|0\\rangle + |1\\rangle$." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.066444Z", - "iopub.status.busy": "2023-08-25T18:25:56.065959Z", - "iopub.status.idle": "2023-08-25T18:25:56.069846Z", - "shell.execute_reply": "2023-08-25T18:25:56.069265Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DictStateFn({'0': 1.0, '1': 1.0})\n" - ] - } - ], - "source": [ - "print(Zero + One)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that you must normalize states by hand. For example, to construct $(|0\\rangle + |1\\rangle)/\\sqrt{2}$, we write" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.074307Z", - "iopub.status.busy": "2023-08-25T18:25:56.072919Z", - "iopub.status.idle": "2023-08-25T18:25:56.079817Z", - "shell.execute_reply": "2023-08-25T18:25:56.079220Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DictStateFn({'0': 1.0, '1': 1.0}) * 0.7071067811865475\n" - ] - } - ], - "source": [ - "import math\n", - "\n", - "v_zero_one = (Zero + One) / math.sqrt(2)\n", - "print(v_zero_one)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In other cases, the result is a symbolic representation of a sum. For example, here is a representation of $|+\\rangle + |-\\rangle$." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.083384Z", - "iopub.status.busy": "2023-08-25T18:25:56.082941Z", - "iopub.status.idle": "2023-08-25T18:25:56.087976Z", - "shell.execute_reply": "2023-08-25T18:25:56.087388Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "SummedOp([\n", - " CircuitStateFn(\n", - " ┌───┐\n", - " q: ┤ H ├\n", - " └───┘\n", - " ),\n", - " CircuitStateFn(\n", - " ┌───┐┌───┐\n", - " q: ┤ X ├┤ H ├\n", - " └───┘└───┘\n", - " )\n", - "])\n" - ] - } - ], - "source": [ - "print(Plus + Minus)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The composition operator is used to perform an inner product, which by default is held in an unevaluated form. Here is a representation of $\\langle 1 | 1 \\rangle$." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.094699Z", - "iopub.status.busy": "2023-08-25T18:25:56.094237Z", - "iopub.status.idle": "2023-08-25T18:25:56.100560Z", - "shell.execute_reply": "2023-08-25T18:25:56.099970Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ComposedOp([\n", - " DictMeasurement({'1': 1}),\n", - " DictStateFn({'1': 1})\n", - "])\n" - ] - } - ], - "source": [ - "print(~One @ One)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that the `is_measurement` flag causes the (bra) state `~One` to be printed `DictMeasurement`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Symbolic expressions may be evaluated with the `eval` method." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.103964Z", - "iopub.status.busy": "2023-08-25T18:25:56.103504Z", - "iopub.status.idle": "2023-08-25T18:25:56.111143Z", - "shell.execute_reply": "2023-08-25T18:25:56.110543Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "1.0" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(~One @ One).eval()" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.114578Z", - "iopub.status.busy": "2023-08-25T18:25:56.114126Z", - "iopub.status.idle": "2023-08-25T18:25:56.121360Z", - "shell.execute_reply": "2023-08-25T18:25:56.120728Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "0.9999999999999998" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(~v_zero_one @ v_zero_one).eval()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is $\\langle - | 1 \\rangle = \\langle (\\langle 0| - \\langle 1|)/\\sqrt{2} | 1\\rangle$." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.124828Z", - "iopub.status.busy": "2023-08-25T18:25:56.124349Z", - "iopub.status.idle": "2023-08-25T18:25:56.136495Z", - "shell.execute_reply": "2023-08-25T18:25:56.135880Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(-0.7071067811865475-8.7e-17j)" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(~Minus @ One).eval()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The composition operator `@` is equivalent to calling the `compose` method." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.141205Z", - "iopub.status.busy": "2023-08-25T18:25:56.139980Z", - "iopub.status.idle": "2023-08-25T18:25:56.146565Z", - "shell.execute_reply": "2023-08-25T18:25:56.145974Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ComposedOp([\n", - " DictMeasurement({'1': 1}),\n", - " DictStateFn({'1': 1})\n", - "])\n" - ] - } - ], - "source": [ - "print((~One).compose(One))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Inner products may also be computed using the `eval` method directly, without constructing a `ComposedOp`." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.151201Z", - "iopub.status.busy": "2023-08-25T18:25:56.150056Z", - "iopub.status.idle": "2023-08-25T18:25:56.157474Z", - "shell.execute_reply": "2023-08-25T18:25:56.156893Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "1.0" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(~One).eval(One)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Symbolic tensor products are constructed as follows. Here is $|0\\rangle \\otimes |+\\rangle$." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.162108Z", - "iopub.status.busy": "2023-08-25T18:25:56.160970Z", - "iopub.status.idle": "2023-08-25T18:25:56.167828Z", - "shell.execute_reply": "2023-08-25T18:25:56.167237Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TensoredOp([\n", - " DictStateFn({'0': 1}),\n", - " CircuitStateFn(\n", - " ┌───┐\n", - " q: ┤ H ├\n", - " └───┘\n", - " )\n", - "])\n" - ] - } - ], - "source": [ - "print(Zero^Plus)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This may be represented as a simple (not compound) `CircuitStateFn`." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.171497Z", - "iopub.status.busy": "2023-08-25T18:25:56.171041Z", - "iopub.status.idle": "2023-08-25T18:25:56.183381Z", - "shell.execute_reply": "2023-08-25T18:25:56.182766Z" - }, - "scrolled": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CircuitStateFn(\n", - " ┌───┐\n", - "q_0: ┤ H ├\n", - " └───┘\n", - "q_1: ─────\n", - " \n", - ")\n" - ] - } - ], - "source": [ - "print((Zero^Plus).to_circuit_op())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Tensor powers are constructed using the caret `^` as follows. Here are $600 (|11111\\rangle + |00000\\rangle)$, and $|10\\rangle^{\\otimes 3}$." - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.189118Z", - "iopub.status.busy": "2023-08-25T18:25:56.187406Z", - "iopub.status.idle": "2023-08-25T18:25:56.197144Z", - "shell.execute_reply": "2023-08-25T18:25:56.196543Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DictStateFn({'11111': 1.0, '00000': 1.0}) * 600.0\n", - "DictStateFn({'101010': 1})\n" - ] - } - ], - "source": [ - "print(600 * ((One^5) + (Zero^5)))\n", - "print((One^Zero)^3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The method `to_matrix_op` converts to `VectorStateFn`." - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.202791Z", - "iopub.status.busy": "2023-08-25T18:25:56.201106Z", - "iopub.status.idle": "2023-08-25T18:25:56.271843Z", - "shell.execute_reply": "2023-08-25T18:25:56.271157Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "VectorStateFn(Statevector([ 0.25-6.1e-17j, -0.25+6.1e-17j, 0.25-6.1e-17j,\n", - " -0.25+6.1e-17j, -0.25+6.1e-17j, 0.25-6.1e-17j,\n", - " -0.25+6.1e-17j, 0.25-6.1e-17j, 0.25-6.1e-17j,\n", - " -0.25+6.1e-17j, 0.25-6.1e-17j, -0.25+6.1e-17j,\n", - " -0.25+6.1e-17j, 0.25-6.1e-17j, -0.25+6.1e-17j,\n", - " 0.25-6.1e-17j],\n", - " dims=(2, 2, 2, 2)))" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "CircuitStateFn(\n", - " ┌───┐\n", - "q_0: ┤ X ├\n", - " ├───┤\n", - "q_1: ┤ H ├\n", - " ├───┤\n", - "q_2: ┤ X ├\n", - " ├───┤\n", - "q_3: ┤ H ├\n", - " └───┘\n", - ")\n", - "{'1111': 0.2724609375, '1101': 0.2626953125, '0101': 0.2490234375, '0111': 0.2158203125}\n" - ] - } - ], - "source": [ - "print(((Plus^Minus)^2).to_matrix_op())\n", - "print(((Plus^One)^2).to_circuit_op())\n", - "print(((Plus^One)^2).to_matrix_op().sample())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Constructing a StateFn is easy. The `StateFn` class also serves as a factory, and can take any applicable primitive in its constructor and return the correct StateFn subclass. Currently the following primitives can be passed into the constructor, listed alongside the `StateFn` subclass they produce:\n", - "\n", - "* str (equal to some basis bitstring) -> DictStateFn\n", - "* dict -> DictStateFn\n", - "* Qiskit Result object -> DictStateFn\n", - "* list -> VectorStateFn\n", - "* np.ndarray -> VectorStateFn\n", - "* Statevector -> VectorStateFn\n", - "* QuantumCircuit -> CircuitStateFn\n", - "* Instruction -> CircuitStateFn\n", - "* OperatorBase -> OperatorStateFn" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.276809Z", - "iopub.status.busy": "2023-08-25T18:25:56.275581Z", - "iopub.status.idle": "2023-08-25T18:25:56.629683Z", - "shell.execute_reply": "2023-08-25T18:25:56.628999Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DictStateFn({'0': 1})\n", - "True\n", - "VectorStateFn(Statevector([0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j],\n", - " dims=(2, 2)))\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/1137983803.py:1: DeprecationWarning: The class ``qiskit.opflow.state_fns.dict_state_fn.DictStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print(StateFn({'0':1}))\n", - "/tmp/ipykernel_10214/1137983803.py:2: DeprecationWarning: The class ``qiskit.opflow.state_fns.dict_state_fn.DictStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print(StateFn({'0':1}) == Zero)\n", - "/tmp/ipykernel_10214/1137983803.py:4: DeprecationWarning: The class ``qiskit.opflow.state_fns.vector_state_fn.VectorStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print(StateFn([0,1,1,0]))\n", - "/tmp/ipykernel_10214/1137983803.py:7: DeprecationWarning: The class ``qiskit.opflow.state_fns.circuit_state_fn.CircuitStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print(StateFn(RealAmplitudes(2)))\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CircuitStateFn(\n", - " ┌──────────────────────────────────────────────────────────┐\n", - "q_0: ┤0 ├\n", - " │ RealAmplitudes(θ[0],θ[1],θ[2],θ[3],θ[4],θ[5],θ[6],θ[7]) │\n", - "q_1: ┤1 ├\n", - " └──────────────────────────────────────────────────────────┘\n", - ")\n" - ] - } - ], - "source": [ - "print(StateFn({'0':1}))\n", - "print(StateFn({'0':1}) == Zero)\n", - "\n", - "print(StateFn([0,1,1,0]))\n", - "\n", - "from qiskit.circuit.library import RealAmplitudes\n", - "print(StateFn(RealAmplitudes(2)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part II: `PrimitiveOp`s\n", - "\n", - "The basic Operators are subclasses of `PrimitiveOp`. Just like `StateFn`, `PrimitiveOp` is also a factory for creating the correct type of `PrimitiveOp` for a given primitive. Currently, the following primitives can be passed into the constructor, listed alongside the `PrimitiveOp` subclass they produce:\n", - "\n", - "* Terra's Pauli -> PauliOp\n", - "* Instruction -> CircuitOp\n", - "* QuantumCircuit -> CircuitOp\n", - "* 2d List -> MatrixOp\n", - "* np.ndarray -> MatrixOp\n", - "* spmatrix -> MatrixOp\n", - "* Terra's quantum_info.Operator -> MatrixOp" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.634723Z", - "iopub.status.busy": "2023-08-25T18:25:56.633405Z", - "iopub.status.idle": "2023-08-25T18:25:56.638555Z", - "shell.execute_reply": "2023-08-25T18:25:56.637972Z" - } - }, - "outputs": [], - "source": [ - "from qiskit.opflow import X, Y, Z, I, CX, T, H, S, PrimitiveOp" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Matrix elements" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `eval` method returns a column from an operator. For example, the Pauli $X$ operator is represented by a `PauliOp`. Asking for a column returns an instance of the sparse representation, a `DictStateFn`." - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.643193Z", - "iopub.status.busy": "2023-08-25T18:25:56.642046Z", - "iopub.status.idle": "2023-08-25T18:25:56.649256Z", - "shell.execute_reply": "2023-08-25T18:25:56.648680Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "PauliOp(Pauli('X'), coeff=1.0)" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "X" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.653652Z", - "iopub.status.busy": "2023-08-25T18:25:56.652514Z", - "iopub.status.idle": "2023-08-25T18:25:56.659131Z", - "shell.execute_reply": "2023-08-25T18:25:56.658557Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DictStateFn({'1': (1+0j)})\n" - ] - } - ], - "source": [ - "print(X.eval('0'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It follows that indexing into an operator, that is obtaining a matrix element, is performed with two calls to the `eval` method." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We have $X = \\left(\\begin{matrix} 0 & 1 \\\\\n", - " 1 & 0\n", - " \\end{matrix} \\right)$. And the matrix element $\\left\\{X \\right\\}_{0,1}$ is" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.663573Z", - "iopub.status.busy": "2023-08-25T18:25:56.662447Z", - "iopub.status.idle": "2023-08-25T18:25:56.669990Z", - "shell.execute_reply": "2023-08-25T18:25:56.669417Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(1+0j)" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "X.eval('0').eval('1')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is an example using the two qubit operator `CX`, the controlled `X`, which is represented by a circuit." - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.674396Z", - "iopub.status.busy": "2023-08-25T18:25:56.673268Z", - "iopub.status.idle": "2023-08-25T18:25:56.681289Z", - "shell.execute_reply": "2023-08-25T18:25:56.680710Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n", - "q_0: ──■──\n", - " ┌─┴─┐\n", - "q_1: ┤ X ├\n", - " └───┘\n", - "[[1. 0. 0. 0.]\n", - " [0. 0. 0. 1.]\n", - " [0. 0. 1. 0.]\n", - " [0. 1. 0. 0.]]\n" - ] - } - ], - "source": [ - "print(CX)\n", - "print(CX.to_matrix().real) # The imaginary part vanishes." - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.685652Z", - "iopub.status.busy": "2023-08-25T18:25:56.684482Z", - "iopub.status.idle": "2023-08-25T18:25:56.692980Z", - "shell.execute_reply": "2023-08-25T18:25:56.692401Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "VectorStateFn(Statevector([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],\n", - " dims=(2, 2)), coeff=1.0, is_measurement=False)" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "CX.eval('01') # 01 is the one in decimal. We get the first column." - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.697419Z", - "iopub.status.busy": "2023-08-25T18:25:56.696288Z", - "iopub.status.idle": "2023-08-25T18:25:56.704529Z", - "shell.execute_reply": "2023-08-25T18:25:56.703961Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(1+0j)" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "CX.eval('01').eval('11') # This returns element with (zero-based) index (1, 3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Applying an operator to a state vector" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Applying an operator to a state vector may be done with the `compose` method (equivalently, `@` operator). Here is a representation of $X | 1 \\rangle = |0\\rangle$." - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.708965Z", - "iopub.status.busy": "2023-08-25T18:25:56.707826Z", - "iopub.status.idle": "2023-08-25T18:25:56.713940Z", - "shell.execute_reply": "2023-08-25T18:25:56.713382Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ComposedOp([\n", - " X,\n", - " DictStateFn({'1': 1})\n", - "])\n" - ] - } - ], - "source": [ - "print(X @ One)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A simpler representation, the `DictStateFn` representation of $|0\\rangle$, is obtained with `eval`." - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.718292Z", - "iopub.status.busy": "2023-08-25T18:25:56.717168Z", - "iopub.status.idle": "2023-08-25T18:25:56.724395Z", - "shell.execute_reply": "2023-08-25T18:25:56.723815Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "DictStateFn({'0': (1+0j)}, coeff=(1+0j), is_measurement=False)" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "(X @ One).eval()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The intermediate `ComposedOp` step may be avoided by using `eval` directly." - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.728722Z", - "iopub.status.busy": "2023-08-25T18:25:56.727581Z", - "iopub.status.idle": "2023-08-25T18:25:56.734661Z", - "shell.execute_reply": "2023-08-25T18:25:56.734099Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "DictStateFn({'0': (1+0j)}, coeff=(1+0j), is_measurement=False)" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "X.eval(One)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Composition and tensor products of operators are effected with `@` and `^`. Here are some examples." - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.738961Z", - "iopub.status.busy": "2023-08-25T18:25:56.737846Z", - "iopub.status.idle": "2023-08-25T18:25:56.802032Z", - "shell.execute_reply": "2023-08-25T18:25:56.801300Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(1+0j)\n", - " ┌───┐ ┌───┐ \n", - "q_0: ──■──┤ H ├───────■──┤ H ├─────\n", - " ┌─┴─┐└───┘┌───┐┌─┴─┐└───┘┌───┐\n", - "q_1: ┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├\n", - " └───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤\n", - "q_2: ──■──┤ X ├┤ H ├──■──┤ X ├┤ H ├\n", - " ┌─┴─┐└───┘├───┤┌─┴─┐└───┘├───┤\n", - "q_3: ┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├\n", - " └───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤\n", - "q_4: ─────┤ X ├┤ H ├─────┤ X ├┤ H ├\n", - " └───┘└───┘ └───┘└───┘\n", - "CircuitStateFn(\n", - " ┌───┐┌───┐ ┌───┐ ┌───┐ \n", - "q_0: ┤ X ├┤ H ├──■──┤ H ├───────■──┤ H ├─────\n", - " ├───┤├───┤┌─┴─┐└───┘┌───┐┌─┴─┐└───┘┌───┐\n", - "q_1: ┤ X ├┤ H ├┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├\n", - " ├───┤├───┤└───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤\n", - "q_2: ┤ X ├┤ H ├──■──┤ X ├┤ H ├──■──┤ X ├┤ H ├\n", - " ├───┤├───┤┌─┴─┐└───┘├───┤┌─┴─┐└───┘├───┤\n", - "q_3: ┤ X ├┤ H ├┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├\n", - " ├───┤├───┤└───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤\n", - "q_4: ┤ X ├┤ H ├─────┤ X ├┤ H ├─────┤ X ├┤ H ├\n", - " └───┘└───┘ └───┘└───┘ └───┘└───┘\n", - ")\n", - "CircuitStateFn(\n", - " ┌─────────────┐ \n", - "q_0: ┤0 ├─────\n", - " │ │ \n", - "q_1: ┤1 Pauli(XII) ├─────\n", - " │ │┌───┐\n", - "q_2: ┤2 ├┤ H ├\n", - " └─────────────┘└───┘\n", - ")\n" - ] - } - ], - "source": [ - "print(((~One^2) @ (CX.eval('01'))).eval())\n", - "\n", - "print(((H^5) @ ((CX^2)^I) @ (I^(CX^2)))**2)\n", - "print((((H^5) @ ((CX^2)^I) @ (I^(CX^2)))**2) @ (Minus^5))\n", - "print(((H^I^I)@(X^I^I)@Zero))" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.807042Z", - "iopub.status.busy": "2023-08-25T18:25:56.805863Z", - "iopub.status.idle": "2023-08-25T18:25:56.812886Z", - "shell.execute_reply": "2023-08-25T18:25:56.812321Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ComposedOp([\n", - " DictMeasurement({'1': 1}),\n", - " CircuitStateFn(\n", - " ┌───┐┌───┐\n", - " q: ┤ X ├┤ H ├\n", - " └───┘└───┘\n", - " )\n", - "])\n" - ] - } - ], - "source": [ - "print(~One @ Minus)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part III: `ListOp` and subclasses" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### `ListOp`\n", - "\n", - "`ListOp` is a container for effectively vectorizing operations over a list of operators and states." - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.817440Z", - "iopub.status.busy": "2023-08-25T18:25:56.816254Z", - "iopub.status.idle": "2023-08-25T18:25:56.824392Z", - "shell.execute_reply": "2023-08-25T18:25:56.823779Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ComposedOp([\n", - " ListOp([\n", - " DictMeasurement({'1': 1}),\n", - " DictMeasurement({'0': 1})\n", - " ]),\n", - " ListOp([\n", - " DictStateFn({'1': 1}),\n", - " DictStateFn({'0': 1})\n", - " ])\n", - "])\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/1512571236.py:3: DeprecationWarning: The class ``qiskit.opflow.list_ops.list_op.ListOp`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print((~ListOp([One, Zero]) @ ListOp([One, Zero])))\n" - ] - } - ], - "source": [ - "from qiskit.opflow import ListOp\n", - "\n", - "print((~ListOp([One, Zero]) @ ListOp([One, Zero])))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For example, the composition above is distributed over the lists (`ListOp`) using the simplification method `reduce`." - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.828945Z", - "iopub.status.busy": "2023-08-25T18:25:56.827797Z", - "iopub.status.idle": "2023-08-25T18:25:56.836469Z", - "shell.execute_reply": "2023-08-25T18:25:56.835868Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ListOp([\n", - " ListOp([\n", - " ComposedOp([\n", - " DictMeasurement({'1': 1}),\n", - " DictStateFn({'1': 1})\n", - " ]),\n", - " ComposedOp([\n", - " DictMeasurement({'1': 1}),\n", - " DictStateFn({'0': 1})\n", - " ])\n", - " ]),\n", - " ListOp([\n", - " ComposedOp([\n", - " DictMeasurement({'0': 1}),\n", - " DictStateFn({'1': 1})\n", - " ]),\n", - " ComposedOp([\n", - " DictMeasurement({'0': 1}),\n", - " DictStateFn({'0': 1})\n", - " ])\n", - " ])\n", - "])\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/2216466513.py:1: DeprecationWarning: The class ``qiskit.opflow.list_ops.list_op.ListOp`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print((~ListOp([One, Zero]) @ ListOp([One, Zero])).reduce())\n" - ] - } - ], - "source": [ - "print((~ListOp([One, Zero]) @ ListOp([One, Zero])).reduce())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### `ListOp`s: `SummedOp`, `ComposedOp`, `TensoredOp`\n", - "\n", - "`ListOp`, introduced above, is useful for vectorizing operations. But, it also serves as the superclass for list-like composite classes.\n", - "If you've already played around with the above, you'll notice that you can easily perform operations between `OperatorBase`s which we may not know how to perform efficiently in general (or simply haven't implemented an efficient procedure for yet), such as addition between `CircuitOp`s. In those cases, you may receive a `ListOp` result (or subclass thereof) from your operation representing the lazy execution of the operation. For example, if you attempt to add together a `DictStateFn` and a `CircuitStateFn`, you'll receive a `SummedOp` representing the sum of the two. This composite State function still has a working `eval` (but may need to perform a non-scalable computation under the hood, such as converting both to vectors).\n", - "\n", - "These composite `OperatorBase`s are how we construct increasingly complex and rich computation out of `PrimitiveOp` and `StateFn` building blocks.\n", - "\n", - "Every `ListOp` has four properties:\n", - "\n", - "* `oplist` - The list of `OperatorBase`s which may represent terms, factors, etc.\n", - "* `combo_fn` - The function taking a list of complex numbers to an output value which defines how to combine the outputs of the `oplist` items. For broadcasting simplicity, this function is defined over NumPy arrays.\n", - "* `coeff` - A coefficient multiplying the primitive. Note that `coeff` can be int, float, complex or a free `Parameter` object (from `qiskit.circuit` in Terra) to be bound later using `my_op.bind_parameters`.\n", - "* `abelian` - Indicates whether the Operators in `oplist` are known to mutually commute (usually set after being converted by the `AbelianGrouper` converter).\n", - "\n", - "Note that `ListOp` supports typical sequence overloads, so you can use indexing like `my_op[4]` to access the `OperatorBase`s in `oplist`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### `OperatorStateFn`\n", - "\n", - "We mentioned above that `OperatorStateFn` represents a density operator. But, if the `is_measurement` flag is `True`, then `OperatorStateFn` represents an observable. The expectation value of this observable can then be constructed via `ComposedOp`. Or, directly, using `eval`. Recall that the `is_measurement` flag (property) is set via the `adjoint` method." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here we construct the observable corresponding to the Pauli $Z$ operator. Note that when printing, it is called `OperatorMeasurement`." - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.841037Z", - "iopub.status.busy": "2023-08-25T18:25:56.839884Z", - "iopub.status.idle": "2023-08-25T18:25:56.851255Z", - "shell.execute_reply": "2023-08-25T18:25:56.850682Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OperatorMeasurement(Z)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/2925661353.py:1: DeprecationWarning: The class ``qiskit.opflow.state_fns.operator_state_fn.OperatorStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print(StateFn(Z).adjoint())\n", - "/tmp/ipykernel_10214/2925661353.py:2: DeprecationWarning: The class ``qiskit.opflow.state_fns.operator_state_fn.OperatorStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " StateFn(Z).adjoint()\n" - ] - }, - { - "data": { - "text/plain": [ - "OperatorStateFn(PauliOp(Pauli('Z'), coeff=1.0), coeff=1.0, is_measurement=True)" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "print(StateFn(Z).adjoint())\n", - "StateFn(Z).adjoint()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here, we compute $\\langle 0 | Z | 0 \\rangle$, $\\langle 1 | Z | 1 \\rangle$, and $\\langle + | Z | + \\rangle$, where $|+\\rangle = (|0\\rangle + |1\\rangle)/\\sqrt{2}$." - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.854777Z", - "iopub.status.busy": "2023-08-25T18:25:56.854341Z", - "iopub.status.idle": "2023-08-25T18:25:56.869160Z", - "shell.execute_reply": "2023-08-25T18:25:56.868532Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(1+0j)\n", - "(-1+0j)\n", - "(-2.2e-17+0j)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/2804323468.py:1: DeprecationWarning: The class ``qiskit.opflow.state_fns.operator_state_fn.OperatorStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print(StateFn(Z).adjoint().eval(Zero))\n", - "/tmp/ipykernel_10214/2804323468.py:2: DeprecationWarning: The class ``qiskit.opflow.state_fns.operator_state_fn.OperatorStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print(StateFn(Z).adjoint().eval(One))\n", - "/tmp/ipykernel_10214/2804323468.py:3: DeprecationWarning: The class ``qiskit.opflow.state_fns.operator_state_fn.OperatorStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print(StateFn(Z).adjoint().eval(Plus))\n" - ] - } - ], - "source": [ - "print(StateFn(Z).adjoint().eval(Zero))\n", - "print(StateFn(Z).adjoint().eval(One))\n", - "print(StateFn(Z).adjoint().eval(Plus))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part IV: Converters\n", - "\n", - "Converters are classes that manipulate operators and states and perform building blocks of algorithms. Examples include changing the basis of operators and Trotterization.\n", - "Converters traverse an expression and perform a particular manipulation or replacement, defined by the converter's `convert()` method, of the Operators within. Typically, if a converter encounters an `OperatorBase` in the recursion which is irrelevant to its conversion purpose, that `OperatorBase` is left unchanged." - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.873880Z", - "iopub.status.busy": "2023-08-25T18:25:56.872707Z", - "iopub.status.idle": "2023-08-25T18:25:56.878114Z", - "shell.execute_reply": "2023-08-25T18:25:56.877442Z" - } - }, - "outputs": [], - "source": [ - "import numpy as np\n", - "from qiskit.opflow import I, X, Y, Z, H, CX, Zero, ListOp, PauliExpectation, PauliTrotterEvolution, CircuitSampler, MatrixEvolution, Suzuki\n", - "from qiskit.circuit import Parameter\n", - "from qiskit import Aer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Evolutions, `exp_i()`, and the `EvolvedOp`\n", - "\n", - "Every `PrimitiveOp` and `ListOp` has an `.exp_i()` function such that `H.exp_i()` corresponds to $e^{-iH}$. In practice, only a few of these Operators have an efficiently computable exponentiation (such as MatrixOp and the PauliOps with only one non-identity single-qubit Pauli), so we need to return a placeholder, or symbolic representation, (similar to how `SummedOp` is a placeholder when we can't perform addition). This placeholder is called `EvolvedOp`, and it holds the `OperatorBase` to be exponentiated in its `.primitive` property.\n", - "\n", - "Qiskit operators fully support parameterization, so we can use a `Parameter` for our evolution time here. Notice that there's no \"evolution time\" argument in any function. The Operator flow exponentiates whatever operator we tell it to, and if we choose to multiply the operator by an evolution time, $e^{-iHt}$, that will be reflected in our exponentiation parameters." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Weighted sum of Pauli operators\n", - "A Hamiltonian expressed as a linear combination of multi-qubit Pauli operators may be constructed like this." - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.882746Z", - "iopub.status.busy": "2023-08-25T18:25:56.881594Z", - "iopub.status.idle": "2023-08-25T18:25:56.888390Z", - "shell.execute_reply": "2023-08-25T18:25:56.887704Z" - } - }, - "outputs": [], - "source": [ - "two_qubit_H2 = (-1.0523732 * I^I) + \\\n", - " (0.39793742 * I^Z) + \\\n", - " (-0.3979374 * Z^I) + \\\n", - " (-0.0112801 * Z^Z) + \\\n", - " (0.18093119 * X^X)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that `two_qubit_H2` is represented as a `SummedOp` whose terms are `PauliOp`s." - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.893005Z", - "iopub.status.busy": "2023-08-25T18:25:56.891830Z", - "iopub.status.idle": "2023-08-25T18:25:56.898482Z", - "shell.execute_reply": "2023-08-25T18:25:56.897846Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-1.0523732 * II\n", - "+ 0.39793742 * IZ\n", - "- 0.3979374 * ZI\n", - "- 0.0112801 * ZZ\n", - "+ 0.18093119 * XX\n" - ] - } - ], - "source": [ - "print(two_qubit_H2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we multiply the Hamiltonian by a `Parameter`. This `Parameter` is stored in the `coeff` property of the `SummedOp`. Calling `exp_i()` on the result wraps it in `EvolvedOp`, representing exponentiation." - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.903041Z", - "iopub.status.busy": "2023-08-25T18:25:56.901906Z", - "iopub.status.idle": "2023-08-25T18:25:56.910883Z", - "shell.execute_reply": "2023-08-25T18:25:56.910278Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "e^(-i*1.0*θ * (\n", - " -1.0523732 * II\n", - " + 0.39793742 * IZ\n", - " - 0.3979374 * ZI\n", - " - 0.0112801 * ZZ\n", - " + 0.18093119 * XX\n", - "))\n", - "EvolvedOp(PauliSumOp(SparsePauliOp(['II', 'IZ', 'ZI', 'ZZ', 'XX'],\n", - " coeffs=[-1.0523732 +0.j, 0.39793742+0.j, -0.3979374 +0.j, -0.0112801 +0.j,\n", - " 0.18093119+0.j]), coeff=1.0*θ), coeff=1.0)\n" - ] - } - ], - "source": [ - "evo_time = Parameter('θ')\n", - "evolution_op = (evo_time*two_qubit_H2).exp_i()\n", - "print(evolution_op) # Note, EvolvedOps print as exponentiations\n", - "print(repr(evolution_op))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We construct `h2_measurement`, which represents `two_qubit_H2` as an observable." - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.915515Z", - "iopub.status.busy": "2023-08-25T18:25:56.914348Z", - "iopub.status.idle": "2023-08-25T18:25:56.923483Z", - "shell.execute_reply": "2023-08-25T18:25:56.922884Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OperatorMeasurement(-1.0523732 * II\n", - "+ 0.39793742 * IZ\n", - "- 0.3979374 * ZI\n", - "- 0.0112801 * ZZ\n", - "+ 0.18093119 * XX)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/251610506.py:1: DeprecationWarning: The class ``qiskit.opflow.state_fns.operator_state_fn.OperatorStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " h2_measurement = StateFn(two_qubit_H2).adjoint()\n" - ] - } - ], - "source": [ - "h2_measurement = StateFn(two_qubit_H2).adjoint()\n", - "print(h2_measurement)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We construct a Bell state $|\\Phi_+\\rangle$ via $\\text{CX} (H\\otimes I) |00\\rangle$." - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.928037Z", - "iopub.status.busy": "2023-08-25T18:25:56.926867Z", - "iopub.status.idle": "2023-08-25T18:25:56.940800Z", - "shell.execute_reply": "2023-08-25T18:25:56.940163Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CircuitStateFn(\n", - " ┌───┐ \n", - "q_0: ┤ H ├──■──\n", - " └───┘┌─┴─┐\n", - "q_1: ─────┤ X ├\n", - " └───┘\n", - ")\n" - ] - } - ], - "source": [ - "bell = CX @ (I ^ H) @ Zero\n", - "print(bell)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is the expression $H e^{-iHt} |\\Phi_+\\rangle$." - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.946668Z", - "iopub.status.busy": "2023-08-25T18:25:56.944947Z", - "iopub.status.idle": "2023-08-25T18:25:56.957552Z", - "shell.execute_reply": "2023-08-25T18:25:56.956933Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ComposedOp([\n", - " OperatorMeasurement(-1.0523732 * II\n", - " + 0.39793742 * IZ\n", - " - 0.3979374 * ZI\n", - " - 0.0112801 * ZZ\n", - " + 0.18093119 * XX),\n", - " e^(-i*1.0*θ * (\n", - " -1.0523732 * II\n", - " + 0.39793742 * IZ\n", - " - 0.3979374 * ZI\n", - " - 0.0112801 * ZZ\n", - " + 0.18093119 * XX\n", - " )),\n", - " CircuitStateFn(\n", - " ┌───┐ \n", - " q_0: ┤ H ├──■──\n", - " └───┘┌─┴─┐\n", - " q_1: ─────┤ X ├\n", - " └───┘\n", - " )\n", - "])\n" - ] - } - ], - "source": [ - "evo_and_meas = h2_measurement @ evolution_op @ bell\n", - "print(evo_and_meas)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Typically, we want to approximate $e^{-iHt}$ using two-qubit gates. We achieve this with the `convert` method of `PauliTrotterEvolution`, which traverses expressions applying trotterization to all `EvolvedOp`s encountered. Although we use `PauliTrotterEvolution` here, there are other possibilities, such as `MatrixEvolution`, which performs the exponentiation exactly." - ] - }, - { - "cell_type": "code", - "execution_count": 55, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.963254Z", - "iopub.status.busy": "2023-08-25T18:25:56.961595Z", - "iopub.status.idle": "2023-08-25T18:25:56.979035Z", - "shell.execute_reply": "2023-08-25T18:25:56.978364Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ComposedOp([\n", - " OperatorMeasurement(-1.0523732 * II\n", - " + 0.39793742 * IZ\n", - " - 0.3979374 * ZI\n", - " - 0.0112801 * ZZ\n", - " + 0.18093119 * XX),\n", - " CircuitStateFn(\n", - " ┌───┐ ┌───────────────────────────────────────────┐\n", - " q_0: ┤ H ├──■──┤0 ├\n", - " └───┘┌─┴─┐│ exp(-it (II + IZ + ZI + ZZ + XX))(1.0*θ) │\n", - " q_1: ─────┤ X ├┤1 ├\n", - " └───┘└───────────────────────────────────────────┘\n", - " )\n", - "])\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/3156331339.py:1: DeprecationWarning: The class ``qiskit.opflow.evolutions.trotterizations.suzuki.Suzuki`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " trotterized_op = PauliTrotterEvolution(trotter_mode=Suzuki(order=2, reps=1)).convert(evo_and_meas)\n", - "/tmp/ipykernel_10214/3156331339.py:1: DeprecationWarning: The class ``qiskit.opflow.evolutions.pauli_trotter_evolution.PauliTrotterEvolution`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " trotterized_op = PauliTrotterEvolution(trotter_mode=Suzuki(order=2, reps=1)).convert(evo_and_meas)\n" - ] - } - ], - "source": [ - "trotterized_op = PauliTrotterEvolution(trotter_mode=Suzuki(order=2, reps=1)).convert(evo_and_meas)\n", - "# We can also set trotter_mode='suzuki' or leave it empty to default to first order Trotterization.\n", - "print(trotterized_op)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`trotterized_op` contains a `Parameter`. The `bind_parameters` method traverses the expression binding values to parameter names as specified via a `dict`. In this case, there is only one parameter." - ] - }, - { - "cell_type": "code", - "execution_count": 56, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.983663Z", - "iopub.status.busy": "2023-08-25T18:25:56.982499Z", - "iopub.status.idle": "2023-08-25T18:25:56.987858Z", - "shell.execute_reply": "2023-08-25T18:25:56.987268Z" - } - }, - "outputs": [], - "source": [ - "bound = trotterized_op.bind_parameters({evo_time: .5})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`bound` is a `ComposedOp`. The second factor is the circuit. Let's draw it to verify that the binding has taken place." - ] - }, - { - "cell_type": "code", - "execution_count": 57, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:56.992373Z", - "iopub.status.busy": "2023-08-25T18:25:56.991221Z", - "iopub.status.idle": "2023-08-25T18:25:57.003384Z", - "shell.execute_reply": "2023-08-25T18:25:57.002808Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "
     ┌───┐     ┌─────────────────────────────────────────┐\n",
-       "q_0: ┤ H ├──■──┤0                                        ├\n",
-       "     └───┘┌─┴─┐│  exp(-it (II + IZ + ZI + ZZ + XX))(0.5) │\n",
-       "q_1: ─────┤ X ├┤1                                        ├\n",
-       "          └───┘└─────────────────────────────────────────┘
" - ], - "text/plain": [ - " ┌───┐ ┌─────────────────────────────────────────┐\n", - "q_0: ┤ H ├──■──┤0 ├\n", - " └───┘┌─┴─┐│ exp(-it (II + IZ + ZI + ZZ + XX))(0.5) │\n", - "q_1: ─────┤ X ├┤1 ├\n", - " └───┘└─────────────────────────────────────────┘" - ] - }, - "execution_count": 57, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bound[1].to_circuit().draw()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Expectations\n", - "\n", - "`Expectation`s are converters that enable the computation of expectation values of observables. They traverse an Operator tree, replacing `OperatorStateFn`s (observables) with equivalent instructions which are more amenable to\n", - "computation on quantum or classical hardware. For example, if we want to measure the expectation value of an Operator `o` expressed as a sum of Paulis with respect to some state function, but can only access diagonal measurements on quantum hardware, we can create an observable `~StateFn(o)` and use a ``PauliExpectation`` to convert it to a diagonal measurement and circuit pre-rotations to append to the state.\n", - "\n", - "Another interesting `Expectation` is the `AerPauliExpectation`, which converts the observable into a `CircuitStateFn` containing a special expectation snapshot instruction which `Aer` can execute natively with high performance." - ] - }, - { - "cell_type": "code", - "execution_count": 58, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:57.008810Z", - "iopub.status.busy": "2023-08-25T18:25:57.007113Z", - "iopub.status.idle": "2023-08-25T18:25:57.038750Z", - "shell.execute_reply": "2023-08-25T18:25:57.038119Z" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/119115492.py:2: DeprecationWarning: The class ``qiskit.opflow.expectations.pauli_expectation.PauliExpectation`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print(PauliExpectation(group_paulis=False).convert(h2_measurement))\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "SummedOp([\n", - " ComposedOp([\n", - " OperatorMeasurement(-1.0523732 * II),\n", - " II\n", - " ]),\n", - " ComposedOp([\n", - " OperatorMeasurement(0.39793742 * IZ),\n", - " II\n", - " ]),\n", - " ComposedOp([\n", - " OperatorMeasurement(-0.3979374 * ZI),\n", - " II\n", - " ]),\n", - " ComposedOp([\n", - " OperatorMeasurement(-0.0112801 * ZZ),\n", - " II\n", - " ]),\n", - " ComposedOp([\n", - " OperatorMeasurement(0.18093119 * ZZ),\n", - " ┌───┐\n", - " q_0: ┤ H ├\n", - " ├───┤\n", - " q_1: ┤ H ├\n", - " └───┘\n", - " ])\n", - "])\n" - ] - } - ], - "source": [ - "# Note that XX was the only non-diagonal measurement in our H2 Observable\n", - "print(PauliExpectation(group_paulis=False).convert(h2_measurement))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "By default `group_paulis=True`, which will use the `AbelianGrouper` to convert the `SummedOp` into groups of mutually qubit-wise commuting Paulis. This reduces circuit execution overhead, as each group can share the same circuit execution." - ] - }, - { - "cell_type": "code", - "execution_count": 59, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:57.043515Z", - "iopub.status.busy": "2023-08-25T18:25:57.042286Z", - "iopub.status.idle": "2023-08-25T18:25:57.059888Z", - "shell.execute_reply": "2023-08-25T18:25:57.059208Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "SummedOp([\n", - " ComposedOp([\n", - " OperatorMeasurement(0.18093119 * ZZ\n", - " - 1.0523732 * II),\n", - " ┌───┐\n", - " q_0: ┤ H ├\n", - " ├───┤\n", - " q_1: ┤ H ├\n", - " └───┘\n", - " ]),\n", - " ComposedOp([\n", - " OperatorMeasurement(0.39793742 * IZ\n", - " - 0.3979374 * ZI\n", - " - 0.0112801 * ZZ),\n", - " II\n", - " ])\n", - "])\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/2025675328.py:1: DeprecationWarning: The class ``qiskit.opflow.expectations.pauli_expectation.PauliExpectation`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " print(PauliExpectation().convert(h2_measurement))\n" - ] - } - ], - "source": [ - "print(PauliExpectation().convert(h2_measurement))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that converters act recursively, that is, they traverse an expression applying their action only where possible. So we can just convert our full evolution and measurement expression. We could have equivalently composed the converted `h2_measurement` with our evolution `CircuitStateFn`. We proceed by applying the conversion on the entire expression." - ] - }, - { - "cell_type": "code", - "execution_count": 60, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:57.064656Z", - "iopub.status.busy": "2023-08-25T18:25:57.063441Z", - "iopub.status.idle": "2023-08-25T18:25:57.085692Z", - "shell.execute_reply": "2023-08-25T18:25:57.085076Z" - }, - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "SummedOp([\n", - " ComposedOp([\n", - " OperatorMeasurement(0.18093119 * ZZ\n", - " - 1.0523732 * II),\n", - " CircuitStateFn(\n", - " ┌───┐ ┌───────────────────────────────────────────┐┌───┐\n", - " q_0: ┤ H ├──■──┤0 ├┤ H ├\n", - " └───┘┌─┴─┐│ exp(-it (II + IZ + ZI + ZZ + XX))(1.0*θ) │├───┤\n", - " q_1: ─────┤ X ├┤1 ├┤ H ├\n", - " └───┘└───────────────────────────────────────────┘└───┘\n", - " )\n", - " ]),\n", - " ComposedOp([\n", - " OperatorMeasurement(0.39793742 * IZ\n", - " - 0.3979374 * ZI\n", - " - 0.0112801 * ZZ),\n", - " CircuitStateFn(\n", - " ┌───┐ ┌───────────────────────────────────────────┐\n", - " q_0: ┤ H ├──■──┤0 ├\n", - " └───┘┌─┴─┐│ exp(-it (II + IZ + ZI + ZZ + XX))(1.0*θ) │\n", - " q_1: ─────┤ X ├┤1 ├\n", - " └───┘└───────────────────────────────────────────┘\n", - " )\n", - " ])\n", - "])\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/4170641321.py:1: DeprecationWarning: The class ``qiskit.opflow.expectations.pauli_expectation.PauliExpectation`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " diagonalized_meas_op = PauliExpectation().convert(trotterized_op)\n" - ] - } - ], - "source": [ - "diagonalized_meas_op = PauliExpectation().convert(trotterized_op)\n", - "print(diagonalized_meas_op)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we bind multiple parameter values into a `ListOp`, followed by `eval` to evaluate the entire expression. We could have used `eval` earlier if we bound earlier, but it would not be efficient. Here, `eval` will convert our `CircuitStateFn`s to `VectorStateFn`s through simulation internally." - ] - }, - { - "cell_type": "code", - "execution_count": 61, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:57.090356Z", - "iopub.status.busy": "2023-08-25T18:25:57.089147Z", - "iopub.status.idle": "2023-08-25T18:25:57.097072Z", - "shell.execute_reply": "2023-08-25T18:25:57.096482Z" - } - }, - "outputs": [], - "source": [ - "evo_time_points = list(range(8))\n", - "h2_trotter_expectations = diagonalized_meas_op.bind_parameters({evo_time: evo_time_points})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here are the expectation values $\\langle \\Phi_+| e^{iHt} H e^{-iHt} |\\Phi_+\\rangle$ corresponding to the different values of the parameter." - ] - }, - { - "cell_type": "code", - "execution_count": 62, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:57.101588Z", - "iopub.status.busy": "2023-08-25T18:25:57.100436Z", - "iopub.status.idle": "2023-08-25T18:25:57.411162Z", - "shell.execute_reply": "2023-08-25T18:25:57.410486Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "array([-0.88272211+0.j, -0.88272211+0.j, -0.88272211+0.j, -0.88272211+0.j,\n", - " -0.88272211+0.j, -0.88272211+0.j, -0.88272211+0.j, -0.88272211+0.j])" - ] - }, - "execution_count": 62, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "h2_trotter_expectations.eval()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Executing `CircuitStateFn`s with the `CircuitSampler`\n", - "\n", - "The `CircuitSampler` traverses an Operator and converts any `CircuitStateFn`s into approximations of the resulting state function by a `DictStateFn` or `VectorStateFn` using a quantum backend. Note that in order to approximate the value of the `CircuitStateFn`, it must 1) send the state function through a depolarizing channel, which will destroy all phase information and 2) replace the sampled frequencies with **square roots** of the frequency, rather than the raw probability of sampling (which would be the equivalent of sampling the **square** of the state function, per the Born rule)." - ] - }, - { - "cell_type": "code", - "execution_count": 63, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:57.416132Z", - "iopub.status.busy": "2023-08-25T18:25:57.414939Z", - "iopub.status.idle": "2023-08-25T18:25:58.273080Z", - "shell.execute_reply": "2023-08-25T18:25:58.272263Z" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10214/2634295565.py:1: DeprecationWarning: The class ``qiskit.opflow.converters.circuit_sampler.CircuitSampler`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " sampler = CircuitSampler(backend=Aer.get_backend('aer_simulator'))\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Sampled Trotterized energies:\n", - " [-0.88272211 -0.88272211 -0.88272211 -0.88272211 -0.88272211 -0.88272211\n", - " -0.88272211 -0.88272211]\n" - ] - } - ], - "source": [ - "sampler = CircuitSampler(backend=Aer.get_backend('aer_simulator'))\n", - "# sampler.quantum_instance.run_config.shots = 1000\n", - "sampled_trotter_exp_op = sampler.convert(h2_trotter_expectations)\n", - "sampled_trotter_energies = sampled_trotter_exp_op.eval()\n", - "print('Sampled Trotterized energies:\\n {}'.format(np.real(sampled_trotter_energies)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note again that the circuits are replaced by dicts with ***square roots*** of the circuit sampling probabilities. Take a look at one sub-expression before and after the conversion:" - ] - }, - { - "cell_type": "code", - "execution_count": 64, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:58.277314Z", - "iopub.status.busy": "2023-08-25T18:25:58.277024Z", - "iopub.status.idle": "2023-08-25T18:25:58.297905Z", - "shell.execute_reply": "2023-08-25T18:25:58.297085Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Before:\n", - "\n", - "ComposedOp([\n", - " OperatorMeasurement(0.18093119 * ZZ\n", - " - 1.0523732 * II),\n", - " CircuitStateFn(\n", - " ┌───┐ ┌───────────────────────────────────────┐┌───┐\n", - " q_0: ┤ H ├──■──┤0 ├┤ H ├\n", - " └───┘┌─┴─┐│ exp(-it (II + IZ + ZI + ZZ + XX))(0) │├───┤\n", - " q_1: ─────┤ X ├┤1 ├┤ H ├\n", - " └───┘└───────────────────────────────────────┘└───┘\n", - " )\n", - "])\n", - "\n", - "After:\n", - "\n", - "ComposedOp([\n", - " OperatorMeasurement(0.18093119 * ZZ\n", - " - 1.0523732 * II),\n", - " DictStateFn({'11': 0.7091753573693885, '00': 0.7050321357924049})\n", - "])\n" - ] - } - ], - "source": [ - "print('Before:\\n')\n", - "print(h2_trotter_expectations.reduce()[0][0])\n", - "print('\\nAfter:\\n')\n", - "print(sampled_trotter_exp_op[0][0])" - ] - }, - { - "cell_type": "code", - "execution_count": 65, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:25:58.303397Z", - "iopub.status.busy": "2023-08-25T18:25:58.302115Z", - "iopub.status.idle": "2023-08-25T18:25:58.902660Z", - "shell.execute_reply": "2023-08-25T18:25:58.902010Z" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "

Version Information

SoftwareVersion
qiskitNone
qiskit-terra0.45.0
qiskit_aer0.12.2
System information
Python version3.8.17
Python compilerGCC 11.3.0
Python builddefault, Jun 7 2023 12:29:56
OSLinux
CPUs2
Memory (Gb)6.7694854736328125
Fri Aug 25 18:25:58 2023 UTC
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "

This code is a part of Qiskit

© Copyright IBM 2017, 2023.

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.

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import qiskit.tools.jupyter\n", - "%qiskit_version_table\n", - "%qiskit_copyright" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.17" - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "state": { - "0536aec4253349f0a510da687a7cb5ad": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": "0px 0px 10px 0px", - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "4462da81d1df499e82002402b0af8517": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HTMLView", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_0536aec4253349f0a510da687a7cb5ad", - "placeholder": "​", - "style": "IPY_MODEL_6491e46af2f8493eb678e78d34c39e13", - "tabbable": null, - "tooltip": null, - "value": "

Circuit Properties

" - } - }, - "6491e46af2f8493eb678e78d34c39e13": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "background": null, - "description_width": "", - "font_size": null, - "text_color": null - } - } - }, - "version_major": 2, - "version_minor": 0 - } - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/tutorials/operators/02_gradients_framework.ipynb b/docs/tutorials/operators/02_gradients_framework.ipynb deleted file mode 100644 index 8c3b9f5ac29c..000000000000 --- a/docs/tutorials/operators/02_gradients_framework.ipynb +++ /dev/null @@ -1,1562 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "collapsed": true, - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# Qiskit Gradient Framework\n", - "\n", - "The gradient framework enables the evaluation of quantum gradients as well as functions thereof.\n", - "Besides standard first order gradients of expectation values of the form\n", - "$$ \\langle\\psi\\left(\\theta\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta\\right)\\rangle $$\n", - "\n", - "\n", - "The gradient framework also supports the evaluation of second order gradients (Hessians), and the Quantum Fisher Information (QFI) of quantum states $|\\psi\\left(\\theta\\right)\\rangle$." - ] - }, - { - "attachments": { - "gradient_framework.png": { - "image/png": "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" - } - }, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![gradient_framework.png](attachment:gradient_framework.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Imports" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:00.525679Z", - "iopub.status.busy": "2023-08-25T18:26:00.523227Z", - "iopub.status.idle": "2023-08-25T18:26:01.188952Z", - "shell.execute_reply": "2023-08-25T18:26:01.184878Z" - }, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/3317236272.py:5: DeprecationWarning: The ``qiskit.opflow`` module is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " from qiskit.opflow import Z, X, I, StateFn, CircuitStateFn, SummedOp\n" - ] - } - ], - "source": [ - "#General imports\n", - "import numpy as np\n", - "\n", - "#Operator Imports\n", - "from qiskit.opflow import Z, X, I, StateFn, CircuitStateFn, SummedOp\n", - "from qiskit.opflow.gradients import Gradient, NaturalGradient, QFI, Hessian\n", - "\n", - "#Circuit imports\n", - "from qiskit.circuit import QuantumCircuit, QuantumRegister, Parameter, ParameterVector, ParameterExpression\n", - "from qiskit.circuit.library import EfficientSU2" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## First Order Gradients\n", - "\n", - "\n", - "Given a parameterized quantum state $|\\psi\\left(\\theta\\right)\\rangle = V\\left(\\theta\\right)|\\psi\\rangle$ with input state $|\\psi\\rangle$, parametrized Ansatz $V\\left(\\theta\\right)$, and observable $\\hat{O}\\left(\\omega\\right)=\\sum_{i}\\omega_i\\hat{O}_i$, we want to compute..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Gradients w.r.t. Measurement Operator Parameters\n", - "\n", - "Gradient of an expectation value w.r.t. a coefficient of the measurement operator respectively observable $\\hat{O}\\left(\\omega\\right)$, i.e.\n", - "$$ \\frac{\\partial\\langle\\psi\\left(\\theta\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta\\right)\\rangle}{\\partial\\omega_i} = \\langle\\psi\\left(\\theta\\right)|\\hat{O}_i\\left(\\omega\\right)|\\psi\\left(\\theta\\right)\\rangle. $$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First of all, we define a quantum state $|\\psi\\left(\\theta\\right)\\rangle$ and a Hamiltonian $H$ acting as observable. Then, the state and the Hamiltonian are wrapped into an object defining the expectation value $$ \\langle\\psi\\left(\\theta\\right)|H|\\psi\\left(\\theta\\right)\\rangle. $$" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:01.192686Z", - "iopub.status.busy": "2023-08-25T18:26:01.192216Z", - "iopub.status.idle": "2023-08-25T18:26:01.564265Z", - "shell.execute_reply": "2023-08-25T18:26:01.563542Z" - }, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/1548928561.py:14: DeprecationWarning: The class ``qiskit.opflow.state_fns.operator_state_fn.OperatorStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " op = ~StateFn(H) @ CircuitStateFn(primitive=qc, coeff=1.)\n", - "/tmp/ipykernel_10262/1548928561.py:14: DeprecationWarning: The class ``qiskit.opflow.state_fns.circuit_state_fn.CircuitStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " op = ~StateFn(H) @ CircuitStateFn(primitive=qc, coeff=1.)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ComposedOp([\n", - " OperatorMeasurement(2.0 * X\n", - " + 1.0 * Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌───────┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b) ├\n", - " └───┘└───────┘└───────┘\n", - " )\n", - "])\n" - ] - } - ], - "source": [ - "# Instantiate the quantum state\n", - "a = Parameter('a')\n", - "b = Parameter('b')\n", - "q = QuantumRegister(1)\n", - "qc = QuantumCircuit(q)\n", - "qc.h(q)\n", - "qc.rz(a, q[0])\n", - "qc.rx(b, q[0])\n", - "\n", - "# Instantiate the Hamiltonian observable\n", - "H = (2 * X) + Z\n", - "\n", - "# Combine the Hamiltonian observable and the state\n", - "op = ~StateFn(H) @ CircuitStateFn(primitive=qc, coeff=1.)\n", - "\n", - "# Print the operator corresponding to the expectation value\n", - "print(op)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We construct a list of the parameters for which we aim to evaluate the gradient.\n", - "Now, this list and the expectation value operator are used to generate the operator which represents the gradient." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:01.567896Z", - "iopub.status.busy": "2023-08-25T18:26:01.567308Z", - "iopub.status.idle": "2023-08-25T18:26:01.643180Z", - "shell.execute_reply": "2023-08-25T18:26:01.642556Z" - }, - "scrolled": false, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ListOp([\n", - " SummedOp([\n", - " ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌─────────────┐┌───────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a + π/2) ├┤ Rx(b) ├┤ H ├\n", - " └───┘└─────────────┘└───────┘└───┘\n", - " )\n", - " ]),\n", - " -1.0 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌─────────────┐┌───────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a - π/2) ├┤ Rx(b) ├┤ H ├\n", - " └───┘└─────────────┘└───────┘└───┘\n", - " )\n", - " ]),\n", - " 0.5 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌─────────────┐┌───────┐\n", - " q0: ┤ H ├┤ Rz(a + π/2) ├┤ Rx(b) ├\n", - " └───┘└─────────────┘└───────┘\n", - " )\n", - " ]),\n", - " -0.5 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌─────────────┐┌───────┐\n", - " q0: ┤ H ├┤ Rz(a - π/2) ├┤ Rx(b) ├\n", - " └───┘└─────────────┘└───────┘\n", - " )\n", - " ])\n", - " ]),\n", - " SummedOp([\n", - " ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌─────────────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b + π/2) ├┤ H ├\n", - " └───┘└───────┘└─────────────┘└───┘\n", - " )\n", - " ]),\n", - " -1.0 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌─────────────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b - π/2) ├┤ H ├\n", - " └───┘└───────┘└─────────────┘└───┘\n", - " )\n", - " ]),\n", - " 0.5 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌─────────────┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b + π/2) ├\n", - " └───┘└───────┘└─────────────┘\n", - " )\n", - " ]),\n", - " -0.5 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌─────────────┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b - π/2) ├\n", - " └───┘└───────┘└─────────────┘\n", - " )\n", - " ])\n", - " ])\n", - "])\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/388471408.py:7: DeprecationWarning: The class ``qiskit.opflow.gradients.gradient.Gradient`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " grad = Gradient().convert(operator = op, params = params)\n" - ] - } - ], - "source": [ - "params = [a, b]\n", - "\n", - "# Define the values to be assigned to the parameters\n", - "value_dict = {a: np.pi / 4, b: np.pi}\n", - "\n", - "# Convert the operator and the gradient target params into the respective operator\n", - "grad = Gradient().convert(operator = op, params = params)\n", - "\n", - "# Print the operator corresponding to the Gradient\n", - "print(grad)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "All that is left to do is to assign values to the parameters and to evaluate the gradient operators." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:01.647475Z", - "iopub.status.busy": "2023-08-25T18:26:01.646058Z", - "iopub.status.idle": "2023-08-25T18:26:01.704502Z", - "shell.execute_reply": "2023-08-25T18:26:01.703801Z" - }, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Gradient [(-1.414213562373094+0j), (-0.7071067811865472+0j)]\n" - ] - } - ], - "source": [ - "# Assign the parameters and evaluate the gradient\n", - "grad_result = grad.assign_parameters(value_dict).eval()\n", - "print('Gradient', grad_result)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Gradients w.r.t. State Parameters\n", - "\n", - "Gradient of an expectation value w.r.t. a state $|\\psi\\left(\\theta\\right)\\rangle$ parameter, i.e.$$\\frac{\\partial\\langle\\psi\\left(\\theta\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta\\right)\\rangle}{\\partial\\theta} $$\n", - "respectively of sampling probabilities w.r.t. a state $|\\psi\\left(\\theta\\right)\\rangle$ parameter, i.e.\n", - "$$ \\frac{\\partial p_i}{\\partial\\theta} = \\frac{\\partial\\langle\\psi\\left(\\theta\\right)|i\\rangle\\langle i |\\psi\\left(\\theta\\right)\\rangle}{\\partial\\theta}.$$\n", - "A gradient w.r.t. a state parameter may be evaluated with different methods. Each method has advantages and disadvantages." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:01.709455Z", - "iopub.status.busy": "2023-08-25T18:26:01.708278Z", - "iopub.status.idle": "2023-08-25T18:26:01.719466Z", - "shell.execute_reply": "2023-08-25T18:26:01.718868Z" - }, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ComposedOp([\n", - " OperatorMeasurement(0.5 * X\n", - " - 1.0 * Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌───────┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b) ├\n", - " └───┘└───────┘└───────┘\n", - " )\n", - "])\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/3920713381.py:9: DeprecationWarning: The class ``qiskit.opflow.state_fns.operator_state_fn.OperatorStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " op = ~StateFn(H) @ CircuitStateFn(primitive=qc, coeff=1.)\n", - "/tmp/ipykernel_10262/3920713381.py:9: DeprecationWarning: The class ``qiskit.opflow.state_fns.circuit_state_fn.CircuitStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " op = ~StateFn(H) @ CircuitStateFn(primitive=qc, coeff=1.)\n" - ] - } - ], - "source": [ - "# Define the Hamiltonian with fixed coefficients\n", - "H = 0.5 * X - 1 * Z\n", - "# Define the parameters w.r.t. we want to compute the gradients\n", - "params = [a, b]\n", - "# Define the values to be assigned to the parameters\n", - "value_dict = { a: np.pi / 4, b: np.pi}\n", - "\n", - "# Combine the Hamiltonian observable and the state into an expectation value operator\n", - "op = ~StateFn(H) @ CircuitStateFn(primitive=qc, coeff=1.)\n", - "print(op)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Parameter Shift Gradients\n", - "Given a Hermitian operator $g$ with two unique eigenvalues $\\pm r$ which acts as generator for a parameterized quantum gate $$G(\\theta)= e^{-i\\theta g}.$$\n", - "Then, quantum gradients can be computed by using eigenvalue $r$ dependent shifts to parameters. \n", - "All [standard, parameterized Qiskit gates](https://github.com/Qiskit/qiskit-terra/tree/master/qiskit/circuit/library/standard_gates) can be shifted with $\\pi/2$, i.e.,\n", - " $$ \\frac{\\partial\\langle\\psi\\left(\\theta\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta\\right)\\rangle}{\\partial\\theta} = \\left(\\langle\\psi\\left(\\theta+\\pi/2\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta+\\pi/2\\right)\\rangle -\\langle\\psi\\left(\\theta-\\pi/2\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta-\\pi/2\\right)\\rangle\\right) / 2.$$\n", - " Probability gradients are computed equivalently." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:01.724151Z", - "iopub.status.busy": "2023-08-25T18:26:01.722838Z", - "iopub.status.idle": "2023-08-25T18:26:01.806625Z", - "shell.execute_reply": "2023-08-25T18:26:01.805968Z" - }, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ListOp([\n", - " SummedOp([\n", - " 0.25 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌─────────────┐┌───────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a + π/2) ├┤ Rx(b) ├┤ H ├\n", - " └───┘└─────────────┘└───────┘└───┘\n", - " )\n", - " ]),\n", - " -0.25 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌─────────────┐┌───────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a - π/2) ├┤ Rx(b) ├┤ H ├\n", - " └───┘└─────────────┘└───────┘└───┘\n", - " )\n", - " ]),\n", - " -0.5 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌─────────────┐┌───────┐\n", - " q0: ┤ H ├┤ Rz(a + π/2) ├┤ Rx(b) ├\n", - " └───┘└─────────────┘└───────┘\n", - " )\n", - " ]),\n", - " 0.5 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌─────────────┐┌───────┐\n", - " q0: ┤ H ├┤ Rz(a - π/2) ├┤ Rx(b) ├\n", - " └───┘└─────────────┘└───────┘\n", - " )\n", - " ])\n", - " ]),\n", - " SummedOp([\n", - " 0.25 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌─────────────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b + π/2) ├┤ H ├\n", - " └───┘└───────┘└─────────────┘└───┘\n", - " )\n", - " ]),\n", - " -0.25 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌─────────────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b - π/2) ├┤ H ├\n", - " └───┘└───────┘└─────────────┘└───┘\n", - " )\n", - " ]),\n", - " -0.5 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌─────────────┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b + π/2) ├\n", - " └───┘└───────┘└─────────────┘\n", - " )\n", - " ]),\n", - " 0.5 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌─────────────┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b - π/2) ├\n", - " └───┘└───────┘└─────────────┘\n", - " )\n", - " ])\n", - " ])\n", - "])\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/2773902786.py:3: DeprecationWarning: The class ``qiskit.opflow.gradients.gradient.Gradient`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " state_grad = Gradient(grad_method='param_shift').convert(operator=op, params=params)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "State gradient computed with parameter shift [(-0.35355339059327356+0j), (0.7071067811865471+0j)]\n" - ] - } - ], - "source": [ - "# Convert the expectation value into an operator corresponding to the gradient w.r.t. the state parameters using \n", - "# the parameter shift method.\n", - "state_grad = Gradient(grad_method='param_shift').convert(operator=op, params=params)\n", - "# Print the operator corresponding to the gradient\n", - "print(state_grad)\n", - "# Assign the parameters and evaluate the gradient\n", - "state_grad_result = state_grad.assign_parameters(value_dict).eval()\n", - "print('State gradient computed with parameter shift', state_grad_result)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "### Linear Combination of Unitaries Gradients\n", - "Unitaries can be written as $U\\left(\\omega\\right) = e^{iM\\left(\\omega\\right)}$, where $M\\left(\\omega\\right)$ denotes a parameterized Hermitian matrix. \n", - "Further, Hermitian matrices can be decomposed into weighted sums of Pauli terms, i.e., $M\\left(\\omega\\right) = \\sum_pm_p\\left(\\omega\\right)h_p$ with $m_p\\left(\\omega\\right)\\in\\mathbb{R}$ and $h_p=\\bigotimes\\limits_{j=0}^{n-1}\\sigma_{j, p}$ for $\\sigma_{j, p}\\in\\left\\{I, X, Y, Z\\right\\}$ acting on the $j^{\\text{th}}$ qubit. Thus, the gradients of \n", - "$U_k\\left(\\omega_k\\right)$ are given by\n", - "\\begin{equation*}\n", - "\\frac{\\partial U_k\\left(\\omega_k\\right)}{\\partial\\omega_k} = \\sum\\limits_pi \\frac{\\partial m_{k,p}\\left(\\omega_k\\right)}{\\partial\\omega_k}U_k\\left(\\omega_k\\right)h_{k_p}.\n", - "\\end{equation*}\n", - "\n", - "Combining this observation with a circuit structure presented in [Simulating physical phenomena by quantum networks](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.65.042323) allows us to compute the gradient with the evaluation of a single quantum circuit." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:01.811546Z", - "iopub.status.busy": "2023-08-25T18:26:01.810356Z", - "iopub.status.idle": "2023-08-25T18:26:01.886854Z", - "shell.execute_reply": "2023-08-25T18:26:01.886211Z" - }, - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ListOp([\n", - " SummedOp([\n", - " 0.5 * ComposedOp([\n", - " OperatorMeasurement(2.0 * ZZ),\n", - " CircuitStateFn(\n", - " ┌────────────┐ ┌───────┐┌───────┐┌────────────┐\n", - " q0: ┤ U(π/2,0,π) ├────────■─┤ Rz(a) ├┤ Rx(b) ├┤ U(π/2,0,π) ├\n", - " └───┬───┬────┘┌─────┐ │ └─┬───┬─┘└───────┘└────────────┘\n", - " q52: ────┤ H ├─────┤ Sdg ├─■───┤ H ├─────────────────────────\n", - " └───┘ └─────┘ └───┘ \n", - " ) * 0.7071067811865476\n", - " ]),\n", - " -1.0 * ComposedOp([\n", - " OperatorMeasurement(2.0 * ZZ),\n", - " CircuitStateFn(\n", - " ┌────────────┐ ┌───────┐┌───────┐\n", - " q0: ┤ U(π/2,0,π) ├────────■─┤ Rz(a) ├┤ Rx(b) ├\n", - " └───┬───┬────┘┌─────┐ │ └─┬───┬─┘└───────┘\n", - " q56: ────┤ H ├─────┤ Sdg ├─■───┤ H ├───────────\n", - " └───┘ └─────┘ └───┘ \n", - " ) * 0.7071067811865476\n", - " ])\n", - " ]),\n", - " SummedOp([\n", - " 0.5 * ComposedOp([\n", - " OperatorMeasurement(2.0 * ZZ),\n", - " CircuitStateFn(\n", - " ┌────────────┐┌───────┐┌───┐┌───────┐┌────────────┐\n", - " q0: ┤ U(π/2,0,π) ├┤ Rz(a) ├┤ X ├┤ Rx(b) ├┤ U(π/2,0,π) ├\n", - " └───┬───┬────┘└┬─────┬┘└─┬─┘└─┬───┬─┘└────────────┘\n", - " q60: ────┤ H ├──────┤ Sdg ├───■────┤ H ├────────────────\n", - " └───┘ └─────┘ └───┘ \n", - " ) * 0.7071067811865476\n", - " ]),\n", - " -1.0 * ComposedOp([\n", - " OperatorMeasurement(2.0 * ZZ),\n", - " CircuitStateFn(\n", - " ┌────────────┐┌───────┐┌───┐┌───────┐\n", - " q0: ┤ U(π/2,0,π) ├┤ Rz(a) ├┤ X ├┤ Rx(b) ├\n", - " └───┬───┬────┘└┬─────┬┘└─┬─┘└─┬───┬─┘\n", - " q64: ────┤ H ├──────┤ Sdg ├───■────┤ H ├──\n", - " └───┘ └─────┘ └───┘ \n", - " ) * 0.7071067811865476\n", - " ])\n", - " ])\n", - "])\n", - "State gradient computed with the linear combination method [(-0.35355339059327373+0j), (0.7071067811865471+0j)]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/141190660.py:3: DeprecationWarning: The class ``qiskit.opflow.gradients.gradient.Gradient`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " state_grad = Gradient(grad_method='lin_comb').convert(operator=op, params=params)\n" - ] - } - ], - "source": [ - "# Convert the expectation value into an operator corresponding to the gradient w.r.t. the state parameter using \n", - "# the linear combination of unitaries method.\n", - "state_grad = Gradient(grad_method='lin_comb').convert(operator=op, params=params)\n", - "\n", - "# Print the operator corresponding to the gradient\n", - "print(state_grad)\n", - "\n", - "# Assign the parameters and evaluate the gradient\n", - "state_grad_result = state_grad.assign_parameters(value_dict).eval()\n", - "print('State gradient computed with the linear combination method', state_grad_result)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "### Finite Difference Gradients\n", - "\n", - "Unlike the other methods, finite difference gradients are numerical estimations rather than analytical values.\n", - "This implementation employs a central difference approach with $\\epsilon \\ll 1$\n", - "$$ \\frac{\\partial\\langle\\psi\\left(\\theta\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta\\right)\\rangle}{\\partial\\theta} \\approx \\frac{1}{2\\epsilon} \\left(\\langle\\psi\\left(\\theta+\\epsilon\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta+\\epsilon\\right)\\rangle - \\partial\\langle\\psi\\left(\\theta-\\epsilon\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta-\\epsilon\\right)\\rangle\\right).$$\n", - " Probability gradients are computed equivalently." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:01.891613Z", - "iopub.status.busy": "2023-08-25T18:26:01.890434Z", - "iopub.status.idle": "2023-08-25T18:26:01.977595Z", - "shell.execute_reply": "2023-08-25T18:26:01.976861Z" - }, - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/1989470805.py:3: DeprecationWarning: The class ``qiskit.opflow.gradients.gradient.Gradient`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " state_grad = Gradient(grad_method='fin_diff').convert(operator=op, params=params)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ListOp([\n", - " SummedOp([\n", - " 250000.0 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌────────────────┐┌───────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a + 1.0e-6) ├┤ Rx(b) ├┤ H ├\n", - " └───┘└────────────────┘└───────┘└───┘\n", - " )\n", - " ]),\n", - " -250000.0 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌────────────────┐┌───────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a - 1.0e-6) ├┤ Rx(b) ├┤ H ├\n", - " └───┘└────────────────┘└───────┘└───┘\n", - " )\n", - " ]),\n", - " -500000.0 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌────────────────┐┌───────┐\n", - " q0: ┤ H ├┤ Rz(a + 1.0e-6) ├┤ Rx(b) ├\n", - " └───┘└────────────────┘└───────┘\n", - " )\n", - " ]),\n", - " 500000.0 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌────────────────┐┌───────┐\n", - " q0: ┤ H ├┤ Rz(a - 1.0e-6) ├┤ Rx(b) ├\n", - " └───┘└────────────────┘└───────┘\n", - " )\n", - " ])\n", - " ]),\n", - " SummedOp([\n", - " 250000.0 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌────────────────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b + 1.0e-6) ├┤ H ├\n", - " └───┘└───────┘└────────────────┘└───┘\n", - " )\n", - " ]),\n", - " -250000.0 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌────────────────┐┌───┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b - 1.0e-6) ├┤ H ├\n", - " └───┘└───────┘└────────────────┘└───┘\n", - " )\n", - " ]),\n", - " -500000.0 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌────────────────┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b + 1.0e-6) ├\n", - " └───┘└───────┘└────────────────┘\n", - " )\n", - " ]),\n", - " 500000.0 * ComposedOp([\n", - " OperatorMeasurement(Z),\n", - " CircuitStateFn(\n", - " ┌───┐┌───────┐┌────────────────┐\n", - " q0: ┤ H ├┤ Rz(a) ├┤ Rx(b - 1.0e-6) ├\n", - " └───┘└───────┘└────────────────┘\n", - " )\n", - " ])\n", - " ])\n", - "])\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "State gradient computed with finite difference [(-0.3535533905739581+0j), (0.707106781152+0j)]\n" - ] - } - ], - "source": [ - "# Convert the expectation value into an operator corresponding to the gradient w.r.t. the state parameter using \n", - "# the finite difference method.\n", - "state_grad = Gradient(grad_method='fin_diff').convert(operator=op, params=params)\n", - "\n", - "# Print the operator corresponding to the gradient\n", - "print(state_grad)\n", - "\n", - "# Assign the parameters and evaluate the gradient\n", - "state_grad_result = state_grad.assign_parameters(value_dict).eval()\n", - "print('State gradient computed with finite difference', state_grad_result)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Natural Gradient\n", - "\n", - "A special type of first order gradient is the natural gradient which has proven itself useful in classical machine learning and is already being studied in the quantum context. This quantity represents a gradient that is 'rescaled' with the inverse [Quantum Fisher Information matrix](#Quantum-Fisher-Information-(QFI)) (QFI)\n", - "$$ QFI ^{-1} \\frac{\\partial\\langle\\psi\\left(\\theta\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta\\right)\\rangle}{\\partial\\theta}.$$" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "Instead of inverting the QFI, one can also use a least-square solver with or without regularization to solve\n", - "\n", - "$$ QFI x = \\frac{\\partial\\langle\\psi\\left(\\theta\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta\\right)\\rangle}{\\partial\\theta}.$$\n", - "\n", - "The implementation supports ridge and lasso regularization with automatic search for a good parameter using [L-curve corner search](https://arxiv.org/pdf/1608.04571.pdf) as well as two types of perturbations of the diagonal elements of the QFI.\n", - "\n", - "The natural gradient can be used instead of the standard gradient with any gradient-based optimizer and/or ODE solver." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:01.982494Z", - "iopub.status.busy": "2023-08-25T18:26:01.981291Z", - "iopub.status.idle": "2023-08-25T18:26:02.658286Z", - "shell.execute_reply": "2023-08-25T18:26:02.657602Z" - }, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/4115387702.py:4: DeprecationWarning: The class ``qiskit.opflow.gradients.natural_gradient.NaturalGradient`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " nat_grad = NaturalGradient(grad_method='lin_comb', qfi_method='lin_comb_full', regularization='ridge').convert(\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Natural gradient computed with linear combination of unitaries [-2.62902831 1.31451415]\n" - ] - } - ], - "source": [ - "# Besides the method to compute the circuit gradients resp. QFI, a regularization method can be chosen: \n", - "# `ridge` or `lasso` with automatic parameter search or `perturb_diag_elements` or `perturb_diag` \n", - "# which perturb the diagonal elements of the QFI.\n", - "nat_grad = NaturalGradient(grad_method='lin_comb', qfi_method='lin_comb_full', regularization='ridge').convert(\n", - " operator=op, params=params)\n", - "\n", - "# Assign the parameters and evaluate the gradient\n", - "nat_grad_result = nat_grad.assign_parameters(value_dict).eval()\n", - "print('Natural gradient computed with linear combination of unitaries', nat_grad_result)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## Hessians (Second Order Gradients)\n", - "\n", - "Four types of second order gradients are supported by the gradient framework.\n", - "\n", - "1. Gradient of an expectation value w.r.t. a coefficient of the measurement operator respectively observable $\\hat{O}\\left(\\omega\\right)$, i.e.\n", - "$\\frac{\\partial^2\\langle\\psi\\left(\\theta\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta\\right)\\rangle}{\\partial\\omega^2}$\n", - "2. Gradient of an expectation value w.r.t. a state $|\\psi\\left(\\theta\\right)\\rangle$ parameter, i.e.\n", - "$\\frac{\\partial^2\\langle\\psi\\left(\\theta\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta\\right)\\rangle}{\\partial\\theta^2}$\n", - "3. Gradient of sampling probabilities w.r.t. a state $|\\psi\\left(\\theta\\right)\\rangle$ parameter, i.e.\n", - "$\\frac{\\partial^2 p_i}{\\partial\\theta^2} = \\frac{\\partial^2\\langle\\psi\\left(\\theta\\right)|i\\rangle\\langle i|\\psi\\left(\\theta\\right)\\rangle}{\\partial\\theta^2}$\n", - "4. Gradient of an expectation value w.r.t. a state $|\\psi\\left(\\theta\\right)\\rangle$ parameter and a coefficient of the measurement operator respectively observable $\\hat{O}\\left(\\omega\\right)$, i.e.\n", - "$\\frac{\\partial^2\\langle\\psi\\left(\\theta\\right)|\\hat{O}\\left(\\omega\\right)|\\psi\\left(\\theta\\right)\\rangle}{\\partial\\theta\\partial\\omega}$\n", - "\n", - "In the following examples are given for the first two Hessian types. The remaining Hessians are evaluated analogously." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Hessians w.r.t. Measurement Operator Parameters\n", - "\n", - "Again, we define a quantum state $|\\psi\\left(\\theta\\right)\\rangle$ and a Hamiltonian $H$ acting as observable. Then, the state and the Hamiltonian are wrapped into an object defining the expectation value $$ \\langle\\psi\\left(\\theta\\right)|H|\\psi\\left(\\theta\\right)\\rangle. $$" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:02.663279Z", - "iopub.status.busy": "2023-08-25T18:26:02.661991Z", - "iopub.status.idle": "2023-08-25T18:26:02.671563Z", - "shell.execute_reply": "2023-08-25T18:26:02.670966Z" - }, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/3187115399.py:15: DeprecationWarning: The class ``qiskit.opflow.state_fns.operator_state_fn.OperatorStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " op = ~StateFn(H) @ CircuitStateFn(primitive=qc, coeff=1.)\n", - "/tmp/ipykernel_10262/3187115399.py:15: DeprecationWarning: The class ``qiskit.opflow.state_fns.circuit_state_fn.CircuitStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " op = ~StateFn(H) @ CircuitStateFn(primitive=qc, coeff=1.)\n" - ] - } - ], - "source": [ - "# Instantiate the Hamiltonian observable\n", - "H = X\n", - "\n", - "# Instantiate the quantum state with two parameters\n", - "a = Parameter('a')\n", - "b = Parameter('b')\n", - "\n", - "q = QuantumRegister(1)\n", - "qc = QuantumCircuit(q)\n", - "qc.h(q)\n", - "qc.rz(a, q[0])\n", - "qc.rx(b, q[0])\n", - "\n", - "# Combine the Hamiltonian observable and the state\n", - "op = ~StateFn(H) @ CircuitStateFn(primitive=qc, coeff=1.)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we can choose the parameters for which we want to compute second order gradients.\n", - "- Given a tuple, the `Hessian` will evaluate the second order gradient for the two parameters. \n", - "- Given a list, the `Hessian` will evaluate the second order gradient for all possible combinations of tuples of these parameters.\n", - "\n", - "After binding parameter values to the parameters, the Hessian can be evaluated." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:02.676093Z", - "iopub.status.busy": "2023-08-25T18:26:02.674937Z", - "iopub.status.idle": "2023-08-25T18:26:02.767444Z", - "shell.execute_reply": "2023-08-25T18:26:02.766823Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hessian \n", - " [[-7.07106781e-01 0.00000000e+00]\n", - " [ 0.00000000e+00 -5.55111512e-17]]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/2007550450.py:2: DeprecationWarning: The class ``qiskit.opflow.gradients.hessian.Hessian`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " hessian = Hessian().convert(operator = op, params = [a, b])\n" - ] - } - ], - "source": [ - "# Convert the operator and the hessian target coefficients into the respective operator\n", - "hessian = Hessian().convert(operator = op, params = [a, b])\n", - "\n", - "# Define the values to be assigned to the parameters\n", - "value_dict = {a: np.pi / 4, b: np.pi/4}\n", - "\n", - "# Assign the parameters and evaluate the Hessian w.r.t. the Hamiltonian coefficients\n", - "hessian_result = hessian.assign_parameters(value_dict).eval()\n", - "print('Hessian \\n', np.real(np.array(hessian_result)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Hessians w.r.t. State Parameters" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:02.772148Z", - "iopub.status.busy": "2023-08-25T18:26:02.770982Z", - "iopub.status.idle": "2023-08-25T18:26:02.976214Z", - "shell.execute_reply": "2023-08-25T18:26:02.975541Z" - }, - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/1499636101.py:5: DeprecationWarning: The class ``qiskit.opflow.gradients.hessian.Hessian`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " state_hess = Hessian(hess_method='param_shift').convert(operator=op, params=params)\n", - "/tmp/ipykernel_10262/1499636101.py:11: DeprecationWarning: The class ``qiskit.opflow.gradients.hessian.Hessian`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " state_hess = Hessian(hess_method='lin_comb').convert(operator=op, params=params)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hessian computed using the parameter shift method\n", - " [[-7.07106781e-01+0.j 0.00000000e+00+0.j]\n", - " [ 0.00000000e+00+0.j -5.55111512e-17+0.j]]\n", - "Hessian computed using the linear combination of unitaries method\n", - " [[-7.07106781e-01+0.j -1.40000000e-17+0.j]\n", - " [-1.40000000e-17+0.j 5.60000000e-17+0.j]]\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hessian computed with finite difference\n", - " [[-7.07122803e-01+0.j -3.05175781e-05+0.j]\n", - " [-3.05175781e-05+0.j -6.10351562e-05+0.j]]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/1499636101.py:17: DeprecationWarning: The class ``qiskit.opflow.gradients.hessian.Hessian`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " state_hess = Hessian(hess_method='fin_diff').convert(operator=op, params=params)\n" - ] - } - ], - "source": [ - "# Define parameters\n", - "params = [a, b]\n", - "\n", - "# Get the operator object representing the Hessian\n", - "state_hess = Hessian(hess_method='param_shift').convert(operator=op, params=params)\n", - "# Assign the parameters and evaluate the Hessian\n", - "hessian_result = state_hess.assign_parameters(value_dict).eval()\n", - "print('Hessian computed using the parameter shift method\\n', (np.array(hessian_result)))\n", - "\n", - "# Get the operator object representing the Hessian\n", - "state_hess = Hessian(hess_method='lin_comb').convert(operator=op, params=params)\n", - "# Assign the parameters and evaluate the Hessian\n", - "hessian_result = state_hess.assign_parameters(value_dict).eval()\n", - "print('Hessian computed using the linear combination of unitaries method\\n', (np.array(hessian_result)))\n", - "\n", - "# Get the operator object representing the Hessian using finite difference\n", - "state_hess = Hessian(hess_method='fin_diff').convert(operator=op, params=params)\n", - "# Assign the parameters and evaluate the Hessian\n", - "hessian_result = state_hess.assign_parameters(value_dict).eval()\n", - "print('Hessian computed with finite difference\\n', (np.array(hessian_result)))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## Quantum Fisher Information (QFI)\n", - "The Quantum Fisher Information is a metric tensor which is representative for the representation capacity of a \n", - "parameterized quantum state $|\\psi\\left(\\theta\\right)\\rangle = V\\left(\\theta\\right)|\\psi\\rangle$ with input state $|\\psi\\rangle$, parametrized Ansatz $V\\left(\\theta\\right)$.\n", - "\n", - "The entries of the QFI for a pure state reads\n", - "\n", - "$$QFI_{kl} = 4 * \\text{Re}\\left[\\langle\\partial_k\\psi|\\partial_l\\psi\\rangle-\\langle\\partial_k\\psi|\\psi\\rangle\\langle\\psi|\\partial_l\\psi\\rangle \\right].$$" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Circuit QFIs\n", - "\n", - "The evaluation of the QFI corresponding to a quantum state that is generated by a parameterized quantum circuit can be conducted in different ways.\n", - "\n", - "### Linear Combination Full QFI\n", - "To compute the full QFI, we use a working qubit as well as intercepting controlled gates. See e.g. [Variational ansatz-based quantum simulation of imaginary time evolution ](https://www.nature.com/articles/s41534-019-0187-2)." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:02.981125Z", - "iopub.status.busy": "2023-08-25T18:26:02.979957Z", - "iopub.status.idle": "2023-08-25T18:26:03.064720Z", - "shell.execute_reply": "2023-08-25T18:26:03.064083Z" - }, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "full QFI \n", - " [[ 1.00000000e+00 -3.40575685e-16]\n", - " [-3.40575685e-16 5.00000000e-01]]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/1911751681.py:2: DeprecationWarning: The class ``qiskit.opflow.state_fns.circuit_state_fn.CircuitStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " state = CircuitStateFn(primitive=qc, coeff=1.)\n", - "/tmp/ipykernel_10262/1911751681.py:5: DeprecationWarning: The class ``qiskit.opflow.gradients.qfi.QFI`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " qfi = QFI(qfi_method='lin_comb_full').convert(operator=state, params=params)\n" - ] - } - ], - "source": [ - "# Wrap the quantum circuit into a CircuitStateFn\n", - "state = CircuitStateFn(primitive=qc, coeff=1.)\n", - "\n", - "# Convert the state and the parameters into the operator object that represents the QFI \n", - "qfi = QFI(qfi_method='lin_comb_full').convert(operator=state, params=params)\n", - "# Define the values for which the QFI is to be computed\n", - "values_dict = {a: np.pi / 4, b: 0.1}\n", - "\n", - "# Assign the parameters and evaluate the QFI\n", - "qfi_result = qfi.assign_parameters(values_dict).eval()\n", - "print('full QFI \\n', np.real(np.array(qfi_result)))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Block-diagonal and Diagonal Approximation\n", - "A block-diagonal resp. diagonal approximation of the QFI can be computed without additional working qubits.\n", - "This implementation requires the unrolling into Pauli rotations and unparameterized Gates." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:03.069421Z", - "iopub.status.busy": "2023-08-25T18:26:03.068259Z", - "iopub.status.idle": "2023-08-25T18:26:03.108201Z", - "shell.execute_reply": "2023-08-25T18:26:03.107580Z" - }, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/1343277097.py:3: DeprecationWarning: The class ``qiskit.opflow.gradients.qfi.QFI`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " qfi = QFI('overlap_block_diag').convert(operator=state, params=params)\n", - "/tmp/ipykernel_10262/1343277097.py:11: DeprecationWarning: The class ``qiskit.opflow.gradients.qfi.QFI`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " qfi = QFI('overlap_diag').convert(operator=state, params=params)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Block-diagonal QFI \n", - " [[1. 0. ]\n", - " [0. 0.5]]\n", - "Diagonal QFI \n", - " [[1. 0. ]\n", - " [0. 0.5]]\n" - ] - } - ], - "source": [ - "# Convert the state and the parameters into the operator object that represents the QFI \n", - "# and set the approximation to 'block_diagonal'\n", - "qfi = QFI('overlap_block_diag').convert(operator=state, params=params)\n", - "\n", - "# Assign the parameters and evaluate the QFI\n", - "qfi_result = qfi.assign_parameters(values_dict).eval()\n", - "print('Block-diagonal QFI \\n', np.real(np.array(qfi_result)))\n", - "\n", - "# Convert the state and the parameters into the operator object that represents the QFI \n", - "# and set the approximation to 'diagonal'\n", - "qfi = QFI('overlap_diag').convert(operator=state, params=params)\n", - "\n", - "# Assign the parameters and evaluate the QFI\n", - "qfi_result = qfi.assign_parameters(values_dict).eval()\n", - "print('Diagonal QFI \\n', np.real(np.array(qfi_result)))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## Application Example: VQE with gradient-based optimization" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Additional Imports" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:03.112835Z", - "iopub.status.busy": "2023-08-25T18:26:03.111685Z", - "iopub.status.idle": "2023-08-25T18:26:03.164582Z", - "shell.execute_reply": "2023-08-25T18:26:03.163939Z" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/514832364.py:6: DeprecationWarning: ``qiskit.algorithms`` has been migrated to an independent package: https://github.com/qiskit-community/qiskit-algorithms. The ``qiskit.algorithms`` import path is deprecated as of qiskit-terra 0.25.0 and will be removed no earlier than 3 months after the release date. Please run ``pip install qiskit_algorithms`` and use ``import qiskit_algorithms`` instead.\n", - " from qiskit.algorithms import VQE\n" - ] - } - ], - "source": [ - "# Execution Imports\n", - "from qiskit import Aer\n", - "from qiskit.utils import QuantumInstance\n", - "\n", - "# Algorithm Imports\n", - "from qiskit.algorithms import VQE\n", - "from qiskit.algorithms.optimizers import CG" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "The Gradient Framework can also be used for a gradient-based `VQE`.\n", - "First, the Hamiltonian and wavefunction ansatz are initialized." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:03.169325Z", - "iopub.status.busy": "2023-08-25T18:26:03.168170Z", - "iopub.status.idle": "2023-08-25T18:26:03.180694Z", - "shell.execute_reply": "2023-08-25T18:26:03.180115Z" - }, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/1163076158.py:26: DeprecationWarning: The class ``qiskit.opflow.state_fns.operator_state_fn.OperatorStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " op = ~StateFn(h2_hamiltonian) @ StateFn(wavefunction)\n", - "/tmp/ipykernel_10262/1163076158.py:26: DeprecationWarning: The class ``qiskit.opflow.state_fns.circuit_state_fn.CircuitStateFn`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " op = ~StateFn(h2_hamiltonian) @ StateFn(wavefunction)\n" - ] - } - ], - "source": [ - "from qiskit.opflow import I, X, Z\n", - "from qiskit.circuit import QuantumCircuit, ParameterVector\n", - "from scipy.optimize import minimize\n", - "\n", - "# Instantiate the system Hamiltonian\n", - "h2_hamiltonian = -1.05 * (I ^ I) + 0.39 * (I ^ Z) - 0.39 * (Z ^ I) - 0.01 * (Z ^ Z) + 0.18 * (X ^ X)\n", - "\n", - "# This is the target energy\n", - "h2_energy = -1.85727503\n", - "\n", - "# Define the Ansatz\n", - "wavefunction = QuantumCircuit(2)\n", - "params = ParameterVector('theta', length=8)\n", - "it = iter(params)\n", - "wavefunction.ry(next(it), 0)\n", - "wavefunction.ry(next(it), 1)\n", - "wavefunction.rz(next(it), 0)\n", - "wavefunction.rz(next(it), 1)\n", - "wavefunction.cx(0, 1)\n", - "wavefunction.ry(next(it), 0)\n", - "wavefunction.ry(next(it), 1)\n", - "wavefunction.rz(next(it), 0)\n", - "wavefunction.rz(next(it), 1)\n", - "\n", - "# Define the expectation value corresponding to the energy\n", - "op = ~StateFn(h2_hamiltonian) @ StateFn(wavefunction)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we can choose whether the `VQE` should use a `Gradient` or `NaturalGradient`, define a `QuantumInstance` to execute the quantum circuits and run the algorithm." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:03.185309Z", - "iopub.status.busy": "2023-08-25T18:26:03.184139Z", - "iopub.status.idle": "2023-08-25T18:26:08.868302Z", - "shell.execute_reply": "2023-08-25T18:26:08.866717Z" - }, - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10262/3278035267.py:1: DeprecationWarning: The class ``qiskit.opflow.gradients.gradient.Gradient`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/opflow_migration.\n", - " grad = Gradient(grad_method='lin_comb')\n", - "/tmp/ipykernel_10262/3278035267.py:3: DeprecationWarning: The class ``qiskit.utils.quantum_instance.QuantumInstance`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. For code migration guidelines, visit https://qisk.it/qi_migration.\n", - " qi_sv = QuantumInstance(Aer.get_backend('aer_simulator_statevector'),\n", - "/tmp/ipykernel_10262/3278035267.py:12: DeprecationWarning: The class ``qiskit.algorithms.minimum_eigen_solvers.vqe.VQE`` is deprecated as of qiskit-terra 0.24.0. It will be removed no earlier than 3 months after the release date. Instead, use the class ``qiskit.algorithms.minimum_eigensolvers.VQE``. See https://qisk.it/algo_migration for a migration guide.\n", - " vqe = VQE(wavefunction, optimizer=optimizer, gradient=grad, quantum_instance=qi_sv)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Result: -1.8404998434683832 Reference: -1.85727503\n" - ] - } - ], - "source": [ - "grad = Gradient(grad_method='lin_comb')\n", - "\n", - "qi_sv = QuantumInstance(Aer.get_backend('aer_simulator_statevector'),\n", - " shots=1,\n", - " seed_simulator=2,\n", - " seed_transpiler=2)\n", - "\n", - "#Conjugate Gradient algorithm\n", - "optimizer = CG(maxiter=50)\n", - "\n", - "# Gradient callable\n", - "vqe = VQE(wavefunction, optimizer=optimizer, gradient=grad, quantum_instance=qi_sv)\n", - "\n", - "result = vqe.compute_minimum_eigenvalue(h2_hamiltonian)\n", - "print('Result:', result.optimal_value, 'Reference:', h2_energy)" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "execution": { - "iopub.execute_input": "2023-08-25T18:26:08.871785Z", - "iopub.status.busy": "2023-08-25T18:26:08.871300Z", - "iopub.status.idle": "2023-08-25T18:26:09.312168Z", - "shell.execute_reply": "2023-08-25T18:26:09.311334Z" - }, - "slideshow": { - "slide_type": "notes" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "

Version Information

SoftwareVersion
qiskitNone
qiskit-terra0.45.0
qiskit_aer0.12.2
System information
Python version3.8.17
Python compilerGCC 11.3.0
Python builddefault, Jun 7 2023 12:29:56
OSLinux
CPUs2
Memory (Gb)6.7694854736328125
Fri Aug 25 18:26:09 2023 UTC
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "

This code is a part of Qiskit

© Copyright IBM 2017, 2023.

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.

" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import qiskit.tools.jupyter\n", - "%qiskit_version_table\n", - "%qiskit_copyright" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.17" - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "state": { - "4f507656df55472a9d8cd8614c47ac47": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": "0px 0px 10px 0px", - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "8f88aaab3cb54c6cb31e9c363a91d292": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HTMLView", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_4f507656df55472a9d8cd8614c47ac47", - "placeholder": "​", - "style": "IPY_MODEL_ae351d7645f04106b5d21c9ae94f09f4", - "tabbable": null, - "tooltip": null, - "value": "

Circuit Properties

" - } - }, - "ae351d7645f04106b5d21c9ae94f09f4": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "background": null, - "description_width": "", - "font_size": null, - "text_color": null - } - } - }, - "version_major": 2, - "version_minor": 0 - } - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/docs/tutorials/operators/images/gradient_framework.png b/docs/tutorials/operators/images/gradient_framework.png deleted file mode 100644 index ca17b48263d5..000000000000 Binary files a/docs/tutorials/operators/images/gradient_framework.png and /dev/null differ diff --git a/qiskit/VERSION.txt b/qiskit/VERSION.txt index 27edeeb5c031..3eefcb9dd5b3 100644 --- a/qiskit/VERSION.txt +++ b/qiskit/VERSION.txt @@ -1 +1 @@ -0.45.0rc1 +1.0.0 diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 20f2e3c80851..8075ae39d3c9 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -14,7 +14,6 @@ """Main Qiskit public functionality.""" -import pkgutil import sys import warnings @@ -69,14 +68,6 @@ import qiskit.circuit.measure import qiskit.circuit.reset -# Allow extending this namespace. Please note that currently this line needs -# to be placed *before* the wrapper imports or any non-import code AND *before* -# importing the package you want to allow extensions for (in this case `backends`). - -# Support for the deprecated extending this namespace. -# Remove this after 0.46.0 release -__path__ = pkgutil.extend_path(__path__, __name__) - # Please note these are global instances, not modules. from qiskit.providers.basicaer import BasicAer diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py deleted file mode 100644 index 18767f1fdd01..000000000000 --- a/qiskit/algorithms/__init__.py +++ /dev/null @@ -1,430 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -""" -===================================== -Algorithms (:mod:`qiskit.algorithms`) -===================================== - -.. deprecated:: 0.25.0 - - The :mod:`qiskit.algorithms` module has been migrated to an independent package: - https://github.com/qiskit-community/qiskit-algorithms. - The current import path is deprecated and will be removed no earlier - than 3 months after the release date. If your code uses primitives, you can run - ``pip install qiskit_algorithms`` and import ``from qiskit_algorithms`` instead. - If you use opflow/quantum instance-based algorithms, please update your code to - use primitives following: https://qisk.it/algo_migration before migrating to - the new package. - -It contains a collection of quantum algorithms, for use with quantum computers, to -carry out research and investigate how to solve problems in different domains on -near-term quantum devices with short depth circuits. - -Algorithms configuration includes the use of :mod:`~qiskit.algorithms.optimizers` which -were designed to be swappable sub-parts of an algorithm. Any component and may be exchanged for -a different implementation of the same component type in order to potentially alter the behavior -and outcome of the algorithm. - -Quantum algorithms are run via a :class:`~qiskit.algorithms.QuantumInstance` -which must be set with the -desired backend where the algorithm's circuits will be executed and be configured with a number of -compile and runtime parameters controlling circuit compilation and execution. It ultimately uses -`Terra `__ for the actual compilation and execution of the quantum -circuits created by the algorithm and its components. - -.. currentmodule:: qiskit.algorithms - -Algorithms -========== - -It contains a variety of quantum algorithms and these have been grouped by logical function such -as minimum eigensolvers and amplitude amplifiers. - - -Amplitude Amplifiers --------------------- - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - AmplificationProblem - AmplitudeAmplifier - Grover - GroverResult - - -Amplitude Estimators --------------------- - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - AmplitudeEstimator - AmplitudeEstimatorResult - AmplitudeEstimation - AmplitudeEstimationResult - EstimationProblem - FasterAmplitudeEstimation - FasterAmplitudeEstimationResult - IterativeAmplitudeEstimation - IterativeAmplitudeEstimationResult - MaximumLikelihoodAmplitudeEstimation - MaximumLikelihoodAmplitudeEstimationResult - - -Eigensolvers ------------- - -Algorithms to find eigenvalues of an operator. For chemistry these can be used to find excited -states of a molecule, and ``qiskit-nature`` has some algorithms that leverage chemistry specific -knowledge to do this in that application domain. - -Primitive-based Eigensolvers -++++++++++++++++++++++++++++ - -These algorithms are based on the Qiskit Primitives, a new execution paradigm that replaces the use -of :class:`.QuantumInstance` in algorithms. To ensure continued support and development, we recommend -using the primitive-based Eigensolvers in place of the legacy :class:`.QuantumInstance`-based ones. - -.. autosummary:: - :toctree: ../stubs/ - - eigensolvers - - -Legacy Eigensolvers -+++++++++++++++++++ - -These algorithms, still based on the :class:`.QuantumInstance`, are superseded -by the primitive-based versions in the section above but are still supported for now. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - Eigensolver - EigensolverResult - NumPyEigensolver - VQD - VQDResult - - -Time Evolvers -------------- - -Algorithms to evolve quantum states in time. Both real and imaginary time evolution is possible -with algorithms that support them. For machine learning, Quantum Imaginary Time Evolution might be -used to train Quantum Boltzmann Machine Neural Networks for example. - -Primitive-based Time Evolvers -+++++++++++++++++++++++++++++ - -These algorithms are based on the Qiskit Primitives, a new execution paradigm that replaces the use -of :class:`.QuantumInstance` in algorithms. To ensure continued support and development, we recommend -using the primitive-based Time Evolvers in place of the legacy :class:`.QuantumInstance`-based ones. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - RealTimeEvolver - ImaginaryTimeEvolver - TimeEvolutionResult - TimeEvolutionProblem - PVQD - PVQDResult - SciPyImaginaryEvolver - SciPyRealEvolver - VarQITE - VarQRTE - -Legacy Time Evolvers -++++++++++++++++++++ - -These algorithms, still based on the :class:`.QuantumInstance`, are superseded -by the primitive-based versions in the section above but are still supported for now. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - RealEvolver - ImaginaryEvolver - TrotterQRTE - EvolutionResult - EvolutionProblem - - -Variational Quantum Time Evolution -++++++++++++++++++++++++++++++++++ - -Classes used by variational quantum time evolution algorithms - :class:`.VarQITE` and -:class:`.VarQRTE`. - -.. autosummary:: - :toctree: ../stubs/ - - time_evolvers.variational - - -Trotterization-based Quantum Real Time Evolution -++++++++++++++++++++++++++++++++++++++++++++++++ - -Package for primitives-enabled Trotterization-based quantum time evolution -algorithm - :class:`~.time_evolvers.TrotterQRTE`. - -.. autosummary:: - :toctree: ../stubs/ - - time_evolvers.trotterization - - -Gradients ----------- - -Algorithms to calculate the gradient of a quantum circuit. - -.. autosummary:: - :toctree: ../stubs/ - - gradients - - -Minimum Eigensolvers ---------------------- - -Algorithms that can find the minimum eigenvalue of an operator. - -Primitive-based Minimum Eigensolvers -++++++++++++++++++++++++++++++++++++ - -These algorithms are based on the Qiskit Primitives, a new execution paradigm that replaces the use -of :class:`.QuantumInstance` in algorithms. To ensure continued support and development, we recommend -using the primitive-based Minimum Eigensolvers in place of the legacy :class:`.QuantumInstance`-based -ones. - -.. autosummary:: - :toctree: ../stubs/ - - minimum_eigensolvers - - -Legacy Minimum Eigensolvers -+++++++++++++++++++++++++++ - -These algorithms, still based on the :class:`.QuantumInstance`, are superseded -by the primitive-based versions in the section above but are still supported for now. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - MinimumEigensolver - MinimumEigensolverResult - NumPyMinimumEigensolver - QAOA - VQE - - -Optimizers ----------- - -Classical optimizers for use by quantum variational algorithms. - -.. autosummary:: - :toctree: ../stubs/ - - optimizers - - -Phase Estimators ----------------- - -Algorithms that estimate the phases of eigenstates of a unitary. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - HamiltonianPhaseEstimation - HamiltonianPhaseEstimationResult - PhaseEstimationScale - PhaseEstimation - PhaseEstimationResult - IterativePhaseEstimation - - -State Fidelities ----------------- - -Algorithms that compute the fidelity of pairs of quantum states. - -.. autosummary:: - :toctree: ../stubs/ - - state_fidelities - - -Exceptions ----------- - -.. autoexception:: AlgorithmError - -Utility classes ---------------- - -Utility classes used by algorithms (mainly for type-hinting purposes). - -.. autosummary:: - :toctree: ../stubs/ - - AlgorithmJob - -Utility functions ------------------ - -Utility functions used by algorithms. - -.. autofunction:: eval_observables -.. autofunction:: estimate_observables - -""" -import warnings - -from .algorithm_job import AlgorithmJob -from .algorithm_result import AlgorithmResult -from .evolvers import EvolutionResult, EvolutionProblem -from .evolvers.real_evolver import RealEvolver -from .evolvers.imaginary_evolver import ImaginaryEvolver -from .variational_algorithm import VariationalAlgorithm, VariationalResult -from .amplitude_amplifiers import Grover, GroverResult, AmplificationProblem, AmplitudeAmplifier -from .amplitude_estimators import ( - AmplitudeEstimator, - AmplitudeEstimatorResult, - AmplitudeEstimation, - AmplitudeEstimationResult, - FasterAmplitudeEstimation, - FasterAmplitudeEstimationResult, - IterativeAmplitudeEstimation, - IterativeAmplitudeEstimationResult, - MaximumLikelihoodAmplitudeEstimation, - MaximumLikelihoodAmplitudeEstimationResult, - EstimationProblem, -) -from .eigen_solvers import NumPyEigensolver, Eigensolver, EigensolverResult, VQD, VQDResult -from .minimum_eigen_solvers import ( - VQE, - VQEResult, - QAOA, - NumPyMinimumEigensolver, - MinimumEigensolver, - MinimumEigensolverResult, -) -from .phase_estimators import ( - HamiltonianPhaseEstimation, - HamiltonianPhaseEstimationResult, - PhaseEstimationScale, - PhaseEstimation, - PhaseEstimationResult, - IterativePhaseEstimation, -) -from .exceptions import AlgorithmError -from .aux_ops_evaluator import eval_observables -from .observables_evaluator import estimate_observables -from .evolvers.trotterization import TrotterQRTE - -from .time_evolvers import ( - ImaginaryTimeEvolver, - RealTimeEvolver, - TimeEvolutionProblem, - TimeEvolutionResult, - PVQD, - PVQDResult, - SciPyImaginaryEvolver, - SciPyRealEvolver, - VarQITE, - VarQRTE, - VarQTE, - VarQTEResult, -) - -__all__ = [ - "AlgorithmJob", - "AlgorithmResult", - "VariationalAlgorithm", - "VariationalResult", - "AmplitudeAmplifier", - "AmplificationProblem", - "Grover", - "GroverResult", - "AmplitudeEstimator", - "AmplitudeEstimatorResult", - "AmplitudeEstimation", - "AmplitudeEstimationResult", - "FasterAmplitudeEstimation", - "FasterAmplitudeEstimationResult", - "IterativeAmplitudeEstimation", - "IterativeAmplitudeEstimationResult", - "MaximumLikelihoodAmplitudeEstimation", - "MaximumLikelihoodAmplitudeEstimationResult", - "EstimationProblem", - "NumPyEigensolver", - "RealEvolver", - "ImaginaryEvolver", - "RealTimeEvolver", - "ImaginaryTimeEvolver", - "TrotterQRTE", - "EvolutionResult", - "EvolutionProblem", - "TimeEvolutionResult", - "TimeEvolutionProblem", - "Eigensolver", - "EigensolverResult", - "VQE", - "VQEResult", - "QAOA", - "NumPyMinimumEigensolver", - "MinimumEigensolver", - "MinimumEigensolverResult", - "HamiltonianPhaseEstimation", - "HamiltonianPhaseEstimationResult", - "VQD", - "VQDResult", - "PhaseEstimationScale", - "PhaseEstimation", - "PhaseEstimationResult", - "PVQD", - "PVQDResult", - "SciPyRealEvolver", - "SciPyImaginaryEvolver", - "IterativePhaseEstimation", - "AlgorithmError", - "eval_observables", - "estimate_observables", - "VarQITE", - "VarQRTE", - "VarQTE", - "VarQTEResult", -] - -warnings.warn( - "``qiskit.algorithms`` has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. " - "The ``qiskit.algorithms`` import path is deprecated as of qiskit-terra 0.25.0 and " - "will be removed no earlier than 3 months after the release date. " - "Please run ``pip install qiskit_algorithms`` and use ``import qiskit_algorithms`` instead.", - category=DeprecationWarning, - stacklevel=2, -) diff --git a/qiskit/algorithms/algorithm_job.py b/qiskit/algorithms/algorithm_job.py deleted file mode 100644 index 16db4df93dfc..000000000000 --- a/qiskit/algorithms/algorithm_job.py +++ /dev/null @@ -1,24 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -""" -AlgorithmJob class -""" -from qiskit.primitives.primitive_job import PrimitiveJob - - -class AlgorithmJob(PrimitiveJob): - """ - This empty class is introduced for typing purposes. - """ - - pass diff --git a/qiskit/algorithms/algorithm_result.py b/qiskit/algorithms/algorithm_result.py deleted file mode 100644 index 0804303a4ef6..000000000000 --- a/qiskit/algorithms/algorithm_result.py +++ /dev/null @@ -1,65 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2021. -# -# 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. - -""" -This module implements the abstract base class for algorithm results. -""" - -from abc import ABC -import inspect -import pprint - - -class AlgorithmResult(ABC): - """Abstract Base Class for algorithm results.""" - - def __str__(self) -> str: - result = {} - for name, value in inspect.getmembers(self): - if ( - not name.startswith("_") - and not inspect.ismethod(value) - and not inspect.isfunction(value) - and hasattr(self, name) - ): - - result[name] = value - - return pprint.pformat(result, indent=4) - - def combine(self, result: "AlgorithmResult") -> None: - """ - Any property from the argument that exists in the receiver is - updated. - Args: - result: Argument result with properties to be set. - Raises: - TypeError: Argument is None - """ - if result is None: - raise TypeError("Argument result expected.") - if result == self: - return - - # find any result public property that exists in the receiver - for name, value in inspect.getmembers(result): - if ( - not name.startswith("_") - and not inspect.ismethod(value) - and not inspect.isfunction(value) - and hasattr(self, name) - ): - try: - setattr(self, name, value) - except AttributeError: - # some attributes may be read only - pass diff --git a/qiskit/algorithms/amplitude_amplifiers/__init__.py b/qiskit/algorithms/amplitude_amplifiers/__init__.py deleted file mode 100644 index bc45f18106bd..000000000000 --- a/qiskit/algorithms/amplitude_amplifiers/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Amplitude Amplifiers Package""" - -from .amplitude_amplifier import AmplitudeAmplifier, AmplitudeAmplifierResult -from .amplification_problem import AmplificationProblem -from .grover import Grover, GroverResult - -__all__ = [ - "AmplitudeAmplifier", - "AmplitudeAmplifierResult", - "AmplificationProblem", - "Grover", - "GroverResult", -] diff --git a/qiskit/algorithms/amplitude_amplifiers/amplification_problem.py b/qiskit/algorithms/amplitude_amplifiers/amplification_problem.py deleted file mode 100644 index 67b20751c417..000000000000 --- a/qiskit/algorithms/amplitude_amplifiers/amplification_problem.py +++ /dev/null @@ -1,213 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. - -"""The Amplification problem class.""" -from __future__ import annotations - -from collections.abc import Callable -from typing import Any - -from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library import GroverOperator -from qiskit.quantum_info import Statevector - - -class AmplificationProblem: - """The amplification problem is the input to amplitude amplification algorithms, like Grover. - - This class contains all problem-specific information required to run an amplitude amplification - algorithm. It minimally contains the Grover operator. It can further hold some post processing - on the optimal bitstring. - """ - - def __init__( - self, - oracle: QuantumCircuit | Statevector, - state_preparation: QuantumCircuit | None = None, - grover_operator: QuantumCircuit | None = None, - post_processing: Callable[[str], Any] | None = None, - objective_qubits: int | list[int] | None = None, - is_good_state: Callable[[str], bool] | list[int] | list[str] | Statevector | None = None, - ) -> None: - r""" - Args: - oracle: The oracle reflecting about the bad states. - state_preparation: A circuit preparing the input state, referred to as - :math:`\mathcal{A}`. If None, a layer of Hadamard gates is used. - grover_operator: The Grover operator :math:`\mathcal{Q}` used as unitary in the - phase estimation circuit. If None, this operator is constructed from the ``oracle`` - and ``state_preparation``. - post_processing: A mapping applied to the most likely bitstring. - objective_qubits: If set, specifies the indices of the qubits that should be measured. - If None, all qubits will be measured. The ``is_good_state`` function will be - applied on the measurement outcome of these qubits. - is_good_state: A function to check whether a string represents a good state. By default - if the ``oracle`` argument has an ``evaluate_bitstring`` method (currently only - provided by the :class:`~qiskit.circuit.library.PhaseOracle` class) this will be - used, otherwise this kwarg is required and **must** be specified. - """ - self._oracle = oracle - self._state_preparation = state_preparation - self._grover_operator = grover_operator - self._post_processing = post_processing - self._objective_qubits = objective_qubits - if is_good_state is not None: - self._is_good_state = is_good_state - elif hasattr(oracle, "evaluate_bitstring"): - self._is_good_state = oracle.evaluate_bitstring - else: - self._is_good_state = None - - @property - def oracle(self) -> QuantumCircuit | Statevector: - """Return the oracle. - - Returns: - The oracle. - """ - return self._oracle - - @oracle.setter - def oracle(self, oracle: QuantumCircuit | Statevector) -> None: - """Set the oracle. - - Args: - oracle: The oracle. - """ - self._oracle = oracle - - @property - def state_preparation(self) -> QuantumCircuit: - r"""Get the state preparation operator :math:`\mathcal{A}`. - - Returns: - The :math:`\mathcal{A}` operator as `QuantumCircuit`. - """ - if self._state_preparation is None: - state_preparation = QuantumCircuit(self.oracle.num_qubits) - state_preparation.h(state_preparation.qubits) - return state_preparation - - return self._state_preparation - - @state_preparation.setter - def state_preparation(self, state_preparation: QuantumCircuit | None) -> None: - r"""Set the :math:`\mathcal{A}` operator. If None, a layer of Hadamard gates is used. - - Args: - state_preparation: The new :math:`\mathcal{A}` operator or None. - """ - self._state_preparation = state_preparation - - @property - def post_processing(self) -> Callable[[str], Any]: - """Apply post processing to the input value. - - Returns: - A handle to the post processing function. Acts as identity by default. - """ - if self._post_processing is None: - return lambda x: x - - return self._post_processing - - @post_processing.setter - def post_processing(self, post_processing: Callable[[str], Any]) -> None: - """Set the post processing function. - - Args: - post_processing: A handle to the post processing function. - """ - self._post_processing = post_processing - - @property - def objective_qubits(self) -> list[int]: - """The indices of the objective qubits. - - Returns: - The indices of the objective qubits as list of integers. - """ - if self._objective_qubits is None: - return list(range(self.oracle.num_qubits)) - - if isinstance(self._objective_qubits, int): - return [self._objective_qubits] - - return self._objective_qubits - - @objective_qubits.setter - def objective_qubits(self, objective_qubits: int | list[int] | None) -> None: - """Set the objective qubits. - - Args: - objective_qubits: The indices of the qubits that should be measured. - If None, all qubits will be measured. The ``is_good_state`` function will be - applied on the measurement outcome of these qubits. - """ - self._objective_qubits = objective_qubits - - @property - def is_good_state(self) -> Callable[[str], bool]: - """Check whether a provided bitstring is a good state or not. - - Returns: - A callable that takes in a bitstring and returns True if the measurement is a good - state, False otherwise. - """ - if (self._is_good_state is None) or callable(self._is_good_state): - return self._is_good_state # returns None if no is_good_state arg has been set - elif isinstance(self._is_good_state, list): - if all(isinstance(good_bitstr, str) for good_bitstr in self._is_good_state): - return lambda bitstr: bitstr in self._is_good_state - else: - return lambda bitstr: all( - bitstr[good_index] == "1" for good_index in self._is_good_state - ) - - return lambda bitstr: bitstr in self._is_good_state.probabilities_dict() - - @is_good_state.setter - def is_good_state( - self, is_good_state: Callable[[str], bool] | list[int] | list[str] | Statevector - ) -> None: - """Set the ``is_good_state`` function. - - Args: - is_good_state: A function to determine whether a bitstring represents a good state. - """ - self._is_good_state = is_good_state - - @property - def grover_operator(self) -> QuantumCircuit | None: - r"""Get the :math:`\mathcal{Q}` operator, or Grover operator. - - If the Grover operator is not set, we try to build it from the :math:`\mathcal{A}` operator - and `objective_qubits`. This only works if `objective_qubits` is a list of integers. - - Returns: - The Grover operator, or None if neither the Grover operator nor the - :math:`\mathcal{A}` operator is set. - """ - if self._grover_operator is None: - return GroverOperator(self.oracle, self.state_preparation) - return self._grover_operator - - @grover_operator.setter - def grover_operator(self, grover_operator: QuantumCircuit | None) -> None: - r"""Set the :math:`\mathcal{Q}` operator. - - If None, this operator is constructed from the ``oracle`` and ``state_preparation``. - - Args: - grover_operator: The new :math:`\mathcal{Q}` operator or None. - """ - self._grover_operator = grover_operator diff --git a/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py b/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py deleted file mode 100644 index 33ef90cb624e..000000000000 --- a/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py +++ /dev/null @@ -1,127 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. - -"""The interface for amplification algorithms and results.""" -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import Any - -import numpy as np - -from .amplification_problem import AmplificationProblem -from ..algorithm_result import AlgorithmResult - - -class AmplitudeAmplifier(ABC): - """The interface for amplification algorithms.""" - - @abstractmethod - def amplify(self, amplification_problem: AmplificationProblem) -> "AmplitudeAmplifierResult": - """Run the amplification algorithm. - - Args: - amplification_problem: The amplification problem. - - Returns: - The result as a ``AmplificationResult``, where e.g. the most likely state can be queried - as ``result.top_measurement``. - """ - raise NotImplementedError - - -class AmplitudeAmplifierResult(AlgorithmResult): - """The amplification result base class.""" - - def __init__(self) -> None: - super().__init__() - self._top_measurement: str | None = None - self._assignment = None - self._oracle_evaluation: bool | None = None - self._circuit_results: list[np.ndarray] | list[dict[str, int]] | None = None - self._max_probability: float | None = None - - @property - def top_measurement(self) -> str | None: - """The most frequently measured output as bitstring. - - Returns: - The most frequently measured output state. - """ - return self._top_measurement - - @top_measurement.setter - def top_measurement(self, value: str) -> None: - """Set the most frequently measured bitstring. - - Args: - value: A new value for the top measurement. - """ - self._top_measurement = value - - @property - def assignment(self) -> Any: - """The post-processed value of the most likely bitstring. - - Returns: - The output of the ``post_processing`` function of the respective - ``AmplificationProblem``, where the input is the ``top_measurement``. The type - is the same as the return type of the post-processing function. - """ - return self._assignment - - @assignment.setter - def assignment(self, value: Any) -> None: - """Set the value for the assignment. - - Args: - value: A new value for the assignment/solution. - """ - self._assignment = value - - @property - def oracle_evaluation(self) -> bool: - """Whether the classical oracle evaluation of the top measurement was True or False. - - Returns: - The classical oracle evaluation of the top measurement. - """ - return self._oracle_evaluation - - @oracle_evaluation.setter - def oracle_evaluation(self, value: bool) -> None: - """Set the classical oracle evaluation of the top measurement. - - Args: - value: A new value for the classical oracle evaluation. - """ - self._oracle_evaluation = value - - @property - def circuit_results(self) -> list[np.ndarray] | list[dict[str, int]] | None: - """Return the circuit results. Can be a statevector or counts dictionary.""" - return self._circuit_results - - @circuit_results.setter - def circuit_results(self, value: list[np.ndarray] | list[dict[str, int]]) -> None: - """Set the circuit results.""" - self._circuit_results = value - - @property - def max_probability(self) -> float: - """Return the maximum sampling probability.""" - return self._max_probability - - @max_probability.setter - def max_probability(self, value: float) -> None: - """Set the maximum sampling probability.""" - self._max_probability = value diff --git a/qiskit/algorithms/amplitude_amplifiers/grover.py b/qiskit/algorithms/amplitude_amplifiers/grover.py deleted file mode 100644 index be04929afa03..000000000000 --- a/qiskit/algorithms/amplitude_amplifiers/grover.py +++ /dev/null @@ -1,449 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""Grover's search algorithm.""" -from __future__ import annotations - -import itertools -import operator -import warnings -from collections.abc import Iterator, Generator -from typing import Any - -import numpy as np - -from qiskit import ClassicalRegister, QuantumCircuit -from qiskit.algorithms.exceptions import AlgorithmError -from qiskit.primitives import BaseSampler -from qiskit.providers import Backend -from qiskit.quantum_info import partial_trace, Statevector -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.utils.deprecation import deprecate_arg, deprecate_func - -from .amplification_problem import AmplificationProblem -from .amplitude_amplifier import AmplitudeAmplifier, AmplitudeAmplifierResult - - -class Grover(AmplitudeAmplifier): - r"""Grover's Search algorithm. - - .. note:: - - If you want to learn more about the theory behind Grover's Search algorithm, check - out the `Qiskit Textbook `_. - or the `Qiskit Tutorials - `_ - for more concrete how-to examples. - - Grover's Search [1, 2] is a well known quantum algorithm that can be used for - searching through unstructured collections of records for particular targets - with quadratic speedup compared to classical algorithms. - - Given a set :math:`X` of :math:`N` elements :math:`X=\{x_1,x_2,\ldots,x_N\}` - and a boolean function :math:`f : X \rightarrow \{0,1\}`, the goal of an - unstructured-search problem is to find an element :math:`x^* \in X` such - that :math:`f(x^*)=1`. - - The search is called *unstructured* because there are no guarantees as to how - the database is ordered. On a sorted database, for instance, one could perform - binary search to find an element in :math:`\mathbb{O}(\log N)` worst-case time. - Instead, in an unstructured-search problem, there is no prior knowledge about - the contents of the database. With classical circuits, there is no alternative - but to perform a linear number of queries to find the target element. - Conversely, Grover's Search algorithm allows to solve the unstructured-search - problem on a quantum computer in :math:`\mathcal{O}(\sqrt{N})` queries. - - To carry out this search a so-called oracle is required, that flags a good element/state. - The action of the oracle :math:`\mathcal{S}_f` is - - .. math:: - - \mathcal{S}_f |x\rangle = (-1)^{f(x)} |x\rangle, - - i.e. it flips the phase of the state :math:`|x\rangle` if :math:`x` is a hit. - The details of how :math:`S_f` works are unimportant to the algorithm; Grover's - search algorithm treats the oracle as a black box. - - This class supports oracles in form of a :class:`~qiskit.circuit.QuantumCircuit`. - - With the given oracle, Grover's Search constructs the Grover operator to amplify the - amplitudes of the good states: - - .. math:: - - \mathcal{Q} = H^{\otimes n} \mathcal{S}_0 H^{\otimes n} \mathcal{S}_f - = D \mathcal{S}_f, - - where :math:`\mathcal{S}_0` flips the phase of the all-zero state and acts as identity - on all other states. Sometimes the first three operands are summarized as diffusion operator, - which implements a reflection over the equal superposition state. - - If the number of solutions is known, we can calculate how often :math:`\mathcal{Q}` should be - applied to find a solution with very high probability, see the method - `optimal_num_iterations`. If the number of solutions is unknown, the algorithm tries different - powers of Grover's operator, see the `iterations` argument, and after each iteration checks - if a good state has been measured using `good_state`. - - The generalization of Grover's Search, Quantum Amplitude Amplification [3], uses a modified - version of :math:`\mathcal{Q}` where the diffusion operator does not reflect about the - equal superposition state, but another state specified via an operator :math:`\mathcal{A}`: - - .. math:: - - \mathcal{Q} = \mathcal{A} \mathcal{S}_0 \mathcal{A}^\dagger \mathcal{S}_f. - - For more information, see the :class:`~qiskit.circuit.library.GroverOperator` in the - circuit library. - - References: - [1]: L. K. Grover (1996), A fast quantum mechanical algorithm for database search, - `arXiv:quant-ph/9605043 `_. - [2]: I. Chuang & M. Nielsen, Quantum Computation and Quantum Information, - Cambridge: Cambridge University Press, 2000. Chapter 6.1.2. - [3]: Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). - Quantum Amplitude Amplification and Estimation. - `arXiv:quant-ph/0005055 `_. - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - iterations: list[int] | Iterator[int] | int | None = None, - growth_rate: float | None = None, - sample_from_iterations: bool = False, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - iterations: Specify the number of iterations/power of Grover's operator to be checked. - * If an int, only one circuit is run with that power of the Grover operator. - If the number of solutions is known, this option should be used with the optimal - power. The optimal power can be computed with ``Grover.optimal_num_iterations``. - * If a list, all the powers in the list are run in the specified order. - * If an iterator, the powers yielded by the iterator are checked, until a maximum - number of iterations or maximum power is reached. - * If ``None``, the :obj:`AmplificationProblem` provided must have an ``is_good_state``, - and circuits are run until that good state is reached. - growth_rate: If specified, the iterator is set to increasing powers of ``growth_rate``, - i.e. to ``int(growth_rate ** 1), int(growth_rate ** 2), ...`` until a maximum - number of iterations is reached. - sample_from_iterations: If True, instead of taking the values in ``iterations`` as - powers of the Grover operator, a random integer sample between 0 and smaller value - than the iteration is used as a power, see [1], Section 4. - quantum_instance: Deprecated: A Quantum Instance or Backend to run the circuits. - sampler: A Sampler to use for sampling the results of the circuits. - - Raises: - ValueError: If ``growth_rate`` is a float but not larger than 1. - ValueError: If both ``iterations`` and ``growth_rate`` is set. - - References: - [1]: Boyer et al., Tight bounds on quantum searching - ``_ - """ - # set default value - if growth_rate is None and iterations is None: - growth_rate = 1.2 - - if growth_rate is not None and iterations is not None: - raise ValueError("Pass either a value for iterations or growth_rate, not both.") - - if growth_rate is not None: - # yield iterations ** 1, iterations ** 2, etc. and casts to int - self._iterations: Generator[int, None, None] | list[int] = ( - int(growth_rate**x) for x in itertools.count(1) - ) - elif isinstance(iterations, int): - self._iterations = [iterations] - else: - self._iterations = iterations - - if quantum_instance is not None and sampler is not None: - raise ValueError("Only one of quantum_instance or sampler can be passed, not both!") - - # check positionally passing the sampler in the place of quantum_instance - # which will be removed in future - if isinstance(quantum_instance, BaseSampler): - sampler = quantum_instance - quantum_instance = None - - self._quantum_instance: QuantumInstance | None = None - if quantum_instance is not None: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - self.quantum_instance = quantum_instance - - self._sampler = sampler - - self._sample_from_iterations = sample_from_iterations - self._iterations_arg = iterations - - @property - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self) -> QuantumInstance | None: - r"""Deprecated. Get the quantum instance. - - Returns: - The quantum instance used to run this algorithm. - """ - return self._quantum_instance - - @quantum_instance.setter - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - r"""Deprecated. Set quantum instance. - - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - - @property - def sampler(self) -> BaseSampler | None: - """Get the sampler. - - Returns: - The sampler used to run this algorithm. - """ - return self._sampler - - @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: - """Set the sampler. - - Args: - sampler: The sampler used to run this algorithm. - """ - self._sampler = sampler - - def amplify(self, amplification_problem: AmplificationProblem) -> "GroverResult": - """Run the Grover algorithm. - - Args: - amplification_problem: The amplification problem. - - Returns: - The result as a ``GroverResult``, where e.g. the most likely state can be queried - as ``result.top_measurement``. - - Raises: - ValueError: If a quantum instance or sampler is not set. - AlgorithmError: If a sampler job fails. - TypeError: If ``is_good_state`` is not provided and is required (i.e. when iterations - is ``None`` or a ``list``) - """ - if self._sampler is None and self._quantum_instance is None: - raise ValueError("A quantum instance or sampler must be provided.") - - if self._quantum_instance is not None and self._sampler is not None: - raise ValueError("Only one of quantum_instance or sampler can be passed, not both!") - - if isinstance(self._iterations, list): - max_iterations = len(self._iterations) - max_power = np.inf # no cap on the power - iterator: Iterator[int] = iter(self._iterations) - else: - max_iterations = max(10, 2**amplification_problem.oracle.num_qubits) - max_power = np.ceil( - 2 ** (len(amplification_problem.grover_operator.reflection_qubits) / 2) - ) - iterator = self._iterations - - result = GroverResult() - - iterations = [] - top_measurement = "0" * len(amplification_problem.objective_qubits) - oracle_evaluation = False - all_circuit_results = [] - max_probability = 0 - shots = 0 - - for _ in range(max_iterations): # iterate at most to the max number of iterations - # get next power and check if allowed - power = next(iterator) - - if power > max_power: - break - - iterations.append(power) # store power - - # sample from [0, power) if specified - if self._sample_from_iterations: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - power = algorithm_globals.random.integers(power) - # Run a grover experiment for a given power of the Grover operator. - if self._sampler is not None: - qc = self.construct_circuit(amplification_problem, power, measurement=True) - job = self._sampler.run([qc]) - - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Sampler job failed.") from exc - - num_bits = len(amplification_problem.objective_qubits) - circuit_results: dict[str, Any] | Statevector | np.ndarray = { - np.binary_repr(k, num_bits): v for k, v in results.quasi_dists[0].items() - } - top_measurement, max_probability = max(circuit_results.items(), key=lambda x: x[1]) - - else: # use of else brach instead of elif as this seperates out the deprecated logic - if self._quantum_instance.is_statevector: - qc = self.construct_circuit(amplification_problem, power, measurement=False) - circuit_results = self._quantum_instance.execute(qc).get_statevector() - num_bits = len(amplification_problem.objective_qubits) - - # trace out work qubits - if qc.width() != num_bits: - indices = [ - i - for i in range(qc.num_qubits) - if i not in amplification_problem.objective_qubits - ] - rho = partial_trace(circuit_results, indices) - circuit_results = np.diag(rho.data) - - max_amplitude = max(circuit_results.max(), circuit_results.min(), key=abs) - max_amplitude_idx = np.where(circuit_results == max_amplitude)[0][0] - top_measurement = np.binary_repr(max_amplitude_idx, num_bits) - max_probability = np.abs(max_amplitude) ** 2 - shots = 1 - else: - qc = self.construct_circuit(amplification_problem, power, measurement=True) - circuit_results = self._quantum_instance.execute(qc).get_counts(qc) - top_measurement = max(circuit_results.items(), key=operator.itemgetter(1))[0] - shots = sum(circuit_results.values()) - max_probability = ( - max(circuit_results.items(), key=operator.itemgetter(1))[1] / shots - ) - - all_circuit_results.append(circuit_results) - - if (isinstance(self._iterations_arg, int)) and ( - amplification_problem.is_good_state is None - ): - oracle_evaluation = None # cannot check for good state without is_good_state arg - break - - # is_good_state arg must be provided if iterations arg is not an integer - if ( - self._iterations_arg is None or isinstance(self._iterations_arg, list) - ) and amplification_problem.is_good_state is None: - raise TypeError("An is_good_state function is required with the provided oracle") - - # only check if top measurement is a good state if an is_good_state arg is provided - oracle_evaluation = amplification_problem.is_good_state(top_measurement) - - if oracle_evaluation is True: - break # we found a solution - - result.iterations = iterations - result.top_measurement = top_measurement - result.assignment = amplification_problem.post_processing(top_measurement) - result.oracle_evaluation = oracle_evaluation - result.circuit_results = all_circuit_results - result.max_probability = max_probability - - return result - - @staticmethod - def optimal_num_iterations(num_solutions: int, num_qubits: int) -> int: - """Return the optimal number of iterations, if the number of solutions is known. - - Args: - num_solutions: The number of solutions. - num_qubits: The number of qubits used to encode the states. - - Returns: - The optimal number of iterations for Grover's algorithm to succeed. - """ - amplitude = np.sqrt(num_solutions / 2**num_qubits) - return round(np.arccos(amplitude) / (2 * np.arcsin(amplitude))) - - def construct_circuit( - self, problem: AmplificationProblem, power: int | None = None, measurement: bool = False - ) -> QuantumCircuit: - """Construct the circuit for Grover's algorithm with ``power`` Grover operators. - - Args: - problem: The amplification problem for the algorithm. - power: The number of times the Grover operator is repeated. If None, this argument - is set to the first item in ``iterations``. - measurement: Boolean flag to indicate if measurement should be included in the circuit. - - Returns: - QuantumCircuit: the QuantumCircuit object for the constructed circuit - - Raises: - ValueError: If no power is passed and the iterations are not an integer. - """ - if power is None: - if len(self._iterations) > 1: - raise ValueError("Please pass ``power`` if the iterations are not an integer.") - power = self._iterations[0] - - qc = QuantumCircuit(problem.oracle.num_qubits, name="Grover circuit") - qc.compose(problem.state_preparation, inplace=True) - if power > 0: - qc.compose(problem.grover_operator.power(power), inplace=True) - - if measurement: - measurement_cr = ClassicalRegister(len(problem.objective_qubits)) - qc.add_register(measurement_cr) - qc.measure(problem.objective_qubits, measurement_cr) - - return qc - - -class GroverResult(AmplitudeAmplifierResult): - """Grover Result.""" - - def __init__(self) -> None: - super().__init__() - self._iterations: list[int] | None = None - - @property - def iterations(self) -> list[int]: - """All the powers of the Grover operator that have been tried. - - Returns: - The powers of the Grover operator tested. - """ - return self._iterations - - @iterations.setter - def iterations(self, value: list[int]) -> None: - """Set the powers of the Grover operator that have been tried. - - Args: - value: A new value for the powers. - """ - self._iterations = value diff --git a/qiskit/algorithms/amplitude_estimators/__init__.py b/qiskit/algorithms/amplitude_estimators/__init__.py deleted file mode 100644 index 764f8863857d..000000000000 --- a/qiskit/algorithms/amplitude_estimators/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""The Amplitude Estimators package.""" - -from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult -from .ae import AmplitudeEstimation, AmplitudeEstimationResult -from .fae import FasterAmplitudeEstimation, FasterAmplitudeEstimationResult -from .iae import IterativeAmplitudeEstimation, IterativeAmplitudeEstimationResult -from .mlae import MaximumLikelihoodAmplitudeEstimation, MaximumLikelihoodAmplitudeEstimationResult -from .estimation_problem import EstimationProblem - -__all__ = [ - "AmplitudeEstimator", - "AmplitudeEstimatorResult", - "AmplitudeEstimation", - "AmplitudeEstimationResult", - "FasterAmplitudeEstimation", - "FasterAmplitudeEstimationResult", - "IterativeAmplitudeEstimation", - "IterativeAmplitudeEstimationResult", - "MaximumLikelihoodAmplitudeEstimation", - "MaximumLikelihoodAmplitudeEstimationResult", - "EstimationProblem", -] diff --git a/qiskit/algorithms/amplitude_estimators/ae.py b/qiskit/algorithms/amplitude_estimators/ae.py deleted file mode 100644 index c454cb18793b..000000000000 --- a/qiskit/algorithms/amplitude_estimators/ae.py +++ /dev/null @@ -1,688 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""The Quantum Phase Estimation-based Amplitude Estimation algorithm.""" - -from __future__ import annotations -from collections import OrderedDict -import warnings -import numpy as np -from scipy.stats import chi2, norm -from scipy.optimize import bisect - -from qiskit import QuantumCircuit, ClassicalRegister -from qiskit.providers import Backend -from qiskit.primitives import BaseSampler -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg, deprecate_func -from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult -from .ae_utils import pdf_a, derivative_log_pdf_a, bisect_max -from .estimation_problem import EstimationProblem -from ..exceptions import AlgorithmError - - -class AmplitudeEstimation(AmplitudeEstimator): - r"""The Quantum Phase Estimation-based Amplitude Estimation algorithm. - - This class implements the original Quantum Amplitude Estimation (QAE) algorithm, introduced by - [1]. This canonical version uses quantum phase estimation along with a set of :math:`m` - additional evaluation qubits to find an estimate :math:`\tilde{a}`, that is restricted to the - grid - - .. math:: - - \tilde{a} \in \{\sin^2(\pi y / 2^m) : y = 0, ..., 2^{m-1}\} - - More evaluation qubits produce a finer sampling grid, therefore the accuracy of the algorithm - increases with :math:`m`. - - Using a maximum likelihood post processing, this grid constraint can be circumvented. - This improved estimator is implemented as well, see [2] Appendix A for more detail. - - .. note:: - - This class does not support the :attr:`.EstimationProblem.is_good_state` property, - as for phase estimation-based QAE, the oracle that identifes the good states - must be encoded in the Grover operator. To set custom oracles, the - :attr:`.EstimationProblem.grover_operator` attribute can be set directly. - - References: - [1]: Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). - Quantum Amplitude Amplification and Estimation. - `arXiv:quant-ph/0005055 `_. - [2]: Grinko, D., Gacon, J., Zoufal, C., & Woerner, S. (2019). - Iterative Quantum Amplitude Estimation. - `arXiv:1912.05559 `_. - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - num_eval_qubits: int, - phase_estimation_circuit: QuantumCircuit | None = None, - iqft: QuantumCircuit | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - num_eval_qubits: The number of evaluation qubits. - phase_estimation_circuit: The phase estimation circuit used to run the algorithm. - Defaults to the standard phase estimation circuit from the circuit library, - `qiskit.circuit.library.PhaseEstimation` when None. - iqft: The inverse quantum Fourier transform component, defaults to using a standard - implementation from `qiskit.circuit.library.QFT` when None. - quantum_instance: Deprecated: The backend (or `QuantumInstance`) to execute - the circuits on. - sampler: A sampler primitive to evaluate the circuits. - - Raises: - ValueError: If the number of evaluation qubits is smaller than 1. - """ - if num_eval_qubits < 1: - raise ValueError("The number of evaluation qubits must at least be 1.") - - super().__init__() - - # set quantum instance - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - self.quantum_instance = quantum_instance - - # get parameters - self._m = num_eval_qubits - self._M = 2**num_eval_qubits # pylint: disable=invalid-name - - self._iqft = iqft - self._pec = phase_estimation_circuit - self._sampler = sampler - - @property - def sampler(self) -> BaseSampler | None: - """Get the sampler primitive. - - Returns: - The sampler primitive to evaluate the circuits. - """ - return self._sampler - - @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: - """Set sampler primitive. - - Args: - sampler: A sampler primitive to evaluate the circuits. - """ - self._sampler = sampler - - @property - @deprecate_func( - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - since="0.24.0", - is_property=True, - ) - def quantum_instance(self) -> QuantumInstance | None: - """Deprecated: Get the quantum instance. - - Returns: - The quantum instance used to run this algorithm. - """ - return self._quantum_instance - - @quantum_instance.setter - @deprecate_func( - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - since="0.24.0", - is_property=True, - ) - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Deprecated: Set quantum instance. - - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - - def construct_circuit( - self, estimation_problem: EstimationProblem, measurement: bool = False - ) -> QuantumCircuit: - """Construct the Amplitude Estimation quantum circuit. - - Args: - estimation_problem: The estimation problem for which to construct the QAE circuit. - measurement: Boolean flag to indicate if measurements should be included in the circuit. - - Returns: - The QuantumCircuit object for the constructed circuit. - """ - # use custom Phase Estimation circuit if provided - if self._pec is not None: - pec = self._pec - - # otherwise use the circuit library -- note that this does not include the A operator - else: - from qiskit.circuit.library import PhaseEstimation - - pec = PhaseEstimation(self._m, estimation_problem.grover_operator, iqft=self._iqft) - - # combine the Phase Estimation circuit with the A operator - circuit = QuantumCircuit(*pec.qregs) - circuit.compose( - estimation_problem.state_preparation, - list(range(self._m, circuit.num_qubits)), - inplace=True, - ) - circuit.compose(pec, inplace=True) - - # add measurements if necessary - if measurement: - cr = ClassicalRegister(self._m) - circuit.add_register(cr) - circuit.measure(list(range(self._m)), list(range(self._m))) - - return circuit - - def evaluate_measurements( - self, - circuit_results: dict[str, int] | np.ndarray, - threshold: float = 1e-6, - ) -> tuple[dict[float, float], dict[int, float]]: - """Evaluate the results from the circuit simulation. - - Given the probabilities from statevector simulation of the QAE circuit, compute the - probabilities that the measurements y/gridpoints a are the best estimate. - - Args: - circuit_results: The circuit result from the QAE circuit. Can be either a counts dict - or a statevector or a quasi-probabilities dict. - threshold: Measurements with probabilities below the threshold are discarded. - - Returns: - Dictionaries containing the a gridpoints with respective probabilities and - y measurements with respective probabilities, in this order. - """ - # compute grid sample and measurement dicts - if isinstance(circuit_results, dict): - if set(map(type, circuit_results.values())) == {int}: - samples, measurements = self._evaluate_count_results(circuit_results) - else: - samples, measurements = self._evaluate_quasi_probabilities_results(circuit_results) - else: - samples, measurements = self._evaluate_statevector_results(circuit_results) - - # cutoff probabilities below the threshold - samples = {a: p for a, p in samples.items() if p > threshold} - measurements = {y: p for y, p in measurements.items() if p > threshold} - - return samples, measurements - - def _evaluate_statevector_results(self, statevector): - # map measured results to estimates - measurements = OrderedDict() # type: OrderedDict - num_qubits = int(np.log2(len(statevector))) - for i, amplitude in enumerate(statevector): - b = bin(i)[2:].zfill(num_qubits)[::-1] - y = int(b[: self._m], 2) # chop off all except the evaluation qubits - measurements[y] = measurements.get(y, 0) + np.abs(amplitude) ** 2 - - samples = OrderedDict() # type: OrderedDict - for y, probability in measurements.items(): - if y >= int(self._M / 2): - y = self._M - y - # due to the finite accuracy of the sine, we round the result to 7 decimals - a = np.round(np.power(np.sin(y * np.pi / 2**self._m), 2), decimals=7) - samples[a] = samples.get(a, 0) + probability - - return samples, measurements - - def _evaluate_quasi_probabilities_results(self, circuit_results): - # construct probabilities - measurements = OrderedDict() - samples = OrderedDict() - for state, probability in circuit_results.items(): - # reverts the last _m items - y = int(state[: -self._m - 1 : -1], 2) - measurements[y] = probability - a = np.round(np.power(np.sin(y * np.pi / 2**self._m), 2), decimals=7) - samples[a] = samples.get(a, 0.0) + probability - - return samples, measurements - - def _evaluate_count_results(self, counts) -> tuple[dict[float, float], dict[int, float]]: - # construct probabilities - measurements: dict[int, float] = OrderedDict() - samples: dict[float, float] = OrderedDict() - shots = sum(counts.values()) - for state, count in counts.items(): - y = int(state.replace(" ", "")[: self._m][::-1], 2) - probability = count / shots - measurements[y] = probability - a = np.round(np.power(np.sin(y * np.pi / 2**self._m), 2), decimals=7) - samples[a] = samples.get(a, 0.0) + probability - - return samples, measurements - - @staticmethod - def compute_mle( - result: "AmplitudeEstimationResult", apply_post_processing: bool = False - ) -> float: - """Compute the Maximum Likelihood Estimator (MLE). - - Args: - result: An amplitude estimation result object. - apply_post_processing: If True, apply the post processing to the MLE before returning - it. - - Returns: - The MLE for the provided result object. - """ - m = result.num_evaluation_qubits - M = 2**m # pylint: disable=invalid-name - qae = result.estimation - - # likelihood function - a_i = np.asarray(list(result.samples.keys())) - p_i = np.asarray(list(result.samples.values())) - - def loglikelihood(a): - return np.sum(result.shots * p_i * np.log(pdf_a(a_i, a, m))) - - # y is pretty much an integer, but to map 1.9999 to 2 we must first - # use round and then int conversion - y = int(np.round(M * np.arcsin(np.sqrt(qae)) / np.pi)) - - # Compute the two intervals in which are candidates for containing - # the maximum of the log-likelihood function: the two bubbles next to - # the QAE estimate - if y == 0: - right_of_qae = np.sin(np.pi * (y + 1) / M) ** 2 - bubbles = [qae, right_of_qae] - - elif y == int(M / 2): # remember, M = 2^m is a power of 2 - left_of_qae = np.sin(np.pi * (y - 1) / M) ** 2 - bubbles = [left_of_qae, qae] - - else: - left_of_qae = np.sin(np.pi * (y - 1) / M) ** 2 - right_of_qae = np.sin(np.pi * (y + 1) / M) ** 2 - bubbles = [left_of_qae, qae, right_of_qae] - - # Find global maximum amongst the two local maxima - a_opt = qae - loglik_opt = loglikelihood(a_opt) - for a, b in zip(bubbles[:-1], bubbles[1:]): - locmax, val = bisect_max(loglikelihood, a, b, retval=True) - if val > loglik_opt: - a_opt = locmax - loglik_opt = val - - if apply_post_processing: - return result.post_processing(a_opt) - - return a_opt - - def estimate(self, estimation_problem: EstimationProblem) -> "AmplitudeEstimationResult": - """Run the amplitude estimation algorithm on provided estimation problem. - - Args: - estimation_problem: The estimation problem. - - Returns: - An amplitude estimation results object. - - Raises: - ValueError: If `state_preparation` or `objective_qubits` are not set in the - `estimation_problem`. - ValueError: A quantum instance or sampler must be provided. - AlgorithmError: Sampler job run error. - """ - # check if A factory or state_preparation has been set - if estimation_problem.state_preparation is None: - raise ValueError( - "The state_preparation property of the estimation problem must be set." - ) - if self._quantum_instance is None and self._sampler is None: - raise ValueError("A quantum instance or sampler must be provided.") - - if estimation_problem.objective_qubits is None: - raise ValueError("The objective_qubits property of the estimation problem must be set.") - - if estimation_problem.has_good_state: - warnings.warn( - "The AmplitudeEstimation class does not support an is_good_state function to " - "identify good states. For this algorithm, a custom oracle has to be encoded directly " - "in the grover_operator. If no custom oracle is set, this algorithm identifies good " - "states as those, where all objective qubits are in state 1." - ) - - result = AmplitudeEstimationResult() - result.num_evaluation_qubits = self._m - result.post_processing = estimation_problem.post_processing - - shots = 0 - if self._quantum_instance is not None and self._quantum_instance.is_statevector: - circuit = self.construct_circuit(estimation_problem, measurement=False) - # run circuit on statevector simulator - statevector = self._quantum_instance.execute(circuit).get_statevector() - result.circuit_results = statevector - # store number of shots: convention is 1 shot for statevector, - # needed so that MLE works! - shots = 1 - else: - circuit = self.construct_circuit(estimation_problem, measurement=True) - if self._quantum_instance is not None: - # run circuit on QASM simulator - result.circuit_results = self._quantum_instance.execute(circuit).get_counts() - shots = sum(result.circuit_results.values()) - else: - try: - job = self._sampler.run([circuit]) - ret = job.result() - except Exception as exc: - raise AlgorithmError("The job was not completed successfully. ") from exc - - shots = ret.metadata[0].get("shots") - if shots is None: - result.circuit_results = ret.quasi_dists[0].binary_probabilities() - shots = 1 - else: - result.circuit_results = { - k: round(v * shots) - for k, v in ret.quasi_dists[0].binary_probabilities().items() - } - - # store shots - result.shots = shots - samples, measurements = self.evaluate_measurements(result.circuit_results) - - result.samples = samples - result.samples_processed = { - estimation_problem.post_processing(a): p for a, p in samples.items() - } - result.measurements = measurements - - # determine the most likely estimate - result.max_probability = 0 - for amplitude, (mapped, prob) in zip(samples.keys(), result.samples_processed.items()): - if prob > result.max_probability: - result.max_probability = prob - result.estimation = amplitude - result.estimation_processed = mapped - - # store the number of oracle queries - result.num_oracle_queries = result.shots * (self._M - 1) - - # run the MLE post-processing - mle = self.compute_mle(result) - result.mle = mle - result.mle_processed = estimation_problem.post_processing(mle) - - result.confidence_interval = self.compute_confidence_interval(result) - result.confidence_interval_processed = tuple( - estimation_problem.post_processing(value) for value in result.confidence_interval - ) - - return result - - @staticmethod - def compute_confidence_interval( - result: "AmplitudeEstimationResult", alpha: float = 0.05, kind: str = "likelihood_ratio" - ) -> tuple[float, float]: - """Compute the (1 - alpha) confidence interval. - - Args: - result: An amplitude estimation result for which to compute the confidence interval. - alpha: Confidence level: compute the (1 - alpha) confidence interval. - kind: The method to compute the confidence interval, can be 'fisher', 'observed_fisher' - or 'likelihood_ratio' (default) - - Returns: - The (1 - alpha) confidence interval of the specified kind. - - Raises: - NotImplementedError: If the confidence interval method `kind` is not implemented. - """ - # if statevector simulator the estimate is exact - if isinstance(result.circuit_results, (list, np.ndarray)): - return (result.mle, result.mle) - - if kind in ["likelihood_ratio", "lr"]: - return _likelihood_ratio_confint(result, alpha) - - if kind in ["fisher", "fi"]: - return _fisher_confint(result, alpha, observed=False) - - if kind in ["observed_fisher", "observed_information", "oi"]: - return _fisher_confint(result, alpha, observed=True) - - raise NotImplementedError(f"CI `{kind}` is not implemented.") - - -class AmplitudeEstimationResult(AmplitudeEstimatorResult): - """The ``AmplitudeEstimation`` result object.""" - - def __init__(self) -> None: - super().__init__() - self._num_evaluation_qubits: int | None = None - self._mle: float | None = None - self._mle_processed: float | None = None - self._samples: dict[float, float] | None = None - self._samples_processed: dict[float, float] | None = None - self._y_measurements: dict[int, float] | None = None - self._max_probability: float | None = None - - @property - def num_evaluation_qubits(self) -> int: - """Returns the number of evaluation qubits.""" - return self._num_evaluation_qubits - - @num_evaluation_qubits.setter - def num_evaluation_qubits(self, num_evaluation_qubits: int) -> None: - """Set the number of evaluation qubits.""" - self._num_evaluation_qubits = num_evaluation_qubits - - @property - def mle_processed(self) -> float: - """Return the post-processed MLE for the amplitude.""" - return self._mle_processed - - @mle_processed.setter - def mle_processed(self, value: float) -> None: - """Set the post-processed MLE for the amplitude.""" - self._mle_processed = value - - @property - def samples_processed(self) -> dict[float, float]: - """Return the post-processed measurement samples with their measurement probability.""" - return self._samples_processed - - @samples_processed.setter - def samples_processed(self, value: dict[float, float]) -> None: - """Set the post-processed measurement samples.""" - self._samples_processed = value - - @property - def mle(self) -> float: - r"""Return the MLE for the amplitude, in $[0, 1]$.""" - return self._mle - - @mle.setter - def mle(self, value: float) -> None: - r"""Set the MLE for the amplitude, in $[0, 1]$.""" - self._mle = value - - @property - def samples(self) -> dict[float, float]: - """Return the measurement samples with their measurement probability.""" - return self._samples - - @samples.setter - def samples(self, value: dict[float, float]) -> None: - """Set the measurement samples with their measurement probability.""" - self._samples = value - - @property - def measurements(self) -> dict[int, float]: - """Return the measurements as integers with their measurement probability.""" - return self._y_measurements - - @measurements.setter - def measurements(self, value: dict[int, float]) -> None: - """Set the measurements as integers with their measurement probability.""" - self._y_measurements = value - - @property - def max_probability(self) -> float: - """Return the maximum sampling probability.""" - return self._max_probability - - @max_probability.setter - def max_probability(self, value: float) -> None: - """Set the maximum sampling probability.""" - self._max_probability = value - - -def _compute_fisher_information(result: AmplitudeEstimationResult, observed: bool = False) -> float: - """Computes the Fisher information for the output of the previous run. - - Args: - result: An amplitude estimation result for which to compute the confidence interval. - observed: If True, the observed Fisher information is returned, otherwise - the expected Fisher information. - - Returns: - The Fisher information. - """ - fisher_information = None - mlv = result.mle # MLE in [0,1] - m = result.num_evaluation_qubits - M = 2**m # pylint: disable=invalid-name - - if observed: - a_i = np.asarray(list(result.samples.keys())) - p_i = np.asarray(list(result.samples.values())) - - # Calculate the observed Fisher information - fisher_information = sum(p * derivative_log_pdf_a(a, mlv, m) ** 2 for p, a in zip(p_i, a_i)) - else: - - def integrand(x): - return (derivative_log_pdf_a(x, mlv, m)) ** 2 * pdf_a(x, mlv, m) - - grid = np.sin(np.pi * np.arange(M / 2 + 1) / M) ** 2 - fisher_information = sum(integrand(x) for x in grid) - - return fisher_information - - -def _fisher_confint( - result: AmplitudeEstimationResult, alpha: float, observed: bool = False -) -> tuple[float, float]: - """Compute the Fisher information confidence interval for the MLE of the previous run. - - Args: - result: An amplitude estimation result for which to compute the confidence interval. - alpha: Specifies the (1 - alpha) confidence level (0 < alpha < 1). - observed: If True, the observed Fisher information is used to construct the - confidence interval, otherwise the expected Fisher information. - - Returns: - The Fisher information confidence interval. - """ - # approximate the standard deviation of the MLE and construct the confidence interval - std = np.sqrt(result.shots * _compute_fisher_information(result, observed)) - confint = result.mle + norm.ppf(1 - alpha / 2) / std * np.array([-1, 1]) - - # transform the confidence interval from [0, 1] to the target interval - return result.post_processing(confint[0]), result.post_processing(confint[1]) - - -def _likelihood_ratio_confint( - result: AmplitudeEstimationResult, alpha: float -) -> tuple[float, float]: - """Compute the likelihood ratio confidence interval for the MLE of the previous run. - - Args: - result: An amplitude estimation result for which to compute the confidence interval. - alpha: Specifies the (1 - alpha) confidence level (0 < alpha < 1). - - Returns: - The likelihood ratio confidence interval. - """ - # Compute the two intervals in which we the look for values above - # the likelihood ratio: the two bubbles next to the QAE estimate - m = result.num_evaluation_qubits - M = 2**m # pylint: disable=invalid-name - qae = result.estimation - - y = int(np.round(M * np.arcsin(np.sqrt(qae)) / np.pi)) - if y == 0: - right_of_qae = np.sin(np.pi * (y + 1) / M) ** 2 - bubbles = [qae, right_of_qae] - - elif y == int(M / 2): # remember, M = 2^m is a power of 2 - left_of_qae = np.sin(np.pi * (y - 1) / M) ** 2 - bubbles = [left_of_qae, qae] - - else: - left_of_qae = np.sin(np.pi * (y - 1) / M) ** 2 - right_of_qae = np.sin(np.pi * (y + 1) / M) ** 2 - bubbles = [left_of_qae, qae, right_of_qae] - - # likelihood function - a_i = np.asarray(list(result.samples.keys())) - p_i = np.asarray(list(result.samples.values())) - - def loglikelihood(a): - return np.sum(result.shots * p_i * np.log(pdf_a(a_i, a, m))) - - # The threshold above which the likelihoods are in the - # confidence interval - loglik_mle = loglikelihood(result.mle) - thres = loglik_mle - chi2.ppf(1 - alpha, df=1) / 2 - - def cut(x): - return loglikelihood(x) - thres - - # Store the boundaries of the confidence interval - # It's valid to start off with the zero-width confidence interval, since the maximum - # of the likelihood function is guaranteed to be over the threshold, and if alpha = 0 - # that's the valid interval - lower = upper = result.mle - - # Check the two intervals/bubbles: check if they surpass the - # threshold and if yes add the part that does to the CI - for a, b in zip(bubbles[:-1], bubbles[1:]): - # Compute local maximum and perform a bisect search between - # the local maximum and the bubble boundaries - locmax, val = bisect_max(loglikelihood, a, b, retval=True) - if val >= thres: - # Bisect pre-condition is that the function has different - # signs at the boundaries of the interval we search in - if cut(a) * cut(locmax) < 0: - left = bisect(cut, a, locmax) - lower = np.minimum(lower, left) - if cut(locmax) * cut(b) < 0: - right = bisect(cut, locmax, b) - upper = np.maximum(upper, right) - - # Put together CI - return result.post_processing(lower), result.post_processing(upper) diff --git a/qiskit/algorithms/amplitude_estimators/ae_utils.py b/qiskit/algorithms/amplitude_estimators/ae_utils.py deleted file mode 100644 index bd695be45a63..000000000000 --- a/qiskit/algorithms/amplitude_estimators/ae_utils.py +++ /dev/null @@ -1,258 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Utils for the Maximum-Likelihood estimation used in ``AmplitudeEstimation``.""" - -import logging -import numpy as np - -logger = logging.getLogger(__name__) - -# pylint: disable=invalid-name - - -def bisect_max(f, a, b, steps=50, minwidth=1e-12, retval=False): - """Find the maximum of the real-valued function f in the interval [a, b] using bisection. - - Args: - f (callable): the function to find the maximum of - a (float): the lower limit of the interval - b (float): the upper limit of the interval - steps (int): the maximum number of steps in the bisection - minwidth (float): if the current interval is smaller than minwidth stop - the search - retval (bool): return value - - Returns: - float: The maximum of f in [a,b] according to this algorithm. - """ - it = 0 - m = (a + b) / 2 - fm = 0 - while it < steps and b - a > minwidth: - l, r = (a + m) / 2, (m + b) / 2 - fl, fm, fr = f(l), f(m), f(r) - - # fl is the maximum - if fl > fm and fl > fr: - b = m - m = l - # fr is the maximum - elif fr > fm and fr > fl: - a = m - m = r - # fm is the maximum - else: - a = l - b = r - - it += 1 - - if it == steps: - logger.warning("-- Warning, bisect_max didn't converge after %s steps", steps) - - if retval: - return m, fm - - return m - - -def _circ_dist(x, p): - r"""Circumferential distance function. - - For two angles :math:`x` and :math:`p` on the unit circuit this function is defined as - - .. math:: - - d(x, p) = \min_{z \in [-1, 0, 1]} |z + p - x| - - Args: - x (float): first angle - p (float): second angle - - Returns: - float: d(x, p) - """ - t = p - x - # Since x and p \in [0,1] it suffices to check not all integers - # but only -1, 0 and 1 - z = np.array([-1, 0, 1]) - - if hasattr(t, "__len__"): - d = np.empty_like(t) - for idx, ti in enumerate(t): - d[idx] = np.min(np.abs(z + ti)) - return d - - return np.min(np.abs(z + t)) - - -def _derivative_circ_dist(x, p): - """Derivative of circumferential distance function. - - Args: - x (float): first angle - p (float): second angle - - Returns: - float: The derivative. - """ - # pylint: disable=chained-comparison,misplaced-comparison-constant - t = p - x - if t < -0.5 or (0 < t and t < 0.5): - return -1 - if t > 0.5 or (-0.5 < t and t < 0): - return 1 - return 0 - - -def _amplitude_to_angle(a): - r"""Transform from the amplitude :math:`a \in [0, 1]` to the generating angle. - - In QAE, the amplitude can be written from a generating angle :math:`\omega` as - - .. math: - - a = \sin^2(\pi \omega) - - This returns the :math:`\omega` for a given :math:`a`. - - Args: - a (float): A value in :math:`[0,1]`. - - Returns: - float: :math:`\sin^{-1}(\sqrt{a}) / \pi` - """ - return np.arcsin(np.sqrt(a)) / np.pi - - -def _derivative_amplitude_to_angle(a): - """Compute the derivative of ``amplitude_to_angle``.""" - return 1 / (2 * np.pi * np.sqrt((1 - a) * a)) - - -def _alpha(x, p): - """Helper function for `pdf_a`, alpha = pi * d(omega(x), omega(p)). - - Here, omega(x) is `_amplitude_to_angle(x)`. - """ - omega = _amplitude_to_angle - return np.pi * _circ_dist(omega(x), omega(p)) - - -def _derivative_alpha(x, p): - """Compute the derivative of alpha.""" - omega = _amplitude_to_angle - d_omega = _derivative_amplitude_to_angle - return np.pi * _derivative_circ_dist(omega(x), omega(p)) * d_omega(p) - - -def _beta(x, p): - """Helper function for `pdf_a`, beta = pi * d(1 - omega(x), omega(p)).""" - omega = _amplitude_to_angle - return np.pi * _circ_dist(1 - omega(x), omega(p)) - - -def _derivative_beta(x, p): - """Compute the derivative of beta.""" - omega = _amplitude_to_angle - d_omega = _derivative_amplitude_to_angle - return np.pi * _derivative_circ_dist(1 - omega(x), omega(p)) * d_omega(p) - - -def _pdf_a_single_angle(x, p, m, pi_delta): - """Helper function for `pdf_a`.""" - M = 2**m - - d = pi_delta(x, p) - res = np.sin(M * d) ** 2 / (M * np.sin(d)) ** 2 if d != 0 else 1 - - return res - - -def pdf_a(x, p, m): - """ - Return the PDF of a, i.e. the probability of getting the estimate x - (in [0, 1]) if p (in [0, 1]) is the true value, given that we use m qubits. - - Args: - x (float): the grid point - p (float): the true value - m (float): the number of evaluation qubits - - Returns: - float: PDF(x|p) - """ - # We'll use list comprehension, so the input should be a list - scalar = False - if not hasattr(x, "__len__"): - scalar = True - x = np.asarray([x]) - - # Compute the probabilities: Add up both angles that produce the given - # value, except for the angles 0 and 0.5, which map to the unique a-values, - # 0 and 1, respectively - pr = np.array( - [ - _pdf_a_single_angle(xi, p, m, _alpha) + _pdf_a_single_angle(xi, p, m, _beta) - if (xi not in [0, 1]) - else _pdf_a_single_angle(xi, p, m, _alpha) - for xi in x - ] - ).flatten() - - # If is was a scalar return scalar otherwise the array - return pr[0] if scalar else pr - - -def derivative_log_pdf_a(x, p, m): - """ - Return the derivative of the logarithm of the PDF of a. - - Args: - x (float): the grid point - p (float): the true value - m (float): the number of evaluation qubits - - Returns: - float: d/dp log(PDF(x|p)) - """ - M = 2**m - - if x not in [0, 1]: - num_p1 = 0 - for A, dA, B, dB in zip( - [_alpha, _beta], - [_derivative_alpha, _derivative_beta], - [_beta, _alpha], - [_derivative_beta, _derivative_alpha], - ): - num_p1 += 2 * M * np.sin(M * A(x, p)) * np.cos(M * A(x, p)) * dA(x, p) * np.sin( - B(x, p) - ) ** 2 + 2 * np.sin(M * A(x, p)) ** 2 * np.sin(B(x, p)) * np.cos(B(x, p)) * dB(x, p) - - den_p1 = ( - np.sin(M * _alpha(x, p)) ** 2 * np.sin(_beta(x, p)) ** 2 - + np.sin(M * _beta(x, p)) ** 2 * np.sin(_alpha(x, p)) ** 2 - ) - - num_p2 = 0 - for A, dA, B in zip( - [_alpha, _beta], [_derivative_alpha, _derivative_beta], [_beta, _alpha] - ): - num_p2 += 2 * np.cos(A(x, p)) * dA(x, p) * np.sin(B(x, p)) - - den_p2 = np.sin(_alpha(x, p)) * np.sin(_beta(x, p)) - - return num_p1 / den_p1 - num_p2 / den_p2 - - return 2 * _derivative_alpha(x, p) * (M / np.tan(M * _alpha(x, p)) - 1 / np.tan(_alpha(x, p))) diff --git a/qiskit/algorithms/amplitude_estimators/amplitude_estimator.py b/qiskit/algorithms/amplitude_estimators/amplitude_estimator.py deleted file mode 100644 index 613827c6ccd6..000000000000 --- a/qiskit/algorithms/amplitude_estimators/amplitude_estimator.py +++ /dev/null @@ -1,131 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""The Amplitude Estimation interface.""" - -from __future__ import annotations -from abc import abstractmethod, ABC -from collections.abc import Callable - -import numpy as np - -from .estimation_problem import EstimationProblem -from ..algorithm_result import AlgorithmResult - - -class AmplitudeEstimator(ABC): - """The Amplitude Estimation interface.""" - - @abstractmethod - def estimate(self, estimation_problem: EstimationProblem) -> "AmplitudeEstimatorResult": - """Run the amplitude estimation algorithm. - - Args: - estimation_problem: An ``EstimationProblem`` containing all problem-relevant information - such as the state preparation and the objective qubits. - """ - raise NotImplementedError - - -class AmplitudeEstimatorResult(AlgorithmResult): - """The results object for amplitude estimation algorithms.""" - - def __init__(self) -> None: - super().__init__() - self._circuit_results: np.ndarray | dict[str, int] | None = None - self._shots: int | None = None - self._estimation: float | None = None - self._estimation_processed: float | None = None - self._num_oracle_queries: int | None = None - self._post_processing: Callable[[float], float] | None = None - self._confidence_interval: tuple[float, float] | None = None - self._confidence_interval_processed: tuple[float, float] | None = None - - @property - def circuit_results(self) -> np.ndarray | dict[str, int] | None: - """Return the circuit results. Can be a statevector or counts dictionary.""" - return self._circuit_results - - @circuit_results.setter - def circuit_results(self, value: np.ndarray | dict[str, int]) -> None: - """Set the circuit results.""" - self._circuit_results = value - - @property - def shots(self) -> int: - """Return the number of shots used. Is 1 for statevector-based simulations.""" - return self._shots - - @shots.setter - def shots(self, value: int) -> None: - """Set the number of shots used.""" - self._shots = value - - @property - def estimation(self) -> float: - r"""Return the estimation for the amplitude in :math:`[0, 1]`.""" - return self._estimation - - @estimation.setter - def estimation(self, value: float) -> None: - r"""Set the estimation for the amplitude in :math:`[0, 1]`.""" - self._estimation = value - - @property - def estimation_processed(self) -> float: - """Return the estimation for the amplitude after the post-processing has been applied.""" - return self._estimation_processed - - @estimation_processed.setter - def estimation_processed(self, value: float) -> None: - """Set the estimation for the amplitude after the post-processing has been applied.""" - self._estimation_processed = value - - @property - def num_oracle_queries(self) -> int: - """Return the number of Grover oracle queries.""" - return self._num_oracle_queries - - @num_oracle_queries.setter - def num_oracle_queries(self, value: int) -> None: - """Set the number of Grover oracle queries.""" - self._num_oracle_queries = value - - @property - def post_processing(self) -> Callable[[float], float]: - """Return a handle to the post processing function.""" - return self._post_processing - - @post_processing.setter - def post_processing(self, post_processing: Callable[[float], float]) -> None: - """Set a handle to the post processing function.""" - self._post_processing = post_processing - - @property - def confidence_interval(self) -> tuple[float, float]: - """Return the confidence interval for the amplitude (95% interval by default).""" - return self._confidence_interval - - @confidence_interval.setter - def confidence_interval(self, confidence_interval: tuple[float, float]) -> None: - """Set the confidence interval for the amplitude (95% interval by default).""" - self._confidence_interval = confidence_interval - - @property - def confidence_interval_processed(self) -> tuple[float, float]: - """Return the post-processed confidence interval (95% interval by default).""" - return self._confidence_interval_processed - - @confidence_interval_processed.setter - def confidence_interval_processed(self, confidence_interval: tuple[float, float]) -> None: - """Set the post-processed confidence interval (95% interval by default).""" - self._confidence_interval_processed = confidence_interval diff --git a/qiskit/algorithms/amplitude_estimators/estimation_problem.py b/qiskit/algorithms/amplitude_estimators/estimation_problem.py deleted file mode 100644 index 72e9418f72d6..000000000000 --- a/qiskit/algorithms/amplitude_estimators/estimation_problem.py +++ /dev/null @@ -1,274 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""The Estimation problem class.""" - -from __future__ import annotations -import warnings -from collections.abc import Callable - -import numpy - -from qiskit.circuit import QuantumCircuit, QuantumRegister -from qiskit.circuit.library import GroverOperator - - -class EstimationProblem: - """The estimation problem is the input to amplitude estimation algorithm. - - This class contains all problem-specific information required to run an amplitude estimation - algorithm. That means, it minimally contains the state preparation and the specification - of the good state. It can further hold some post processing on the estimation of the amplitude - or a custom Grover operator. - """ - - def __init__( - self, - state_preparation: QuantumCircuit, - objective_qubits: int | list[int], - grover_operator: QuantumCircuit | None = None, - post_processing: Callable[[float], float] | None = None, - is_good_state: Callable[[str], bool] | None = None, - ) -> None: - r""" - Args: - state_preparation: A circuit preparing the input state, referred to as - :math:`\mathcal{A}`. - objective_qubits: A single qubit index or a list of qubit indices to specify which - qubits to measure. The ``is_good_state`` function is applied on the bitstring of - these objective qubits. - grover_operator: The Grover operator :math:`\mathcal{Q}` used as unitary in the - phase estimation circuit. - post_processing: A mapping applied to the result of the algorithm - :math:`0 \leq a \leq 1`, usually used to map the estimate to a target interval. - Defaults to the identity. - is_good_state: A function to check whether a string represents a good state. Defaults - to all objective qubits being in state :math:`|1\rangle`. - """ - self._state_preparation = state_preparation - self._objective_qubits = objective_qubits - self._grover_operator = grover_operator - self._post_processing = post_processing - self._is_good_state = is_good_state - - @property - def state_preparation(self) -> QuantumCircuit | None: - r"""Get the :math:`\mathcal{A}` operator encoding the amplitude :math:`a`. - - Returns: - The :math:`\mathcal{A}` operator as `QuantumCircuit`. - """ - return self._state_preparation - - @state_preparation.setter - def state_preparation(self, state_preparation: QuantumCircuit) -> None: - r"""Set the :math:`\mathcal{A}` operator, that encodes the amplitude to be estimated. - - Args: - state_preparation: The new :math:`\mathcal{A}` operator. - """ - self._state_preparation = state_preparation - - @property - def objective_qubits(self) -> list[int]: - """Get the criterion for a measurement outcome to be in a 'good' state. - - Returns: - The criterion as list of qubit indices. - """ - if isinstance(self._objective_qubits, int): - return [self._objective_qubits] - - return self._objective_qubits - - @objective_qubits.setter - def objective_qubits(self, objective_qubits: int | list[int]) -> None: - """Set the criterion for a measurement outcome to be in a 'good' state. - - Args: - objective_qubits: The criterion as callable of list of qubit indices. - """ - self._objective_qubits = objective_qubits - - @property - def post_processing(self) -> Callable[[float], float]: - """Apply post processing to the input value. - - Returns: - A handle to the post processing function. Acts as identity by default. - """ - if self._post_processing is None: - return lambda x: x - - return self._post_processing - - @post_processing.setter - def post_processing(self, post_processing: Callable[[float], float] | None) -> None: - """Set the post processing function. - - Args: - post_processing: A handle to the post processing function. If set to ``None``, the - identity will be used as post processing. - """ - self._post_processing = post_processing - - @property - def has_good_state(self) -> bool: - """Check whether an :attr:`is_good_state` function is set. - - Some amplitude estimators, such as :class:`.AmplitudeEstimation` do not support - a custom implementation of the :attr:`is_good_state` function, and can only handle - the default. - - Returns: - ``True``, if a custom :attr:`is_good_state` is set, otherwise returns ``False``. - """ - return self._is_good_state is not None - - @property - def is_good_state(self) -> Callable[[str], bool]: - """Checks whether a bitstring represents a good state. - - Returns: - Handle to the ``is_good_state`` callable. - """ - if self._is_good_state is None: - return lambda x: all(bit == "1" for bit in x) - - return self._is_good_state - - @is_good_state.setter - def is_good_state(self, is_good_state: Callable[[str], bool] | None) -> None: - """Set the ``is_good_state`` function. - - Args: - is_good_state: A function to determine whether a bitstring represents a good state. - If set to ``None``, the good state will be defined as all bits being one. - """ - self._is_good_state = is_good_state - - @property - def grover_operator(self) -> QuantumCircuit | None: - r"""Get the :math:`\mathcal{Q}` operator, or Grover operator. - - If the Grover operator is not set, we try to build it from the :math:`\mathcal{A}` operator - and `objective_qubits`. This only works if `objective_qubits` is a list of integers. - - Returns: - The Grover operator, or None if neither the Grover operator nor the - :math:`\mathcal{A}` operator is set. - """ - if self._grover_operator is not None: - return self._grover_operator - - # build the reflection about the bad state: a MCZ with open controls (thus X gates - # around the controls) and X gates around the target to change from a phaseflip on - # |1> to a phaseflip on |0> - num_state_qubits = self.state_preparation.num_qubits - self.state_preparation.num_ancillas - - oracle = QuantumCircuit(num_state_qubits) - oracle.h(self.objective_qubits[-1]) - if len(self.objective_qubits) == 1: - oracle.x(self.objective_qubits[0]) - else: - oracle.mcx(self.objective_qubits[:-1], self.objective_qubits[-1]) - oracle.h(self.objective_qubits[-1]) - - # construct the grover operator - return GroverOperator(oracle, self.state_preparation) - - @grover_operator.setter - def grover_operator(self, grover_operator: QuantumCircuit | None) -> None: - r"""Set the :math:`\mathcal{Q}` operator. - - Args: - grover_operator: The new :math:`\mathcal{Q}` operator. If set to ``None``, - the default construction via ``qiskit.circuit.library.GroverOperator`` is used. - """ - self._grover_operator = grover_operator - - def rescale(self, scaling_factor: float) -> "EstimationProblem": - """Rescale the good state amplitude in the estimation problem. - - Args: - scaling_factor: The scaling factor in [0, 1]. - - Returns: - A rescaled estimation problem. - """ - if self._grover_operator is not None: - warnings.warn("Rescaling discards the Grover operator.") - - # rescale the amplitude by a factor of 1/4 by adding an auxiliary qubit - rescaled_stateprep = _rescale_amplitudes(self.state_preparation, scaling_factor) - num_qubits = self.state_preparation.num_qubits - objective_qubits = self.objective_qubits + [num_qubits] - - # add the scaling qubit to the good state qualifier - def is_good_state(bitstr): - return self.is_good_state(bitstr[1:]) and bitstr[0] == "1" - - # rescaled estimation problem - problem = EstimationProblem( - rescaled_stateprep, - objective_qubits=objective_qubits, - post_processing=self.post_processing, - is_good_state=is_good_state, - ) - - return problem - - -def _rescale_amplitudes(circuit: QuantumCircuit, scaling_factor: float) -> QuantumCircuit: - r"""Uses an auxiliary qubit to scale the amplitude of :math:`|1\rangle` by ``scaling_factor``. - - Explained in Section 2.1. of [1]. - - For example, for a scaling factor of 0.25 this turns this circuit - - .. parsed-literal:: - - ┌───┐ - state_0: ─────┤ H ├─────────■──── - ┌───┴───┴───┐ ┌───┴───┐ - obj_0: ─┤ RY(0.125) ├─┤ RY(1) ├ - └───────────┘ └───────┘ - - into - - .. parsed-literal:: - - ┌───┐ - state_0: ─────┤ H ├─────────■──── - ┌───┴───┴───┐ ┌───┴───┐ - obj_0: ─┤ RY(0.125) ├─┤ RY(1) ├ - ┌┴───────────┴┐└───────┘ - scaling_0: ┤ RY(0.50536) ├───────── - └─────────────┘ - - References: - - [1]: K. Nakaji. Faster Amplitude Estimation, 2020; - `arXiv:2002.02417 `_ - - Args: - circuit: The circuit whose amplitudes to rescale. - scaling_factor: The rescaling factor. - - Returns: - A copy of the circuit with an additional qubit and RY gate for the rescaling. - """ - qr = QuantumRegister(1, "scaling") - rescaled = QuantumCircuit(*circuit.qregs, qr) - rescaled.compose(circuit, circuit.qubits, inplace=True) - rescaled.ry(2 * numpy.arcsin(scaling_factor), qr) - return rescaled diff --git a/qiskit/algorithms/amplitude_estimators/fae.py b/qiskit/algorithms/amplitude_estimators/fae.py deleted file mode 100644 index 08fedaffa34a..000000000000 --- a/qiskit/algorithms/amplitude_estimators/fae.py +++ /dev/null @@ -1,391 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2022. -# -# 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. - -"""Faster Amplitude Estimation.""" - -from __future__ import annotations -import warnings -import numpy as np - -from qiskit.circuit import QuantumCircuit, ClassicalRegister -from qiskit.providers import Backend -from qiskit.primitives import BaseSampler -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg, deprecate_func -from qiskit.algorithms.exceptions import AlgorithmError - -from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult -from .estimation_problem import EstimationProblem - - -class FasterAmplitudeEstimation(AmplitudeEstimator): - """The Faster Amplitude Estimation algorithm. - - The Faster Amplitude Estimation (FAE) [1] algorithm is a variant of Quantum Amplitude - Estimation (QAE), where the Quantum Phase Estimation (QPE) by an iterative Grover search, - similar to [2]. - - Due to the iterative version of the QPE, this algorithm does not require any additional - qubits, as the originally proposed QAE [3] and thus the resulting circuits are less complex. - - References: - - [1]: K. Nakaji. Faster Amplitude Estimation, 2020; - `arXiv:2002.02417 `_ - [2]: D. Grinko et al. Iterative Amplitude Estimation, 2019; - `arXiv:1912.05559 `_ - [3]: G. Brassard et al. Quantum Amplitude Amplification and Estimation, 2000; - `arXiv:quant-ph/0005055 `_ - - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - delta: float, - maxiter: int, - rescale: bool = True, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - delta: The probability that the true value is outside of the final confidence interval. - maxiter: The number of iterations, the maximal power of Q is `2 ** (maxiter - 1)`. - rescale: Whether to rescale the problem passed to `estimate`. - quantum_instance: Deprecated: The quantum instance or backend - to run the circuits. - sampler: A sampler primitive to evaluate the circuits. - - .. note:: - - This algorithm overwrites the number of shots set in the ``quantum_instance`` - argument, but will reset them to the initial number after running. - - """ - super().__init__() - # set quantum instance - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - self.quantum_instance = quantum_instance - self._shots = (int(1944 * np.log(2 / delta)), int(972 * np.log(2 / delta))) - self._rescale = rescale - self._delta = delta - self._maxiter = maxiter - self._num_oracle_calls = 0 - self._sampler = sampler - - @property - def sampler(self) -> BaseSampler | None: - """Get the sampler primitive. - - Returns: - The sampler primitive to evaluate the circuits. - """ - return self._sampler - - @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: - """Set sampler primitive. - - Args: - sampler: A sampler primitive to evaluate the circuits. - """ - self._sampler = sampler - - @property - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self) -> QuantumInstance | None: - """Deprecated. Get the quantum instance. - - Returns: - The quantum instance used to run this algorithm. - """ - return self._quantum_instance - - @quantum_instance.setter - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Deprecated. Set quantum instance. - - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - - def _cos_estimate(self, estimation_problem, k, shots): - if self._quantum_instance is None and self._sampler is None: - raise ValueError("A quantum instance or sampler must be provided.") - - if self._sampler is not None: - circuit = self.construct_circuit(estimation_problem, k, measurement=True) - try: - job = self._sampler.run([circuit], shots=shots) - result = job.result() - except Exception as exc: - raise AlgorithmError("The job was not completed successfully. ") from exc - - if shots is None: - shots = 1 - self._num_oracle_calls += (2 * k + 1) * shots - - # sum over all probabilities where the objective qubits are 1 - prob = 0 - for bit, probabilities in result.quasi_dists[0].binary_probabilities().items(): - # check if it is a good state - if estimation_problem.is_good_state(bit): - prob += probabilities - - cos_estimate = 1 - 2 * prob - elif self._quantum_instance.is_statevector: - circuit = self.construct_circuit(estimation_problem, k, measurement=False) - statevector = self._quantum_instance.execute(circuit).get_statevector() - - # sum over all amplitudes where the objective qubits are 1 - prob = 0 - for i, amplitude in enumerate(statevector): - # get bitstring of objective qubits - full_state = bin(i)[2:].zfill(circuit.num_qubits)[::-1] - state = "".join([full_state[i] for i in estimation_problem.objective_qubits]) - - # check if it is a good state - if estimation_problem.is_good_state(state[::-1]): - prob = prob + np.abs(amplitude) ** 2 - - cos_estimate = 1 - 2 * prob - else: - circuit = self.construct_circuit(estimation_problem, k, measurement=True) - - self._quantum_instance.run_config.shots = shots - counts = self._quantum_instance.execute(circuit).get_counts() - self._num_oracle_calls += (2 * k + 1) * shots - - good_counts = 0 - for state, count in counts.items(): - if estimation_problem.is_good_state(state): - good_counts += count - - cos_estimate = 1 - 2 * good_counts / shots - - return cos_estimate - - def _chernoff(self, cos, shots) -> list[float]: - width = np.sqrt(np.log(2 / self._delta) * 12 / shots) - confint = [np.maximum(-1, cos - width), np.minimum(1, cos + width)] - return confint - - def construct_circuit( - self, estimation_problem: EstimationProblem, k: int, measurement: bool = False - ) -> QuantumCircuit | tuple[QuantumCircuit, list[int]]: - r"""Construct the circuit :math:`Q^k X |0\rangle>`. - - The A operator is the unitary specifying the QAE problem and Q the associated Grover - operator. - - Args: - estimation_problem: The estimation problem for which to construct the circuit. - k: The power of the Q operator. - measurement: Boolean flag to indicate if measurements should be included in the - circuits. - - Returns: - The circuit :math:`Q^k X |0\rangle`. - """ - num_qubits = max( - estimation_problem.state_preparation.num_qubits, - estimation_problem.grover_operator.num_qubits, - ) - circuit = QuantumCircuit(num_qubits, name="circuit") - - # add classical register if needed - if measurement: - c = ClassicalRegister(len(estimation_problem.objective_qubits)) - circuit.add_register(c) - - # add A operator - circuit.compose(estimation_problem.state_preparation, inplace=True) - - # add Q^k - if k != 0: - circuit.compose(estimation_problem.grover_operator.power(k), inplace=True) - - # add optional measurement - if measurement: - # real hardware can currently not handle operations after measurements, which might - # happen if the circuit gets transpiled, hence we're adding a safeguard-barrier - circuit.barrier() - circuit.measure(estimation_problem.objective_qubits, c[:]) - - return circuit - - def estimate(self, estimation_problem: EstimationProblem) -> "FasterAmplitudeEstimationResult": - """Run the amplitude estimation algorithm on provided estimation problem. - - Args: - estimation_problem: The estimation problem. - - Returns: - An amplitude estimation results object. - - Raises: - ValueError: A quantum instance or Sampler must be provided. - AlgorithmError: Sampler run error. - """ - if self._quantum_instance is None and self._sampler is None: - raise ValueError("A quantum instance or sampler must be provided.") - - self._num_oracle_calls = 0 - user_defined_shots = ( - self._quantum_instance._run_config.shots if self._quantum_instance is not None else None - ) - - if self._rescale: - problem = estimation_problem.rescale(0.25) - else: - problem = estimation_problem - - if self._quantum_instance is not None and self._quantum_instance.is_statevector: - cos = self._cos_estimate(problem, k=0, shots=1) - theta = np.arccos(cos) / 2 - theta_ci = [theta, theta] - theta_cis = [theta_ci] - num_steps = num_first_stage_steps = 1 - else: - theta_ci = [0, np.arcsin(0.25)] - first_stage = True - j_0 = self._maxiter - - theta_cis = [theta_ci] - num_first_stage_steps = 0 - num_steps = 0 - - def cos_estimate(power, shots): - return self._cos_estimate(problem, power, shots) - - for j in range(1, self._maxiter + 1): - num_steps += 1 - if first_stage: - num_first_stage_steps += 1 - c = cos_estimate(2 ** (j - 1), self._shots[0]) - chernoff_ci = self._chernoff(c, self._shots[0]) - theta_ci = [np.arccos(x) / (2 ** (j + 1) + 2) for x in chernoff_ci[::-1]] - - if 2 ** (j + 1) * theta_ci[1] >= 3 * np.pi / 8 and j < self._maxiter: - j_0 = j - v = 2**j * np.sum(theta_ci) - first_stage = False - else: - cos = cos_estimate(2 ** (j - 1), self._shots[1]) - cos_2 = cos_estimate(2 ** (j - 1) + 2 ** (j_0 - 1), self._shots[1]) - sin = (cos * np.cos(v) - cos_2) / np.sin(v) - rho = np.arctan2(sin, cos) - n = int(((2 ** (j + 1) + 2) * theta_ci[1] - rho + np.pi / 3) / (2 * np.pi)) - - theta_ci = [ - (2 * np.pi * n + rho + sign * np.pi / 3) / (2 ** (j + 1) + 2) - for sign in [-1, 1] - ] - theta_cis.append(theta_ci) - - theta = np.mean(theta_ci) - rescaling = 4 if self._rescale else 1 - value = (rescaling * np.sin(theta)) ** 2 - value_ci = ((rescaling * np.sin(theta_ci[0])) ** 2, (rescaling * np.sin(theta_ci[1])) ** 2) - - result = FasterAmplitudeEstimationResult() - result.num_oracle_queries = self._num_oracle_calls - result.num_steps = num_steps - result.num_first_state_steps = num_first_stage_steps - if self._quantum_instance is not None and self._quantum_instance.is_statevector: - result.success_probability = 1.0 - else: - result.success_probability = 1 - (2 * self._maxiter - j_0) * self._delta - - result.estimation = value - result.estimation_processed = problem.post_processing(value) - result.confidence_interval = value_ci - result.confidence_interval_processed = tuple(problem.post_processing(x) for x in value_ci) - result.theta_intervals = theta_cis - - # reset shots to what the user had defined - if self._quantum_instance is not None: - self._quantum_instance._run_config.shots = user_defined_shots - - return result - - -class FasterAmplitudeEstimationResult(AmplitudeEstimatorResult): - """The result object for the Faster Amplitude Estimation algorithm.""" - - def __init__(self) -> None: - super().__init__() - self._success_probability: float | None = None - self._num_steps: int | None = None - self._num_first_state_steps: int | None = None - self._theta_intervals: list[list[float]] | None = None - - @property - def success_probability(self) -> float: - """Return the success probability of the algorithm.""" - return self._success_probability - - @success_probability.setter - def success_probability(self, probability: float) -> None: - """Set the success probability of the algorithm.""" - self._success_probability = probability - - @property - def num_steps(self) -> int: - """Return the total number of steps taken in the algorithm.""" - return self._num_steps - - @num_steps.setter - def num_steps(self, num_steps: int) -> None: - """Set the total number of steps taken in the algorithm.""" - self._num_steps = num_steps - - @property - def num_first_state_steps(self) -> int: - """Return the number of steps taken in the first step of algorithm.""" - return self._num_first_state_steps - - @num_first_state_steps.setter - def num_first_state_steps(self, num_steps: int) -> None: - """Set the number of steps taken in the first step of algorithm.""" - self._num_first_state_steps = num_steps - - @property - def theta_intervals(self) -> list[list[float]]: - """Return the confidence intervals for the angles in each iteration.""" - return self._theta_intervals - - @theta_intervals.setter - def theta_intervals(self, value: list[list[float]]) -> None: - """Set the confidence intervals for the angles in each iteration.""" - self._theta_intervals = value diff --git a/qiskit/algorithms/amplitude_estimators/iae.py b/qiskit/algorithms/amplitude_estimators/iae.py deleted file mode 100644 index e420b76e6a4e..000000000000 --- a/qiskit/algorithms/amplitude_estimators/iae.py +++ /dev/null @@ -1,684 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""The Iterative Quantum Amplitude Estimation Algorithm.""" - -from __future__ import annotations -from typing import cast -import warnings -import numpy as np -from scipy.stats import beta - -from qiskit import ClassicalRegister, QuantumCircuit -from qiskit.providers import Backend -from qiskit.primitives import BaseSampler -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg, deprecate_func - -from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult -from .estimation_problem import EstimationProblem -from ..exceptions import AlgorithmError - - -class IterativeAmplitudeEstimation(AmplitudeEstimator): - r"""The Iterative Amplitude Estimation algorithm. - - This class implements the Iterative Quantum Amplitude Estimation (IQAE) algorithm, proposed - in [1]. The output of the algorithm is an estimate that, - with at least probability :math:`1 - \alpha`, differs by epsilon to the target value, where - both alpha and epsilon can be specified. - - It differs from the original QAE algorithm proposed by Brassard [2] in that it does not rely on - Quantum Phase Estimation, but is only based on Grover's algorithm. IQAE iteratively - applies carefully selected Grover iterations to find an estimate for the target amplitude. - - References: - [1]: Grinko, D., Gacon, J., Zoufal, C., & Woerner, S. (2019). - Iterative Quantum Amplitude Estimation. - `arXiv:1912.05559 `_. - [2]: Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). - Quantum Amplitude Amplification and Estimation. - `arXiv:quant-ph/0005055 `_. - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - epsilon_target: float, - alpha: float, - confint_method: str = "beta", - min_ratio: float = 2, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - The output of the algorithm is an estimate for the amplitude `a`, that with at least - probability 1 - alpha has an error of epsilon. The number of A operator calls scales - linearly in 1/epsilon (up to a logarithmic factor). - - Args: - epsilon_target: Target precision for estimation target `a`, has values between 0 and 0.5 - alpha: Confidence level, the target probability is 1 - alpha, has values between 0 and 1 - confint_method: Statistical method used to estimate the confidence intervals in - each iteration, can be 'chernoff' for the Chernoff intervals or 'beta' for the - Clopper-Pearson intervals (default) - min_ratio: Minimal q-ratio (:math:`K_{i+1} / K_i`) for FindNextK - quantum_instance: Deprecated: Quantum Instance or Backend - sampler: A sampler primitive to evaluate the circuits. - - Raises: - AlgorithmError: if the method to compute the confidence intervals is not supported - ValueError: If the target epsilon is not in (0, 0.5] - ValueError: If alpha is not in (0, 1) - ValueError: If confint_method is not supported - """ - # validate ranges of input arguments - if not 0 < epsilon_target <= 0.5: - raise ValueError(f"The target epsilon must be in (0, 0.5], but is {epsilon_target}.") - - if not 0 < alpha < 1: - raise ValueError(f"The confidence level alpha must be in (0, 1), but is {alpha}") - - if confint_method not in {"chernoff", "beta"}: - raise ValueError( - f"The confidence interval method must be chernoff or beta, but is {confint_method}." - ) - - super().__init__() - - # set quantum instance - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - self.quantum_instance = quantum_instance - - # store parameters - self._epsilon = epsilon_target - self._alpha = alpha - self._min_ratio = min_ratio - self._confint_method = confint_method - self._sampler = sampler - - @property - def sampler(self) -> BaseSampler | None: - """Get the sampler primitive. - - Returns: - The sampler primitive to evaluate the circuits. - """ - return self._sampler - - @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: - """Set sampler primitive. - - Args: - sampler: A sampler primitive to evaluate the circuits. - """ - self._sampler = sampler - - @property - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self) -> QuantumInstance | None: - """Deprecated. Get the quantum instance. - - Returns: - The quantum instance used to run this algorithm. - """ - return self._quantum_instance - - @quantum_instance.setter - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Deprecated. Set quantum instance. - - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - - @property - def epsilon_target(self) -> float: - """Returns the target precision ``epsilon_target`` of the algorithm. - - Returns: - The target precision (which is half the width of the confidence interval). - """ - return self._epsilon - - @epsilon_target.setter - def epsilon_target(self, epsilon: float) -> None: - """Set the target precision of the algorithm. - - Args: - epsilon: Target precision for estimation target `a`. - """ - self._epsilon = epsilon - - def _find_next_k( - self, - k: int, - upper_half_circle: bool, - theta_interval: tuple[float, float], - min_ratio: float = 2.0, - ) -> tuple[int, bool]: - """Find the largest integer k_next, such that the interval (4 * k_next + 2)*theta_interval - lies completely in [0, pi] or [pi, 2pi], for theta_interval = (theta_lower, theta_upper). - - Args: - k: The current power of the Q operator. - upper_half_circle: Boolean flag of whether theta_interval lies in the - upper half-circle [0, pi] or in the lower one [pi, 2pi]. - theta_interval: The current confidence interval for the angle theta, - i.e. (theta_lower, theta_upper). - min_ratio: Minimal ratio K/K_next allowed in the algorithm. - - Returns: - The next power k, and boolean flag for the extrapolated interval. - - Raises: - AlgorithmError: if min_ratio is smaller or equal to 1 - """ - if min_ratio <= 1: - raise AlgorithmError("min_ratio must be larger than 1 to ensure convergence") - - # initialize variables - theta_l, theta_u = theta_interval - old_scaling = 4 * k + 2 # current scaling factor, called K := (4k + 2) - - # the largest feasible scaling factor K cannot be larger than K_max, - # which is bounded by the length of the current confidence interval - max_scaling = int(1 / (2 * (theta_u - theta_l))) - scaling = max_scaling - (max_scaling - 2) % 4 # bring into the form 4 * k_max + 2 - - # find the largest feasible scaling factor K_next, and thus k_next - while scaling >= min_ratio * old_scaling: - theta_min = scaling * theta_l - int(scaling * theta_l) - theta_max = scaling * theta_u - int(scaling * theta_u) - - if theta_min <= theta_max <= 0.5 and theta_min <= 0.5: - # the extrapolated theta interval is in the upper half-circle - upper_half_circle = True - return int((scaling - 2) / 4), upper_half_circle - - elif theta_max >= 0.5 and theta_max >= theta_min >= 0.5: - # the extrapolated theta interval is in the upper half-circle - upper_half_circle = False - return int((scaling - 2) / 4), upper_half_circle - - scaling -= 4 - - # if we do not find a feasible k, return the old one - return int(k), upper_half_circle - - def construct_circuit( - self, estimation_problem: EstimationProblem, k: int = 0, measurement: bool = False - ) -> QuantumCircuit: - r"""Construct the circuit :math:`\mathcal{Q}^k \mathcal{A} |0\rangle`. - - The A operator is the unitary specifying the QAE problem and Q the associated Grover - operator. - - Args: - estimation_problem: The estimation problem for which to construct the QAE circuit. - k: The power of the Q operator. - measurement: Boolean flag to indicate if measurements should be included in the - circuits. - - Returns: - The circuit implementing :math:`\mathcal{Q}^k \mathcal{A} |0\rangle`. - """ - num_qubits = max( - estimation_problem.state_preparation.num_qubits, - estimation_problem.grover_operator.num_qubits, - ) - circuit = QuantumCircuit(num_qubits, name="circuit") - - # add classical register if needed - if measurement: - c = ClassicalRegister(len(estimation_problem.objective_qubits)) - circuit.add_register(c) - - # add A operator - circuit.compose(estimation_problem.state_preparation, inplace=True) - - # add Q^k - if k != 0: - circuit.compose(estimation_problem.grover_operator.power(k), inplace=True) - - # add optional measurement - if measurement: - # real hardware can currently not handle operations after measurements, which might - # happen if the circuit gets transpiled, hence we're adding a safeguard-barrier - circuit.barrier() - circuit.measure(estimation_problem.objective_qubits, c[:]) - - return circuit - - def _good_state_probability( - self, - problem: EstimationProblem, - counts_or_statevector: dict[str, int] | np.ndarray, - num_state_qubits: int, - ) -> tuple[int, float] | float: - """Get the probability to measure '1' in the last qubit. - - Args: - problem: The estimation problem, used to obtain the number of objective qubits and - the ``is_good_state`` function. - counts_or_statevector: Either a counts-dictionary (with one measured qubit only!) or - the statevector returned from the statevector_simulator. - num_state_qubits: The number of state qubits. - - Returns: - If a dict is given, return (#one-counts, #one-counts/#all-counts), - otherwise Pr(measure '1' in the last qubit). - """ - if isinstance(counts_or_statevector, dict): - one_counts = 0 - for state, counts in counts_or_statevector.items(): - if problem.is_good_state(state): - one_counts += counts - - return int(one_counts), one_counts / sum(counts_or_statevector.values()) - else: - statevector = counts_or_statevector - num_qubits = int(np.log2(len(statevector))) # the total number of qubits - - # sum over all amplitudes where the objective qubit is 1 - prob = 0 - for i, amplitude in enumerate(statevector): - # consider only state qubits and revert bit order - bitstr = bin(i)[2:].zfill(num_qubits)[-num_state_qubits:][::-1] - objectives = [bitstr[index] for index in problem.objective_qubits] - if problem.is_good_state(objectives): - prob = prob + np.abs(amplitude) ** 2 - - return prob - - def estimate( - self, estimation_problem: EstimationProblem - ) -> "IterativeAmplitudeEstimationResult": - """Run the amplitude estimation algorithm on provided estimation problem. - - Args: - estimation_problem: The estimation problem. - - Returns: - An amplitude estimation results object. - - Raises: - ValueError: A quantum instance or Sampler must be provided. - AlgorithmError: Sampler job run error. - """ - if self._quantum_instance is None and self._sampler is None: - raise ValueError("A quantum instance or sampler must be provided.") - - # initialize memory variables - powers = [0] # list of powers k: Q^k, (called 'k' in paper) - ratios = [] # list of multiplication factors (called 'q' in paper) - theta_intervals = [[0, 1 / 4]] # a priori knowledge of theta / 2 / pi - a_intervals = [[0.0, 1.0]] # a priori knowledge of the confidence interval of the estimate - num_oracle_queries = 0 - num_one_shots = [] - - # maximum number of rounds - max_rounds = ( - int(np.log(self._min_ratio * np.pi / 8 / self._epsilon) / np.log(self._min_ratio)) + 1 - ) - upper_half_circle = True # initially theta is in the upper half-circle - - if self._quantum_instance is not None and self._quantum_instance.is_statevector: - # for statevector we can directly return the probability to measure 1 - # note, that no iterations here are necessary - # simulate circuit - circuit = self.construct_circuit(estimation_problem, k=0, measurement=False) - ret = self._quantum_instance.execute(circuit) - - # get statevector - statevector = ret.get_statevector(circuit) - - # calculate the probability of measuring '1' - num_qubits = circuit.num_qubits - circuit.num_ancillas - prob = self._good_state_probability(estimation_problem, statevector, num_qubits) - prob = cast(float, prob) # tell MyPy it's a float and not Tuple[int, float ] - - a_confidence_interval = [prob, prob] # type: list[float] - a_intervals.append(a_confidence_interval) - - theta_i_interval = [ - np.arccos(1 - 2 * a_i) / 2 / np.pi for a_i in a_confidence_interval # type: ignore - ] - theta_intervals.append(theta_i_interval) - num_oracle_queries = 0 # no Q-oracle call, only a single one to A - - else: - num_iterations = 0 # keep track of the number of iterations - # number of shots per iteration - shots = 0 - # do while loop, keep in mind that we scaled theta mod 2pi such that it lies in [0,1] - while theta_intervals[-1][1] - theta_intervals[-1][0] > self._epsilon / np.pi: - num_iterations += 1 - - # get the next k - k, upper_half_circle = self._find_next_k( - powers[-1], - upper_half_circle, - theta_intervals[-1], # type: ignore - min_ratio=self._min_ratio, - ) - - # store the variables - powers.append(k) - ratios.append((2 * powers[-1] + 1) / (2 * powers[-2] + 1)) - - # run measurements for Q^k A|0> circuit - circuit = self.construct_circuit(estimation_problem, k, measurement=True) - counts = {} - if self._quantum_instance is not None: - ret = self._quantum_instance.execute(circuit) - # get the counts and store them - counts = ret.get_counts(circuit) - shots = self._quantum_instance._run_config.shots - else: - try: - job = self._sampler.run([circuit]) - ret = job.result() - except Exception as exc: - raise AlgorithmError("The job was not completed successfully. ") from exc - - shots = ret.metadata[0].get("shots") - if shots is None: - circuit = self.construct_circuit(estimation_problem, k=0, measurement=True) - try: - job = self._sampler.run([circuit]) - ret = job.result() - except Exception as exc: - raise AlgorithmError( - "The job was not completed successfully. " - ) from exc - - # calculate the probability of measuring '1' - prob = 0.0 - for bit, probabilities in ret.quasi_dists[0].binary_probabilities().items(): - # check if it is a good state - if estimation_problem.is_good_state(bit): - prob += probabilities - - a_confidence_interval = [prob, prob] - a_intervals.append(a_confidence_interval) - - theta_i_interval = [ - np.arccos(1 - 2 * a_i) / 2 / np.pi for a_i in a_confidence_interval - ] - theta_intervals.append(theta_i_interval) - num_oracle_queries = 0 # no Q-oracle call, only a single one to A - break - - counts = { - k: round(v * shots) - for k, v in ret.quasi_dists[0].binary_probabilities().items() - } - - # calculate the probability of measuring '1', 'prob' is a_i in the paper - num_qubits = circuit.num_qubits - circuit.num_ancillas - # type: ignore - one_counts, prob = self._good_state_probability( - estimation_problem, counts, num_qubits - ) - - num_one_shots.append(one_counts) - - # track number of Q-oracle calls - num_oracle_queries += shots * k - - # if on the previous iterations we have K_{i-1} == K_i, we sum these samples up - j = 1 # number of times we stayed fixed at the same K - round_shots = shots - round_one_counts = one_counts - if num_iterations > 1: - while ( - powers[num_iterations - j] == powers[num_iterations] - and num_iterations >= j + 1 - ): - j = j + 1 - round_shots += shots - round_one_counts += num_one_shots[-j] - - # compute a_min_i, a_max_i - if self._confint_method == "chernoff": - a_i_min, a_i_max = _chernoff_confint(prob, round_shots, max_rounds, self._alpha) - else: # 'beta' - a_i_min, a_i_max = _clopper_pearson_confint( - round_one_counts, round_shots, self._alpha / max_rounds - ) - - # compute theta_min_i, theta_max_i - if upper_half_circle: - theta_min_i = np.arccos(1 - 2 * a_i_min) / 2 / np.pi - theta_max_i = np.arccos(1 - 2 * a_i_max) / 2 / np.pi - else: - theta_min_i = 1 - np.arccos(1 - 2 * a_i_max) / 2 / np.pi - theta_max_i = 1 - np.arccos(1 - 2 * a_i_min) / 2 / np.pi - - # compute theta_u, theta_l of this iteration - scaling = 4 * k + 2 # current K_i factor - theta_u = (int(scaling * theta_intervals[-1][1]) + theta_max_i) / scaling - theta_l = (int(scaling * theta_intervals[-1][0]) + theta_min_i) / scaling - theta_intervals.append([theta_l, theta_u]) - - # compute a_u_i, a_l_i - a_u = np.sin(2 * np.pi * theta_u) ** 2 - a_l = np.sin(2 * np.pi * theta_l) ** 2 - a_u = cast(float, a_u) - a_l = cast(float, a_l) - a_intervals.append([a_l, a_u]) - - # get the latest confidence interval for the estimate of a - confidence_interval = tuple(a_intervals[-1]) - - # the final estimate is the mean of the confidence interval - estimation = np.mean(confidence_interval) - - result = IterativeAmplitudeEstimationResult() - result.alpha = self._alpha - result.post_processing = estimation_problem.post_processing - result.num_oracle_queries = num_oracle_queries - - result.estimation = estimation - result.epsilon_estimated = (confidence_interval[1] - confidence_interval[0]) / 2 - result.confidence_interval = confidence_interval - - result.estimation_processed = estimation_problem.post_processing(estimation) - confidence_interval = tuple( - estimation_problem.post_processing(x) for x in confidence_interval - ) - result.confidence_interval_processed = confidence_interval - result.epsilon_estimated_processed = (confidence_interval[1] - confidence_interval[0]) / 2 - result.estimate_intervals = a_intervals - result.theta_intervals = theta_intervals - result.powers = powers - result.ratios = ratios - - return result - - -class IterativeAmplitudeEstimationResult(AmplitudeEstimatorResult): - """The ``IterativeAmplitudeEstimation`` result object.""" - - def __init__(self) -> None: - super().__init__() - self._alpha: float | None = None - self._epsilon_target: float | None = None - self._epsilon_estimated: float | None = None - self._epsilon_estimated_processed: float | None = None - self._estimate_intervals: list[list[float]] | None = None - self._theta_intervals: list[list[float]] | None = None - self._powers: list[int] | None = None - self._ratios: list[float] | None = None - self._confidence_interval_processed: tuple[float, float] | None = None - - @property - def alpha(self) -> float: - r"""Return the confidence level :math:`\alpha`.""" - return self._alpha - - @alpha.setter - def alpha(self, value: float) -> None: - r"""Set the confidence level :math:`\alpha`.""" - self._alpha = value - - @property - def epsilon_target(self) -> float: - """Return the target half-width of the confidence interval.""" - return self._epsilon_target - - @epsilon_target.setter - def epsilon_target(self, value: float) -> None: - """Set the target half-width of the confidence interval.""" - self._epsilon_target = value - - @property - def epsilon_estimated(self) -> float: - """Return the estimated half-width of the confidence interval.""" - return self._epsilon_estimated - - @epsilon_estimated.setter - def epsilon_estimated(self, value: float) -> None: - """Set the estimated half-width of the confidence interval.""" - self._epsilon_estimated = value - - @property - def epsilon_estimated_processed(self) -> float: - """Return the post-processed estimated half-width of the confidence interval.""" - return self._epsilon_estimated_processed - - @epsilon_estimated_processed.setter - def epsilon_estimated_processed(self, value: float) -> None: - """Set the post-processed estimated half-width of the confidence interval.""" - self._epsilon_estimated_processed = value - - @property - def estimate_intervals(self) -> list[list[float]]: - """Return the confidence intervals for the estimate in each iteration.""" - return self._estimate_intervals - - @estimate_intervals.setter - def estimate_intervals(self, value: list[list[float]]) -> None: - """Set the confidence intervals for the estimate in each iteration.""" - self._estimate_intervals = value - - @property - def theta_intervals(self) -> list[list[float]]: - """Return the confidence intervals for the angles in each iteration.""" - return self._theta_intervals - - @theta_intervals.setter - def theta_intervals(self, value: list[list[float]]) -> None: - """Set the confidence intervals for the angles in each iteration.""" - self._theta_intervals = value - - @property - def powers(self) -> list[int]: - """Return the powers of the Grover operator in each iteration.""" - return self._powers - - @powers.setter - def powers(self, value: list[int]) -> None: - """Set the powers of the Grover operator in each iteration.""" - self._powers = value - - @property - def ratios(self) -> list[float]: - r"""Return the ratios :math:`K_{i+1}/K_{i}` for each iteration :math:`i`.""" - return self._ratios - - @ratios.setter - def ratios(self, value: list[float]) -> None: - r"""Set the ratios :math:`K_{i+1}/K_{i}` for each iteration :math:`i`.""" - self._ratios = value - - @property - def confidence_interval_processed(self) -> tuple[float, float]: - """Return the post-processed confidence interval.""" - return self._confidence_interval_processed - - @confidence_interval_processed.setter - def confidence_interval_processed(self, value: tuple[float, float]) -> None: - """Set the post-processed confidence interval.""" - self._confidence_interval_processed = value - - -def _chernoff_confint( - value: float, shots: int, max_rounds: int, alpha: float -) -> tuple[float, float]: - """Compute the Chernoff confidence interval for `shots` i.i.d. Bernoulli trials. - - The confidence interval is - - [value - eps, value + eps], where eps = sqrt(3 * log(2 * max_rounds/ alpha) / shots) - - but at most [0, 1]. - - Args: - value: The current estimate. - shots: The number of shots. - max_rounds: The maximum number of rounds, used to compute epsilon_a. - alpha: The confidence level, used to compute epsilon_a. - - Returns: - The Chernoff confidence interval. - """ - eps = np.sqrt(3 * np.log(2 * max_rounds / alpha) / shots) - lower = np.maximum(0, value - eps) - upper = np.minimum(1, value + eps) - return lower, upper - - -def _clopper_pearson_confint(counts: int, shots: int, alpha: float) -> tuple[float, float]: - """Compute the Clopper-Pearson confidence interval for `shots` i.i.d. Bernoulli trials. - - Args: - counts: The number of positive counts. - shots: The number of shots. - alpha: The confidence level for the confidence interval. - - Returns: - The Clopper-Pearson confidence interval. - """ - lower, upper = 0, 1 - - # if counts == 0, the beta quantile returns nan - if counts != 0: - lower = beta.ppf(alpha / 2, counts, shots - counts + 1) - - # if counts == shots, the beta quantile returns nan - if counts != shots: - upper = beta.ppf(1 - alpha / 2, counts + 1, shots - counts) - - return lower, upper diff --git a/qiskit/algorithms/amplitude_estimators/mlae.py b/qiskit/algorithms/amplitude_estimators/mlae.py deleted file mode 100644 index aa9f600b700e..000000000000 --- a/qiskit/algorithms/amplitude_estimators/mlae.py +++ /dev/null @@ -1,667 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""The Maximum Likelihood Amplitude Estimation algorithm.""" - -from __future__ import annotations -import warnings -from collections.abc import Sequence -from typing import Callable, List, Tuple - -import numpy as np -from scipy.optimize import brute -from scipy.stats import norm, chi2 - -from qiskit.providers import Backend -from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit -from qiskit.utils import QuantumInstance -from qiskit.primitives import BaseSampler -from qiskit.utils.deprecation import deprecate_arg, deprecate_func - -from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult -from .estimation_problem import EstimationProblem -from ..exceptions import AlgorithmError - -MINIMIZER = Callable[[Callable[[float], float], List[Tuple[float, float]]], float] - - -class MaximumLikelihoodAmplitudeEstimation(AmplitudeEstimator): - """The Maximum Likelihood Amplitude Estimation algorithm. - - This class implements the quantum amplitude estimation (QAE) algorithm without phase - estimation, as introduced in [1]. In comparison to the original QAE algorithm [2], - this implementation relies solely on different powers of the Grover operator and does not - require additional evaluation qubits. - Finally, the estimate is determined via a maximum likelihood estimation, which is why this - class in named ``MaximumLikelihoodAmplitudeEstimation``. - - References: - [1]: Suzuki, Y., Uno, S., Raymond, R., Tanaka, T., Onodera, T., & Yamamoto, N. (2019). - Amplitude Estimation without Phase Estimation. - `arXiv:1904.10246 `_. - [2]: Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). - Quantum Amplitude Amplification and Estimation. - `arXiv:quant-ph/0005055 `_. - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - evaluation_schedule: list[int] | int, - minimizer: MINIMIZER | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - evaluation_schedule: If a list, the powers applied to the Grover operator. The list - element must be non-negative. If a non-negative integer, an exponential schedule is - used where the highest power is 2 to the integer minus 1: - `[id, Q^2^0, ..., Q^2^(evaluation_schedule-1)]`. - minimizer: A minimizer used to find the minimum of the likelihood function. - Defaults to a brute search where the number of evaluation points is determined - according to ``evaluation_schedule``. The minimizer takes a function as first - argument and a list of (float, float) tuples (as bounds) as second argument and - returns a single float which is the found minimum. - quantum_instance: Deprecated: Quantum Instance or Backend - sampler: A sampler primitive to evaluate the circuits. - - Raises: - ValueError: If the number of oracle circuits is smaller than 1. - """ - - super().__init__() - - # set quantum instance - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - self.quantum_instance = quantum_instance - - # get parameters - if isinstance(evaluation_schedule, int): - if evaluation_schedule < 0: - raise ValueError("The evaluation schedule cannot be < 0.") - - self._evaluation_schedule = [0] + [2**j for j in range(evaluation_schedule)] - else: - if any(value < 0 for value in evaluation_schedule): - raise ValueError("The elements of the evaluation schedule cannot be < 0.") - - self._evaluation_schedule = evaluation_schedule - - if minimizer is None: - # default number of evaluations is max(10^4, pi/2 * 10^3 * 2^(m)) - nevals = max(10000, int(np.pi / 2 * 1000 * 2 * self._evaluation_schedule[-1])) - - def default_minimizer(objective_fn, bounds): - return brute(objective_fn, bounds, Ns=nevals)[0] - - self._minimizer = default_minimizer - else: - self._minimizer = minimizer - - self._sampler = sampler - - @property - def sampler(self) -> BaseSampler | None: - """Get the sampler primitive. - - Returns: - The sampler primitive to evaluate the circuits. - """ - return self._sampler - - @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: - """Set sampler primitive. - - Args: - sampler: A sampler primitive to evaluate the circuits. - """ - self._sampler = sampler - - @property - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self) -> QuantumInstance | None: - """Deprecated. Get the quantum instance. - - Returns: - The quantum instance used to run this algorithm. - """ - return self._quantum_instance - - @quantum_instance.setter - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Deprecated. Set quantum instance. - - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - - def construct_circuits( - self, estimation_problem: EstimationProblem, measurement: bool = False - ) -> list[QuantumCircuit]: - """Construct the Amplitude Estimation w/o QPE quantum circuits. - - Args: - estimation_problem: The estimation problem for which to construct the QAE circuit. - measurement: Boolean flag to indicate if measurement should be included in the circuits. - - Returns: - A list with the QuantumCircuit objects for the algorithm. - """ - # keep track of the Q-oracle queries - circuits = [] - - num_qubits = max( - estimation_problem.state_preparation.num_qubits, - estimation_problem.grover_operator.num_qubits, - ) - q = QuantumRegister(num_qubits, "q") - qc_0 = QuantumCircuit(q, name="qc_a") # 0 applications of Q, only a single A operator - - # add classical register if needed - if measurement: - c = ClassicalRegister(len(estimation_problem.objective_qubits)) - qc_0.add_register(c) - - qc_0.compose(estimation_problem.state_preparation, inplace=True) - - for k in self._evaluation_schedule: - qc_k = qc_0.copy(name=f"qc_a_q_{k}") - - if k != 0: - qc_k.compose(estimation_problem.grover_operator.power(k), inplace=True) - - if measurement: - # real hardware can currently not handle operations after measurements, - # which might happen if the circuit gets transpiled, hence we're adding - # a safeguard-barrier - qc_k.barrier() - qc_k.measure(estimation_problem.objective_qubits, c[:]) - - circuits += [qc_k] - - return circuits - - @staticmethod - def compute_confidence_interval( - result: "MaximumLikelihoodAmplitudeEstimationResult", - alpha: float, - kind: str = "fisher", - apply_post_processing: bool = False, - ) -> tuple[float, float]: - """Compute the `alpha` confidence interval using the method `kind`. - - The confidence level is (1 - `alpha`) and supported kinds are 'fisher', - 'likelihood_ratio' and 'observed_fisher' with shorthand - notations 'fi', 'lr' and 'oi', respectively. - - Args: - result: A maximum likelihood amplitude estimation result. - alpha: The confidence level. - kind: The method to compute the confidence interval. Defaults to 'fisher', which - computes the theoretical Fisher information. - apply_post_processing: If True, apply post-processing to the confidence interval. - - Returns: - The specified confidence interval. - - Raises: - AlgorithmError: If `run()` hasn't been called yet. - NotImplementedError: If the method `kind` is not supported. - """ - interval: tuple[float, float] | None = None - - # if statevector simulator the estimate is exact - if all(isinstance(data, (list, np.ndarray)) for data in result.circuit_results): - interval = (result.estimation, result.estimation) - - elif kind in ["likelihood_ratio", "lr"]: - interval = _likelihood_ratio_confint(result, alpha) - - elif kind in ["fisher", "fi"]: - interval = _fisher_confint(result, alpha, observed=False) - - elif kind in ["observed_fisher", "observed_information", "oi"]: - interval = _fisher_confint(result, alpha, observed=True) - - if interval is None: - raise NotImplementedError(f"CI `{kind}` is not implemented.") - - if apply_post_processing: - return result.post_processing(interval[0]), result.post_processing(interval[1]) - - return interval - - def compute_mle( - self, - circuit_results: list[dict[str, int] | np.ndarray], - estimation_problem: EstimationProblem, - num_state_qubits: int | None = None, - return_counts: bool = False, - ) -> float | tuple[float, list[float]]: - """Compute the MLE via a grid-search. - - This is a stable approach if sufficient gridpoints are used. - - Args: - circuit_results: A list of circuit outcomes. Can be counts or statevectors. - estimation_problem: The estimation problem containing the evaluation schedule and the - number of likelihood function evaluations used to find the minimum. - num_state_qubits: The number of state qubits, required for statevector simulations. - return_counts: If True, returns the good counts. - - Returns: - The MLE for the provided result object. - """ - good_counts, all_counts = _get_counts(circuit_results, estimation_problem, num_state_qubits) - - # search range - eps = 1e-15 # to avoid invalid value in log - search_range = [0 + eps, np.pi / 2 - eps] - - def loglikelihood(theta): - # loglik contains the first `it` terms of the full loglikelihood - loglik = 0 - for i, k in enumerate(self._evaluation_schedule): - angle = (2 * k + 1) * theta - loglik += np.log(np.sin(angle) ** 2) * good_counts[i] - loglik += np.log(np.cos(angle) ** 2) * (all_counts[i] - good_counts[i]) - return -loglik - - est_theta = self._minimizer(loglikelihood, [search_range]) - - if return_counts: - return est_theta, good_counts - return est_theta - - def estimate( - self, estimation_problem: EstimationProblem - ) -> "MaximumLikelihoodAmplitudeEstimationResult": - """Run the amplitude estimation algorithm on provided estimation problem. - - Args: - estimation_problem: The estimation problem. - - Returns: - An amplitude estimation results object. - - Raises: - ValueError: A quantum instance or Sampler must be provided. - AlgorithmError: If `state_preparation` is not set in - `estimation_problem`. - AlgorithmError: Sampler job run error - """ - if self._quantum_instance is None and self._sampler is None: - raise ValueError("A quantum instance or sampler must be provided.") - if estimation_problem.state_preparation is None: - raise AlgorithmError( - "The state_preparation property of the estimation problem must be set." - ) - - result = MaximumLikelihoodAmplitudeEstimationResult() - result.evaluation_schedule = self._evaluation_schedule - result.minimizer = self._minimizer - result.post_processing = estimation_problem.post_processing - - shots = 0 - if self._quantum_instance is not None and self._quantum_instance.is_statevector: - # run circuit on statevector simulator - circuits = self.construct_circuits(estimation_problem, measurement=False) - ret = self._quantum_instance.execute(circuits) - - # get statevectors and construct MLE input - statevectors = [np.asarray(ret.get_statevector(circuit)) for circuit in circuits] - result.circuit_results = statevectors - - # to count the number of Q-oracle calls (don't count shots) - result.shots = 1 - else: - circuits = self.construct_circuits(estimation_problem, measurement=True) - if self._quantum_instance is not None: - # run circuit on QASM simulator - ret = self._quantum_instance.execute(circuits) - # get counts and construct MLE input - result.circuit_results = [ret.get_counts(circuit) for circuit in circuits] - shots = self._quantum_instance._run_config.shots - else: - try: - job = self._sampler.run(circuits) - ret = job.result() - except Exception as exc: - raise AlgorithmError("The job was not completed successfully. ") from exc - - result.circuit_results = [] - shots = ret.metadata[0].get("shots") - if shots is None: - for quasi_dist in ret.quasi_dists: - circuit_result = quasi_dist.binary_probabilities() - result.circuit_results.append(circuit_result) - shots = 1 - else: - # get counts and construct MLE input - for quasi_dist in ret.quasi_dists: - counts = { - k: round(v * shots) - for k, v in quasi_dist.binary_probabilities().items() - } - result.circuit_results.append(counts) - - result.shots = shots - - # run maximum likelihood estimation - num_state_qubits = circuits[0].num_qubits - circuits[0].num_ancillas - theta, good_counts = self.compute_mle( - result.circuit_results, estimation_problem, num_state_qubits, True - ) - - # store results - result.theta = theta - result.good_counts = good_counts - result.estimation = np.sin(result.theta) ** 2 - - # not sure why pylint complains, this is a callable and the tests pass - # pylint: disable=not-callable - result.estimation_processed = result.post_processing(result.estimation) - - result.fisher_information = _compute_fisher_information(result) - result.num_oracle_queries = result.shots * sum(k for k in result.evaluation_schedule) - - # compute and store confidence interval - confidence_interval = self.compute_confidence_interval(result, alpha=0.05, kind="fisher") - result.confidence_interval = confidence_interval - result.confidence_interval_processed = tuple( - estimation_problem.post_processing(value) for value in confidence_interval - ) - - return result - - -class MaximumLikelihoodAmplitudeEstimationResult(AmplitudeEstimatorResult): - """The ``MaximumLikelihoodAmplitudeEstimation`` result object.""" - - def __init__(self) -> None: - super().__init__() - self._theta: float | None = None - self._minimizer: Callable | None = None - self._good_counts: list[float] | None = None - self._evaluation_schedule: list[int] | None = None - self._fisher_information: float | None = None - - @property - def theta(self) -> float: - r"""Return the estimate for the angle :math:`\theta`.""" - return self._theta - - @theta.setter - def theta(self, value: float) -> None: - r"""Set the estimate for the angle :math:`\theta`.""" - self._theta = value - - @property - def minimizer(self) -> Callable: - """Return the minimizer used for the search of the likelihood function.""" - return self._minimizer - - @minimizer.setter - def minimizer(self, value: Callable) -> None: - """Set the number minimizer used for the search of the likelihood function.""" - self._minimizer = value - - @property - def good_counts(self) -> list[float]: - """Return the percentage of good counts per circuit power.""" - return self._good_counts - - @good_counts.setter - def good_counts(self, counts: list[float]) -> None: - """Set the percentage of good counts per circuit power.""" - self._good_counts = counts - - @property - def evaluation_schedule(self) -> list[int]: - """Return the evaluation schedule for the powers of the Grover operator.""" - return self._evaluation_schedule - - @evaluation_schedule.setter - def evaluation_schedule(self, evaluation_schedule: list[int]) -> None: - """Set the evaluation schedule for the powers of the Grover operator.""" - self._evaluation_schedule = evaluation_schedule - - @property - def fisher_information(self) -> float: - """Return the Fisher information for the estimated amplitude.""" - return self._fisher_information - - @fisher_information.setter - def fisher_information(self, value: float) -> None: - """Set the Fisher information for the estimated amplitude.""" - self._fisher_information = value - - -def _safe_min(array, default=0): - if len(array) == 0: - return default - return np.min(array) - - -def _safe_max(array, default=(np.pi / 2)): - if len(array) == 0: - return default - return np.max(array) - - -def _compute_fisher_information( - result: "MaximumLikelihoodAmplitudeEstimationResult", - num_sum_terms: int | None = None, - observed: bool = False, -) -> float: - """Compute the Fisher information. - - Args: - result: A maximum likelihood amplitude estimation result. - num_sum_terms: The number of sum terms to be included in the calculation of the - Fisher information. By default all values are included. - observed: If True, compute the observed Fisher information, otherwise the theoretical - one. - - Returns: - The computed Fisher information, or np.inf if statevector simulation was used. - - Raises: - KeyError: Call run() first! - """ - a = result.estimation - - # Corresponding angle to the value a (only use real part of 'a') - theta_a = np.arcsin(np.sqrt(np.real(a))) - - # Get the number of hits (shots_k) and one-hits (h_k) - one_hits = result.good_counts - all_hits = [result.shots] * len(one_hits) - - # Include all sum terms or just up to a certain term? - evaluation_schedule = result.evaluation_schedule - if num_sum_terms is not None: - evaluation_schedule = evaluation_schedule[:num_sum_terms] - # not necessary since zip goes as far as shortest list: - # all_hits = all_hits[:num_sum_terms] - # one_hits = one_hits[:num_sum_terms] - - # Compute the Fisher information - fisher_information = None - if observed: - # Note, that the observed Fisher information is very unreliable in this algorithm! - d_loglik = 0 - for shots_k, h_k, m_k in zip(all_hits, one_hits, evaluation_schedule): - tan = np.tan((2 * m_k + 1) * theta_a) - d_loglik += (2 * m_k + 1) * (h_k / tan + (shots_k - h_k) * tan) - - d_loglik /= np.sqrt(a * (1 - a)) - fisher_information = d_loglik**2 / len(all_hits) - - else: - fisher_information = sum( - shots_k * (2 * m_k + 1) ** 2 for shots_k, m_k in zip(all_hits, evaluation_schedule) - ) - fisher_information /= a * (1 - a) - - return fisher_information - - -def _fisher_confint( - result: MaximumLikelihoodAmplitudeEstimationResult, alpha: float = 0.05, observed: bool = False -) -> tuple[float, float]: - """Compute the `alpha` confidence interval based on the Fisher information. - - Args: - result: A maximum likelihood amplitude estimation results object. - alpha: The level of the confidence interval (must be <= 0.5), default to 0.05. - observed: If True, use observed Fisher information. - - Returns: - float: The alpha confidence interval based on the Fisher information - Raises: - AssertionError: Call run() first! - """ - # Get the (observed) Fisher information - fisher_information = None - try: - fisher_information = result.fisher_information - except KeyError as ex: - raise AssertionError("Call run() first!") from ex - - if observed: - fisher_information = _compute_fisher_information(result, observed=True) - - normal_quantile = norm.ppf(1 - alpha / 2) - confint = np.real(result.estimation) + normal_quantile / np.sqrt(fisher_information) * np.array( - [-1, 1] - ) - return result.post_processing(confint[0]), result.post_processing(confint[1]) - - -def _likelihood_ratio_confint( - result: MaximumLikelihoodAmplitudeEstimationResult, - alpha: float = 0.05, - nevals: int | None = None, -) -> tuple[float, float]: - """Compute the likelihood-ratio confidence interval. - - Args: - result: A maximum likelihood amplitude estimation results object. - alpha: The level of the confidence interval (< 0.5), defaults to 0.05. - nevals: The number of evaluations to find the intersection with the loglikelihood - function. Defaults to an adaptive value based on the maximal power of Q. - - Returns: - The alpha-likelihood-ratio confidence interval. - """ - if nevals is None: - nevals = max(10000, int(np.pi / 2 * 1000 * 2 * result.evaluation_schedule[-1])) - - def loglikelihood(theta, one_counts, all_counts): - loglik = 0 - for i, k in enumerate(result.evaluation_schedule): - loglik += np.log(np.sin((2 * k + 1) * theta) ** 2) * one_counts[i] - loglik += np.log(np.cos((2 * k + 1) * theta) ** 2) * (all_counts[i] - one_counts[i]) - return loglik - - one_counts = result.good_counts - all_counts = [result.shots] * len(one_counts) - - eps = 1e-15 # to avoid invalid value in log - thetas = np.linspace(0 + eps, np.pi / 2 - eps, nevals) - values = np.zeros(len(thetas)) - for i, theta in enumerate(thetas): - values[i] = loglikelihood(theta, one_counts, all_counts) - - loglik_mle = loglikelihood(result.theta, one_counts, all_counts) - chi2_quantile = chi2.ppf(1 - alpha, df=1) - thres = loglik_mle - chi2_quantile / 2 - - # the (outer) LR confidence interval - above_thres = thetas[values >= thres] - - # it might happen that the `above_thres` array is empty, - # to still provide a valid result use safe_min/max which - # then yield [0, pi/2] - confint = [_safe_min(above_thres, default=0), _safe_max(above_thres, default=np.pi / 2)] - mapped_confint = tuple(result.post_processing(np.sin(bound) ** 2) for bound in confint) - - return mapped_confint - - -def _get_counts( - circuit_results: Sequence[np.ndarray | list[float] | dict[str, int]], - estimation_problem: EstimationProblem, - num_state_qubits: int, -) -> tuple[list[float], list[int]]: - """Get the good and total counts. - - Returns: - A pair of two lists, ([1-counts per experiment], [shots per experiment]). - - Raises: - AlgorithmError: If self.run() has not been called yet. - """ - one_hits = [] # h_k: how often 1 has been measured, for a power Q^(m_k) - # shots_k: how often has been measured at a power Q^(m_k) - all_hits: np.ndarray | list[float] = [] - if all(isinstance(data, (list, np.ndarray)) for data in circuit_results): - probabilities = [] - num_qubits = int(np.log2(len(circuit_results[0]))) # the total number of qubits - for statevector in circuit_results: - p_k = 0.0 - for i, amplitude in enumerate(statevector): - probability = np.abs(amplitude) ** 2 - # consider only state qubits and revert bit order - bitstr = bin(i)[2:].zfill(num_qubits)[-num_state_qubits:][::-1] - objectives = [bitstr[index] for index in estimation_problem.objective_qubits] - if estimation_problem.is_good_state(objectives): - p_k += probability - probabilities += [p_k] - - one_hits = probabilities - all_hits = np.ones_like(one_hits) - else: - for counts in circuit_results: - all_hits.append(sum(counts.values())) - one_hits.append( - sum( - count - for bitstr, count in counts.items() - if estimation_problem.is_good_state(bitstr) - ) - ) - - return one_hits, all_hits diff --git a/qiskit/algorithms/aux_ops_evaluator.py b/qiskit/algorithms/aux_ops_evaluator.py deleted file mode 100644 index 788b66d6e43f..000000000000 --- a/qiskit/algorithms/aux_ops_evaluator.py +++ /dev/null @@ -1,195 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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. -"""Evaluator of auxiliary operators for algorithms.""" - -from __future__ import annotations - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.opflow import ( - CircuitSampler, - ListOp, - StateFn, - OperatorBase, - ExpectationBase, -) -from qiskit.providers import Backend -from qiskit.quantum_info import Statevector -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_func - -from .list_or_dict import ListOrDict - - -@deprecate_func( - additional_msg=( - "Instead, use the function " - "``qiskit.algorithms.observables_evaluator.estimate_observables``. See " - "https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", -) -def eval_observables( - quantum_instance: QuantumInstance | Backend, - quantum_state: Statevector | QuantumCircuit | OperatorBase, - observables: ListOrDict[OperatorBase], - expectation: ExpectationBase, - threshold: float = 1e-12, -) -> ListOrDict[tuple[complex, complex]]: - """ - Deprecated: Accepts a list or a dictionary of operators and calculates - their expectation values - means - and standard deviations. They are calculated with respect to a quantum state provided. A user - can optionally provide a threshold value which filters mean values falling below the threshold. - - This function has been superseded by the - :func:`qiskit.algorithms.observables_evaluator.eval_observables` function. - It will be deprecated in a future release and subsequently - removed after that. - - Args: - quantum_instance: A quantum instance used for calculations. - quantum_state: An unparametrized quantum circuit representing a quantum state that - expectation values are computed against. - observables: A list or a dictionary of operators whose expectation values are to be - calculated. - expectation: An instance of ExpectationBase which defines a method for calculating - expectation values. - threshold: A threshold value that defines which mean values should be neglected (helpful for - ignoring numerical instabilities close to 0). - - Returns: - A list or a dictionary of tuples (mean, standard deviation). - - Raises: - ValueError: If a ``quantum_state`` with free parameters is provided. - """ - - if ( - isinstance( - quantum_state, (QuantumCircuit, OperatorBase) - ) # Statevector cannot be parametrized - and len(quantum_state.parameters) > 0 - ): - raise ValueError( - "A parametrized representation of a quantum_state was provided. It is not " - "allowed - it cannot have free parameters." - ) - - # Create new CircuitSampler to avoid breaking existing one's caches. - sampler = CircuitSampler(quantum_instance) - - list_op = _prepare_list_op(quantum_state, observables) - observables_expect = expectation.convert(list_op) - observables_expect_sampled = sampler.convert(observables_expect) - - # compute means - values = np.real(observables_expect_sampled.eval()) - - # compute standard deviations - # We use sampler.quantum_instance to take care of case in which quantum_instance is Backend - std_devs = _compute_std_devs( - observables_expect_sampled, observables, expectation, sampler.quantum_instance - ) - - # Discard values below threshold - observables_means = values * (np.abs(values) > threshold) - # zip means and standard deviations into tuples - observables_results = list(zip(observables_means, std_devs)) - - # Return None eigenvalues for None operators if observables is a list. - # None operators are already dropped in compute_minimum_eigenvalue if observables is a dict. - - return _prepare_result(observables_results, observables) - - -def _prepare_list_op( - quantum_state: Statevector | QuantumCircuit | OperatorBase, - observables: ListOrDict[OperatorBase], -) -> ListOp: - """ - Accepts a list or a dictionary of operators and converts them to a ``ListOp``. - - Args: - quantum_state: An unparametrized quantum circuit representing a quantum state that - expectation values are computed against. - observables: A list or a dictionary of operators. - - Returns: - A ``ListOp`` that includes all provided observables. - """ - if isinstance(observables, dict): - observables = list(observables.values()) - - if not isinstance(quantum_state, StateFn): - quantum_state = StateFn(quantum_state) - - return ListOp([StateFn(obs, is_measurement=True).compose(quantum_state) for obs in observables]) - - -def _prepare_result( - observables_results: list[tuple[complex, complex]], - observables: ListOrDict[OperatorBase], -) -> ListOrDict[tuple[complex, complex]]: - """ - Prepares a list or a dictionary of eigenvalues from ``observables_results`` and - ``observables``. - - Args: - observables_results: A list of of tuples (mean, standard deviation). - observables: A list or a dictionary of operators whose expectation values are to be - calculated. - - Returns: - A list or a dictionary of tuples (mean, standard deviation). - """ - if isinstance(observables, list): - observables_eigenvalues: ListOrDict[tuple[complex, complex]] = [None] * len(observables) - key_value_iterator = enumerate(observables_results) - else: - observables_eigenvalues = {} - key_value_iterator = zip(observables.keys(), observables_results) - for key, value in key_value_iterator: - if observables[key] is not None: - observables_eigenvalues[key] = value - return observables_eigenvalues - - -def _compute_std_devs( - observables_expect_sampled: OperatorBase, - observables: ListOrDict[OperatorBase], - expectation: ExpectationBase, - quantum_instance: QuantumInstance | Backend, -) -> list[complex]: - """ - Calculates a list of standard deviations from expectation values of observables provided. - - Args: - observables_expect_sampled: Expected values of observables. - observables: A list or a dictionary of operators whose expectation values are to be - calculated. - expectation: An instance of ExpectationBase which defines a method for calculating - expectation values. - quantum_instance: A quantum instance used for calculations. - - Returns: - A list of standard deviations. - """ - variances = np.real(expectation.compute_variance(observables_expect_sampled)) - if not isinstance(variances, np.ndarray) and variances == 0.0: - # when `variances` is a single value equal to 0., our expectation value is exact and we - # manually ensure the variances to be a list of the correct length - variances = np.zeros(len(observables), dtype=float) - # TODO: this will crash if quantum_instance is a backend - std_devs = np.sqrt(variances / quantum_instance.run_config.shots) - return std_devs diff --git a/qiskit/algorithms/eigen_solvers/__init__.py b/qiskit/algorithms/eigen_solvers/__init__.py deleted file mode 100644 index 90cab015b8f5..000000000000 --- a/qiskit/algorithms/eigen_solvers/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Eigen Solvers Package""" - -from .numpy_eigen_solver import NumPyEigensolver -from .eigen_solver import Eigensolver, EigensolverResult -from .vqd import VQD, VQDResult - -__all__ = ["NumPyEigensolver", "Eigensolver", "EigensolverResult", "VQD", "VQDResult"] diff --git a/qiskit/algorithms/eigen_solvers/eigen_solver.py b/qiskit/algorithms/eigen_solvers/eigen_solver.py deleted file mode 100644 index 5fd59b3e023c..000000000000 --- a/qiskit/algorithms/eigen_solvers/eigen_solver.py +++ /dev/null @@ -1,134 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# 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. - -"""The Eigensolver interface""" -from __future__ import annotations -from abc import ABC, abstractmethod - -import numpy as np - -from qiskit.opflow import OperatorBase -from qiskit.utils.deprecation import deprecate_func -from ..algorithm_result import AlgorithmResult -from ..list_or_dict import ListOrDict - - -class Eigensolver(ABC): - """Deprecated: Eigensolver Interface. - - The Eigensolver interface has been superseded by the - :class:`qiskit.algorithms.eigensolvers.Eigensolver` interface. - This interface will be deprecated in a future release and subsequently - removed after that. - - Algorithms that can compute eigenvalues for an operator - may implement this interface to allow different algorithms to be - used interchangeably. - """ - - @deprecate_func( - additional_msg=( - "Instead, use the interface ``qiskit.algorithms.eigensolvers.Eigensolver``. See " - "https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - pass - - @abstractmethod - def compute_eigenvalues( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> "EigensolverResult": - """ - Computes eigenvalues. Operator and aux_operators can be supplied here and - if not None will override any already set into algorithm so it can be reused with - different operators. While an operator is required by algorithms, aux_operators - are optional. To 'remove' a previous aux_operators array use an empty list here. - - Args: - operator: Qubit operator of the Observable - aux_operators: Optional list of auxiliary operators to be evaluated with the - eigenstate of the minimum eigenvalue main result and their expectation values - returned. For instance in chemistry these can be dipole operators, total particle - count operators so we can get values for these at the ground state. - - Returns: - EigensolverResult - """ - return EigensolverResult() - - @classmethod - def supports_aux_operators(cls) -> bool: - """Whether computing the expectation value of auxiliary operators is supported. - - Returns: - True if aux_operator expectations can be evaluated, False otherwise - """ - return False - - -class EigensolverResult(AlgorithmResult): - """Deprecated: Eigensolver Result. - - The EigensolverResult class has been superseded by the - :class:`qiskit.algorithms.eigensolvers.EigensolverResult` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.eigensolvers.EigensolverResult``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - super().__init__() - self._eigenvalues: np.ndarray | None = None - self._eigenstates: np.ndarray | None = None - self._aux_operator_eigenvalues: list[ListOrDict[tuple[complex, complex]]] | None = None - - @property - def eigenvalues(self) -> np.ndarray | None: - """returns eigen values""" - return self._eigenvalues - - @eigenvalues.setter - def eigenvalues(self, value: np.ndarray) -> None: - """set eigen values""" - self._eigenvalues = value - - @property - def eigenstates(self) -> np.ndarray | None: - """return eigen states""" - return self._eigenstates - - @eigenstates.setter - def eigenstates(self, value: np.ndarray) -> None: - """set eigen states""" - self._eigenstates = value - - @property - def aux_operator_eigenvalues(self) -> list[ListOrDict[tuple[complex, complex]]] | None: - """Return aux operator expectation values. - - These values are in fact tuples formatted as (mean, standard deviation). - """ - return self._aux_operator_eigenvalues - - @aux_operator_eigenvalues.setter - def aux_operator_eigenvalues(self, value: list[ListOrDict[tuple[complex, complex]]]) -> None: - """set aux operator eigen values""" - self._aux_operator_eigenvalues = value diff --git a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py deleted file mode 100644 index 6b0536330441..000000000000 --- a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py +++ /dev/null @@ -1,278 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""The Eigensolver algorithm.""" -from __future__ import annotations - -import logging -import warnings -from collections.abc import Callable - -import numpy as np -from scipy import sparse as scisparse - -from qiskit.opflow import I, ListOp, OperatorBase, StateFn -from qiskit.utils.validation import validate_min -from qiskit.utils.deprecation import deprecate_func -from ..exceptions import AlgorithmError -from .eigen_solver import Eigensolver, EigensolverResult -from ..list_or_dict import ListOrDict - -logger = logging.getLogger(__name__) - - -class NumPyEigensolver(Eigensolver): - r""" - Deprecated: NumPy Eigensolver algorithm. - - The NumPyEigensolver class has been superseded by the - :class:`qiskit.algorithms.eigensolvers.NumPyEigensolver` class. - This class will be deprecated in a future release and subsequently - removed after that. - - NumPy Eigensolver computes up to the first :math:`k` eigenvalues of a complex-valued square - matrix of dimension :math:`n \times n`, with :math:`k \leq n`. - - Note: - Operators are automatically converted to SciPy's ``spmatrix`` - as needed and this conversion can be costly in terms of memory and performance as the - operator size, mostly in terms of number of qubits it represents, gets larger. - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.eigensolvers.NumPyEigensolver``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - k: int = 1, - filter_criterion: Callable[ - [list | np.ndarray, float, ListOrDict[float] | None], bool - ] = None, - ) -> None: - """ - Args: - k: How many eigenvalues are to be computed, has a min. value of 1. - filter_criterion: callable that allows to filter eigenvalues/eigenstates, only feasible - eigenstates are returned in the results. The callable has the signature - `filter(eigenstate, eigenvalue, aux_values)` and must return a boolean to indicate - whether to keep this value in the final returned result or not. If the number of - elements that satisfies the criterion is smaller than `k` then the returned list has - fewer elements and can even be empty. - """ - validate_min("k", k, 1) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - - self._in_k = k - self._k = k - - self._filter_criterion = filter_criterion - - self._ret = EigensolverResult() - - @property - def k(self) -> int: - """returns k (number of eigenvalues requested)""" - return self._in_k - - @k.setter - def k(self, k: int) -> None: - """set k (number of eigenvalues requested)""" - validate_min("k", k, 1) - self._in_k = k - self._k = k - - @property - def filter_criterion( - self, - ) -> Callable[[list | np.ndarray, float, ListOrDict[float] | None], bool] | None: - """returns the filter criterion if set""" - return self._filter_criterion - - @filter_criterion.setter - def filter_criterion( - self, - filter_criterion: Callable[[list | np.ndarray, float, ListOrDict[float] | None], bool] - | None, - ) -> None: - """set the filter criterion""" - self._filter_criterion = filter_criterion - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def _check_set_k(self, operator: OperatorBase) -> None: - if operator is not None: - if self._in_k > 2**operator.num_qubits: - self._k = 2**operator.num_qubits - logger.debug( - "WARNING: Asked for %s eigenvalues but max possible is %s.", self._in_k, self._k - ) - else: - self._k = self._in_k - - def _solve(self, operator: OperatorBase) -> None: - sp_mat = operator.to_spmatrix() - # If matrix is diagonal, the elements on the diagonal are the eigenvalues. Solve by sorting. - if scisparse.csr_matrix(sp_mat.diagonal()).nnz == sp_mat.nnz: - diag = sp_mat.diagonal() - indices = np.argsort(diag)[: self._k] - eigval = diag[indices] - eigvec = np.zeros((sp_mat.shape[0], self._k)) - for i, idx in enumerate(indices): - eigvec[idx, i] = 1.0 - else: - if self._k >= 2**operator.num_qubits - 1: - logger.debug("SciPy doesn't support to get all eigenvalues, using NumPy instead.") - if operator.is_hermitian(): - eigval, eigvec = np.linalg.eigh(operator.to_matrix()) - else: - eigval, eigvec = np.linalg.eig(operator.to_matrix()) - else: - if operator.is_hermitian(): - eigval, eigvec = scisparse.linalg.eigsh(sp_mat, k=self._k, which="SA") - else: - eigval, eigvec = scisparse.linalg.eigs(sp_mat, k=self._k, which="SR") - indices = np.argsort(eigval)[: self._k] - eigval = eigval[indices] - eigvec = eigvec[:, indices] - self._ret.eigenvalues = eigval - self._ret.eigenstates = eigvec.T - - def _get_ground_state_energy(self, operator: OperatorBase) -> None: - if self._ret.eigenvalues is None or self._ret.eigenstates is None: - self._solve(operator) - - def _get_energies( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None - ) -> None: - if self._ret.eigenvalues is None or self._ret.eigenstates is None: - self._solve(operator) - - if aux_operators is not None: - aux_op_vals = [] - for i in range(self._k): - aux_op_vals.append( - self._eval_aux_operators(aux_operators, self._ret.eigenstates[i]) - ) - self._ret.aux_operator_eigenvalues = aux_op_vals - - @staticmethod - def _eval_aux_operators( - aux_operators: ListOrDict[OperatorBase], wavefn, threshold: float = 1e-12 - ) -> ListOrDict[tuple[complex, complex]]: - - values: ListOrDict[tuple[complex, complex]] - - # As a list, aux_operators can contain None operators for which None values are returned. - # As a dict, the None operators in aux_operators have been dropped in compute_eigenvalues. - if isinstance(aux_operators, list): - values = [None] * len(aux_operators) - key_op_iterator = enumerate(aux_operators) - else: - values = {} - key_op_iterator = aux_operators.items() - for key, operator in key_op_iterator: - if operator is None: - continue - value = 0.0 - if operator.coeff != 0: - mat = operator.to_spmatrix() - # Terra doesn't support sparse yet, so do the matmul directly if so - # This is necessary for the particle_hole and other chemistry tests because the - # pauli conversions are 2^12th large and will OOM error if not sparse. - if isinstance(mat, scisparse.spmatrix): - value = mat.dot(wavefn).dot(np.conj(wavefn)) - else: - value = StateFn(operator, is_measurement=True).eval(wavefn) - value = value if np.abs(value) > threshold else 0.0 - # The value get's wrapped into a tuple: (mean, standard deviation). - # Since this is an exact computation, the standard deviation is known to be zero. - values[key] = (value, 0.0) - return values - - def compute_eigenvalues( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> EigensolverResult: - super().compute_eigenvalues(operator, aux_operators) - - if operator is None: - raise AlgorithmError("Operator was never provided") - - self._check_set_k(operator) - zero_op = I.tensorpower(operator.num_qubits) * 0.0 - if isinstance(aux_operators, list) and len(aux_operators) > 0: - # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. - aux_operators = [zero_op if op == 0 else op for op in aux_operators] - elif isinstance(aux_operators, dict) and len(aux_operators) > 0: - aux_operators = { - key: zero_op if op == 0 else op # Convert zero values to zero operators - for key, op in aux_operators.items() - if op is not None # Discard None values - } - else: - aux_operators = None - - k_orig = self._k - if self._filter_criterion: - # need to consider all elements if a filter is set - self._k = 2**operator.num_qubits - - self._ret = EigensolverResult() - self._solve(operator) - - # compute energies before filtering, as this also evaluates the aux operators - self._get_energies(operator, aux_operators) - - # if a filter is set, loop over the given values and only keep - if self._filter_criterion: - - eigvecs = [] - eigvals = [] - aux_ops = [] - cnt = 0 - for i in range(len(self._ret.eigenvalues)): - eigvec = self._ret.eigenstates[i] - eigval = self._ret.eigenvalues[i] - if self._ret.aux_operator_eigenvalues is not None: - aux_op = self._ret.aux_operator_eigenvalues[i] - else: - aux_op = None - if self._filter_criterion(eigvec, eigval, aux_op): - cnt += 1 - eigvecs += [eigvec] - eigvals += [eigval] - if self._ret.aux_operator_eigenvalues is not None: - aux_ops += [aux_op] - if cnt == k_orig: - break - - self._ret.eigenstates = np.array(eigvecs) - self._ret.eigenvalues = np.array(eigvals) - # conversion to np.array breaks in case of aux_ops - self._ret.aux_operator_eigenvalues = aux_ops - - self._k = k_orig - - # evaluate ground state after filtering (in case a filter is set) - self._get_ground_state_energy(operator) - if self._ret.eigenstates is not None: - self._ret.eigenstates = ListOp([StateFn(vec) for vec in self._ret.eigenstates]) - - logger.debug("EigensolverResult:\n%s", self._ret) - return self._ret diff --git a/qiskit/algorithms/eigen_solvers/vqd.py b/qiskit/algorithms/eigen_solvers/vqd.py deleted file mode 100644 index 068cdd08cd14..000000000000 --- a/qiskit/algorithms/eigen_solvers/vqd.py +++ /dev/null @@ -1,807 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""The Variational Quantum Deflation Algorithm for computing higher energy states. - -See https://arxiv.org/abs/1805.08138. -""" -from __future__ import annotations - -import logging -import warnings -from collections.abc import Callable -from time import time -import numpy as np - -from qiskit.circuit import QuantumCircuit, Parameter -from qiskit.circuit.library import RealAmplitudes -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.providers import Backend -from qiskit.opflow import ( - OperatorBase, - ExpectationBase, - ExpectationFactory, - StateFn, - CircuitStateFn, - ListOp, - CircuitSampler, - PauliSumOp, -) -from qiskit.opflow.gradients import GradientBase -from qiskit.utils.validation import validate_min -from qiskit.utils.backend_utils import is_aer_provider -from qiskit.utils.deprecation import deprecate_func -from qiskit.utils import QuantumInstance -from ..list_or_dict import ListOrDict -from ..optimizers import Optimizer, SLSQP, Minimizer -from ..variational_algorithm import VariationalAlgorithm, VariationalResult -from .eigen_solver import Eigensolver, EigensolverResult -from ..minimum_eigen_solvers.vqe import _validate_bounds, _validate_initial_point -from ..exceptions import AlgorithmError -from ..aux_ops_evaluator import eval_observables - -logger = logging.getLogger(__name__) - - -class VQD(VariationalAlgorithm, Eigensolver): - r"""Deprecated: Variational Quantum Deflation algorithm. - - The VQD class has been superseded by the - :class:`qiskit.algorithms.eigensolvers.VQD` class. - This class will be deprecated in a future release and subsequently - removed after that. - - `VQD `__ is a quantum algorithm that uses a - variational technique to find - the k eigenvalues of the Hamiltonian :math:`H` of a given system. - - The algorithm computes excited state energies of generalised hamiltonians - by optimising over a modified cost function where each succesive eigen value - is calculated iteratively by introducing an overlap term with all - the previously computed eigenstaes that must be minimised, thus ensuring - higher energy eigen states are found. - - An instance of VQD requires defining three algorithmic sub-components: - an integer k denoting the number of eigenstates to calculate, a trial - state (a.k.a. ansatz)which is a :class:`QuantumCircuit`, - and one of the classical :mod:`~qiskit.algorithms.optimizers`. - The ansatz is varied, via its set of parameters, by the optimizer, - such that it works towards a state, as determined by the parameters - applied to the ansatz, that will result in the minimum expectation values - being measured of the input operator (Hamiltonian). The algorithm does - this by iteratively refining each excited state to be orthogonal to all - the previous excited states. - - An optional array of parameter values, via the *initial_point*, may be provided as the - starting point for the search of the minimum eigenvalue. This feature is particularly useful - such as when there are reasons to believe that the solution point is close to a particular - point. - - The length of the *initial_point* list value must match the number of the parameters - expected by the ansatz being used. If the *initial_point* is left at the default - of ``None``, then VQD will look to the ansatz for a preferred value, based on its - given initial state. If the ansatz returns ``None``, - then a random point will be generated within the parameter bounds set, as per above. - If the ansatz provides ``None`` as the lower bound, then VQD - will default it to :math:`-2\pi`; similarly, if the ansatz returns ``None`` - as the upper bound, the default value will be :math:`2\pi`. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.eigensolvers.VQD``." - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - ansatz: QuantumCircuit | None = None, - k: int = 2, - betas: list[float] | None = None, - optimizer: Optimizer | Minimizer | None = None, - initial_point: np.ndarray | None = None, - gradient: GradientBase | Callable | None = None, - expectation: ExpectationBase | None = None, - include_custom: bool = False, - max_evals_grouped: int = 1, - callback: Callable[[int, np.ndarray, float, float, int], None] | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - ) -> None: - """ - - Args: - ansatz: A parameterized circuit used as ansatz for the wave function. - k: the number of eigenvalues to return. Returns the lowest k eigenvalues. - betas: beta parameters in the VQD paper. - Should have length k - 1, with k the number of excited states. - These hyperparameters balance the contribution of each overlap term to the cost - function and have a default value computed as the mean square sum of the - coefficients of the observable. - optimizer: A classical optimizer. Can either be a Qiskit optimizer or a callable - that takes an array as input and returns a Qiskit or SciPy optimization result. - initial_point: An optional initial point (i.e. initial parameter values) - for the optimizer. If ``None`` then VQD will look to the ansatz for a preferred - point and if not will simply compute a random one. - gradient: An optional gradient function or operator for optimizer. - Only used to compute the ground state at the moment. - expectation: The Expectation converter for taking the average value of the - Observable over the ansatz state function. When ``None`` (the default) an - :class:`~qiskit.opflow.expectations.ExpectationFactory` is used to select - an appropriate expectation based on the operator and backend. When using Aer - qasm_simulator backend, with paulis, it is however much faster to leverage custom - Aer function for the computation but, although VQD performs much faster - with it, the outcome is ideal, with no shot noise, like using a state vector - simulator. If you are just looking for the quickest performance when choosing Aer - qasm_simulator and the lack of shot noise is not an issue then set `include_custom` - parameter here to ``True`` (defaults to ``False``). - include_custom: When `expectation` parameter here is None setting this to ``True`` will - allow the factory to include the custom Aer pauli expectation. - max_evals_grouped: Max number of evaluations performed simultaneously. Signals the - given optimizer that more than one set of parameters can be supplied so that - multiple points to compute the gradient can be passed and if computed in parallel - potentially the expectation values can be computed in parallel. Typically this is - possible when a finite difference gradient is used by the optimizer such that - improve overall execution time. Deprecated if a gradient operator or function is - given. - callback: a callback that can access the intermediate data during the optimization. - Four parameter values are passed to the callback as follows during each evaluation - by the optimizer for its current set of parameters as it works towards the minimum. - These are: the evaluation count, the optimizer parameters for the ansatz, the - evaluated mean, the evaluated standard deviation, and the current step. - quantum_instance: Quantum Instance or Backend - - """ - validate_min("max_evals_grouped", max_evals_grouped, 1) - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - - self._max_evals_grouped = max_evals_grouped - self._circuit_sampler: CircuitSampler | None = None - self._expectation = None - self.expectation = expectation - self._include_custom = include_custom - - # set ansatz -- still supporting pre 0.18.0 sorting - - self._ansatz: QuantumCircuit | None = None - self.ansatz = ansatz - - self.k = k - self.betas = betas - - self._optimizer: Optimizer | None = None - self.optimizer = optimizer - - self._initial_point: np.ndarray | None = None - self.initial_point = initial_point - self._gradient: GradientBase | Callable | None = None - self.gradient = gradient - self._quantum_instance: QuantumInstance | None = None - - if quantum_instance is not None: - self.quantum_instance = quantum_instance - - self._eval_time = None - self._eval_count = 0 - self._callback: Callable[[int, np.ndarray, float, float, int], None] | None = None - self.callback = callback - - logger.info(self.print_settings()) - - @property - def ansatz(self) -> QuantumCircuit: - """Returns the ansatz.""" - return self._ansatz - - @ansatz.setter - def ansatz(self, ansatz: QuantumCircuit | None): - """Sets the ansatz. - - Args: - ansatz: The parameterized circuit used as an ansatz. - If None is passed, RealAmplitudes is used by default. - - """ - if ansatz is None: - ansatz = RealAmplitudes() - - self._ansatz = ansatz - - @property - def gradient(self) -> GradientBase | Callable | None: - """Returns the gradient.""" - return self._gradient - - @gradient.setter - def gradient(self, gradient: GradientBase | Callable | None): - """Sets the gradient.""" - self._gradient = gradient - - @property - def quantum_instance(self) -> QuantumInstance | None: - """Returns quantum instance.""" - return self._quantum_instance - - @quantum_instance.setter - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Sets a quantum_instance.""" - if not isinstance(quantum_instance, QuantumInstance): - quantum_instance = QuantumInstance(quantum_instance) - - self._quantum_instance = quantum_instance - self._circuit_sampler = CircuitSampler( - quantum_instance, param_qobj=is_aer_provider(quantum_instance.backend) - ) - - @property - def initial_point(self) -> np.ndarray | None: - """Returns initial point.""" - return self._initial_point - - @initial_point.setter - def initial_point(self, initial_point: np.ndarray): - """Sets initial point""" - self._initial_point = initial_point - - @property - def max_evals_grouped(self) -> int: - """Returns max_evals_grouped""" - return self._max_evals_grouped - - @max_evals_grouped.setter - def max_evals_grouped(self, max_evals_grouped: int): - """Sets max_evals_grouped""" - self._max_evals_grouped = max_evals_grouped - self.optimizer.set_max_evals_grouped(max_evals_grouped) - - @property - def include_custom(self) -> bool: - """Returns include_custom""" - return self._include_custom - - @include_custom.setter - def include_custom(self, include_custom: bool): - """Sets include_custom. If set to another value than the one that was previsously set, - the expectation attribute is reset to None. - """ - if include_custom != self._include_custom: - self._include_custom = include_custom - self.expectation = None - - @property - def callback(self) -> Callable[[int, np.ndarray, float, float, int], None] | None: - """Returns callback""" - return self._callback - - @callback.setter - def callback(self, callback: Callable[[int, np.ndarray, float, float, int], None] | None): - """Sets callback""" - self._callback = callback - - @property - def expectation(self) -> ExpectationBase | None: - """The expectation value algorithm used to construct the expectation measurement from - the observable.""" - return self._expectation - - @expectation.setter - def expectation(self, exp: ExpectationBase | None) -> None: - self._expectation = exp - - def _check_operator_ansatz(self, operator: OperatorBase): - """Check that the number of qubits of operator and ansatz match.""" - if operator is not None and self.ansatz is not None: - if operator.num_qubits != self.ansatz.num_qubits: - # try to set the number of qubits on the ansatz, if possible - try: - self.ansatz.num_qubits = operator.num_qubits - except AttributeError as ex: - raise AlgorithmError( - "The number of qubits of the ansatz does not match the " - "operator, and the ansatz does not allow setting the " - "number of qubits using `num_qubits`." - ) from ex - - @property - def optimizer(self) -> Optimizer: - """Returns optimizer""" - return self._optimizer - - @optimizer.setter - def optimizer(self, optimizer: Optimizer | None): - """Sets the optimizer attribute. - - Args: - optimizer: The optimizer to be used. If None is passed, SLSQP is used by default. - - """ - if optimizer is None: - optimizer = SLSQP() - - if isinstance(optimizer, Optimizer): - optimizer.set_max_evals_grouped(self.max_evals_grouped) - - self._optimizer = optimizer - - @property - def setting(self): - """Prepare the setting of VQD as a string.""" - ret = f"Algorithm: {self.__class__.__name__}\n" - params = "" - for key, value in self.__dict__.items(): - if key[0] == "_": - if "initial_point" in key and value is None: - params += "-- {}: {}\n".format(key[1:], "Random seed") - else: - params += f"-- {key[1:]}: {value}\n" - ret += f"{params}" - return ret - - def print_settings(self): - """Preparing the setting of VQD into a string. - - Returns: - str: the formatted setting of VQD. - """ - ret = "\n" - ret += "==================== Setting of {} ============================\n".format( - self.__class__.__name__ - ) - ret += f"{self.setting}" - ret += "===============================================================\n" - if self.ansatz is not None: - ret += "{}".format(self.ansatz.draw(output="text")) - else: - ret += "ansatz has not been set" - ret += "===============================================================\n" - ret += f"{self._optimizer.setting}" - ret += "===============================================================\n" - return ret - - def construct_expectation( - self, - parameter: list[float] | list[Parameter] | np.ndarray, - operator: OperatorBase, - return_expectation: bool = False, - ) -> OperatorBase | tuple[OperatorBase, ExpectationBase]: - r""" - Generate the ansatz circuit and expectation value measurement, and return their - runnable composition. - - Args: - parameter: Parameters for the ansatz circuit. - operator: Qubit operator of the Observable - return_expectation: If True, return the ``ExpectationBase`` expectation converter used - in the construction of the expectation value. Useful e.g. to compute the standard - deviation of the expectation value. - - Returns: - The Operator equalling the measurement of the ansatz :class:`StateFn` by the - Observable's expectation :class:`StateFn`, and, optionally, the expectation converter. - - Raises: - AlgorithmError: If no operator has been provided. - AlgorithmError: If no expectation is passed and None could be inferred via the - ExpectationFactory. - """ - if operator is None: - raise AlgorithmError("The operator was never provided.") - - self._check_operator_ansatz(operator) - - # if expectation was never created, try to create one - if self.expectation is None: - expectation = ExpectationFactory.build( - operator=operator, - backend=self.quantum_instance, - include_custom=self._include_custom, - ) - else: - expectation = self.expectation - - wave_function = self.ansatz.assign_parameters(parameter) - - observable_meas = expectation.convert(StateFn(operator, is_measurement=True)) - ansatz_circuit_op = CircuitStateFn(wave_function) - expect_op = observable_meas.compose(ansatz_circuit_op).reduce() - - if return_expectation: - return expect_op, expectation - - return expect_op - - def construct_circuit( - self, - parameter: list[float] | list[Parameter] | np.ndarray, - operator: OperatorBase, - ) -> list[QuantumCircuit]: - """Return the circuits used to compute the expectation value. - - Args: - parameter: Parameters for the ansatz circuit. - operator: Qubit operator of the Observable - - Returns: - A list of the circuits used to compute the expectation value. - """ - expect_op = self.construct_expectation(parameter, operator).to_circuit_op() - - circuits = [] - - # recursively extract circuits - def extract_circuits(op): - if isinstance(op, CircuitStateFn): - circuits.append(op.primitive) - elif isinstance(op, ListOp): - for op_i in op.oplist: - extract_circuits(op_i) - - extract_circuits(expect_op) - - return circuits - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def _eval_aux_ops( - self, - parameters: np.ndarray, - aux_operators: ListOrDict[OperatorBase], - expectation: ExpectationBase, - threshold: float = 1e-12, - ) -> ListOrDict[tuple[complex, complex]]: - # Create new CircuitSampler to avoid breaking existing one's caches. - sampler = CircuitSampler(self.quantum_instance) - - if isinstance(aux_operators, dict): - list_op = ListOp(list(aux_operators.values())) - else: - list_op = ListOp(aux_operators) - - aux_op_meas = expectation.convert(StateFn(list_op, is_measurement=True)) - aux_op_expect = aux_op_meas.compose( - CircuitStateFn(self.ansatz.assign_parameters(parameters)) - ) - aux_op_expect_sampled = sampler.convert(aux_op_expect) - - # compute means - values = np.real(aux_op_expect_sampled.eval()) - - # compute standard deviations - variances = np.real(expectation.compute_variance(aux_op_expect_sampled)) - if not isinstance(variances, np.ndarray) and variances == 0.0: - # when `variances` is a single value equal to 0., our expectation value is exact and we - # manually ensure the variances to be a list of the correct length - variances = np.zeros(len(aux_operators), dtype=float) - std_devs = np.sqrt(variances / self.quantum_instance.run_config.shots) - - # Discard values below threshold - aux_op_means = values * (np.abs(values) > threshold) - # zip means and standard deviations into tuples - aux_op_results = zip(aux_op_means, std_devs) - - # Return None eigenvalues for None operators if aux_operators is a list. - # None operators are already dropped in compute_minimum_eigenvalue if aux_operators is a - # dict. - if isinstance(aux_operators, list): - aux_operator_eigenvalues: ListOrDict[tuple[complex, complex]] = [None] * len( - aux_operators - ) - key_value_iterator = enumerate(aux_op_results) - else: - aux_operator_eigenvalues = {} - key_value_iterator = zip(aux_operators.keys(), aux_op_results) - - for key, value in key_value_iterator: - if aux_operators[key] is not None: - aux_operator_eigenvalues[key] = value - - return aux_operator_eigenvalues - - def compute_eigenvalues( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> EigensolverResult: - super().compute_eigenvalues(operator, aux_operators) - - if self.quantum_instance is None: - raise AlgorithmError( - "A QuantumInstance or Backend must be supplied to run the quantum algorithm." - ) - self.quantum_instance.circuit_summary = True - - # this sets the size of the ansatz, so it must be called before the initial point - # validation - self._check_operator_ansatz(operator) - - # set an expectation for this algorithm run (will be reset to None at the end) - initial_point = _validate_initial_point(self.initial_point, self.ansatz) - - bounds = _validate_bounds(self.ansatz) - # We need to handle the array entries being zero or Optional i.e. having value None - if aux_operators: - zero_op = PauliSumOp.from_list([("I" * self.ansatz.num_qubits, 0)]) - - # Convert the None and zero values when aux_operators is a list. - # Drop None and convert zero values when aux_operators is a dict. - if isinstance(aux_operators, list): - key_op_iterator = enumerate(aux_operators) - converted: ListOrDict[OperatorBase] = [zero_op] * len(aux_operators) - else: - key_op_iterator = aux_operators.items() - converted = {} - for key, op in key_op_iterator: - if op is not None: - converted[key] = zero_op if op == 0 else op - - aux_operators = converted - - else: - aux_operators = None - - if self.betas is None: - upper_bound = ( - abs(operator.coeff) - if isinstance(operator, PauliOp) - else abs(operator.coeff) * sum(abs(operation.coeff) for operation in operator) - ) - self.betas = [upper_bound * 10] * (self.k) - logger.info("beta autoevaluated to %s", self.betas[0]) - - result = VQDResult() - result.optimal_point = [] - result.optimal_parameters = [] - result.optimal_value = [] - result.cost_function_evals = [] - result.optimizer_time = [] - result.eigenvalues = [] - result.eigenstates = [] - - if aux_operators is not None: - aux_values = [] - - for step in range(1, self.k + 1): - - self._eval_count = 0 - energy_evaluation, expectation = self.get_energy_evaluation( - step, operator, return_expectation=True, prev_states=result.optimal_parameters - ) - - # Convert the gradient operator into a callable function that is compatible with the - # optimization routine. Only used for the ground state currently as Gradient() doesnt - # support SumOps yet - if isinstance(self._gradient, GradientBase): - gradient = self._gradient.gradient_wrapper( - StateFn(operator, is_measurement=True) @ StateFn(self.ansatz), - bind_params=list(self.ansatz.parameters), - backend=self._quantum_instance, - ) - else: - gradient = self._gradient - - start_time = time() - - if callable(self.optimizer): - opt_result = self.optimizer( # pylint: disable=not-callable - fun=energy_evaluation, x0=initial_point, jac=gradient, bounds=bounds - ) - else: - opt_result = self.optimizer.minimize( - fun=energy_evaluation, x0=initial_point, jac=gradient, bounds=bounds - ) - - eval_time = time() - start_time - - result.optimal_point.append(opt_result.x) - result.optimal_parameters.append(dict(zip(self.ansatz.parameters, opt_result.x))) - result.optimal_value.append(opt_result.fun) - result.cost_function_evals.append(opt_result.nfev) - result.optimizer_time.append(eval_time) - - eigenvalue = ( - StateFn(operator, is_measurement=True) - .compose( - CircuitStateFn(self.ansatz.assign_parameters(result.optimal_parameters[-1])) - ) - .reduce() - .eval() - ) - - result.eigenvalues.append(eigenvalue) - result.eigenstates.append(self._get_eigenstate(result.optimal_parameters[-1])) - - if aux_operators is not None: - bound_ansatz = self.ansatz.assign_parameters(result.optimal_point[-1]) - aux_value = eval_observables( - self.quantum_instance, bound_ansatz, aux_operators, expectation=expectation - ) - aux_values.append(aux_value) - - if step == 1: - - logger.info( - "Ground state optimization complete in %s seconds.\n" - "Found opt_params %s in %s evals", - eval_time, - result.optimal_point, - self._eval_count, - ) - else: - logger.info( - ( - "%s excited state optimization complete in %s s.\n" - "Found opt_params %s in %s evals" - ), - str(step - 1), - eval_time, - result.optimal_point, - self._eval_count, - ) - - # To match the signature of NumpyEigenSolver Result - result.eigenstates = ListOp([StateFn(vec) for vec in result.eigenstates]) - result.eigenvalues = np.array(result.eigenvalues) - result.optimal_point = np.array(result.optimal_point) - result.optimal_value = np.array(result.optimal_value) - result.cost_function_evals = np.array(result.cost_function_evals) - result.optimizer_time = np.array(result.optimizer_time) - - if aux_operators is not None: - result.aux_operator_eigenvalues = aux_values - - return result - - def get_energy_evaluation( - self, - step: int, - operator: OperatorBase, - return_expectation: bool = False, - prev_states: list[np.ndarray] | None = None, - ) -> Callable[[np.ndarray], float | list[float]] | tuple[ - Callable[[np.ndarray], float | list[float]], ExpectationBase - ]: - """Returns a function handle to evaluates the energy at given parameters for the ansatz. - - This return value is the objective function to be passed to the optimizer for evaluation. - - Args: - step: level of energy being calculated. 0 for ground, 1 for first excited state... - operator: The operator whose energy to evaluate. - return_expectation: If True, return the ``ExpectationBase`` expectation converter used - in the construction of the expectation value. Useful e.g. to evaluate other - operators with the same expectation value converter. - prev_states: List of parameters from previous rounds of optimization. - - - Returns: - A callable that computes and returns the energy of the hamiltonian - of each parameter, and, optionally, the expectation - - Raises: - RuntimeError: If the circuit is not parameterized (i.e. has 0 free parameters). - AlgorithmError: If operator was not provided. - - """ - - num_parameters = self.ansatz.num_parameters - if num_parameters == 0: - raise RuntimeError("The ansatz must be parameterized, but has 0 free parameters.") - - if operator is None: - raise AlgorithmError("The operator was never provided.") - - if step > 1 and (len(prev_states) + 1) != step: - raise RuntimeError( - f"Passed previous states of the wrong size." - f"Passed array has length {str(len(prev_states))}" - ) - - self._check_operator_ansatz(operator) - overlap_op = [] - - ansatz_params = self.ansatz.parameters - expect_op, expectation = self.construct_expectation( - ansatz_params, operator, return_expectation=True - ) - - for state in range(step - 1): - - prev_circ = self.ansatz.assign_parameters(prev_states[state]) - overlap_op.append(~CircuitStateFn(prev_circ) @ CircuitStateFn(self.ansatz)) - - def energy_evaluation(parameters): - parameter_sets = np.reshape(parameters, (-1, num_parameters)) - # Dict associating each parameter with the lists of parameterization values for it - param_bindings = dict(zip(ansatz_params, parameter_sets.transpose().tolist())) - - sampled_expect_op = self._circuit_sampler.convert(expect_op, params=param_bindings) - means = np.real(sampled_expect_op.eval()) - - for state in range(step - 1): - sampled_final_op = self._circuit_sampler.convert( - overlap_op[state], params=param_bindings - ) - cost = sampled_final_op.eval() - means += np.real(self.betas[state] * np.conj(cost) * cost) - - if self._callback is not None: - variance = np.real(expectation.compute_variance(sampled_expect_op)) - estimator_error = np.sqrt(variance / self.quantum_instance.run_config.shots) - for i, param_set in enumerate(parameter_sets): - self._eval_count += 1 - self._callback(self._eval_count, param_set, means[i], estimator_error[i], step) - else: - self._eval_count += len(means) - - return means if len(means) > 1 else means[0] - - if return_expectation: - return energy_evaluation, expectation - - return energy_evaluation - - def _get_eigenstate(self, optimal_parameters) -> list[float] | dict[str, int]: - """Get the simulation outcome of the ansatz, provided with parameters.""" - optimal_circuit = self.ansatz.assign_parameters(optimal_parameters) - state_fn = self._circuit_sampler.convert(StateFn(optimal_circuit)).eval() - if self.quantum_instance.is_statevector: - state = state_fn.primitive.data # VectorStateFn -> Statevector -> np.array - else: - state = state_fn.to_dict_fn().primitive # SparseVectorStateFn -> DictStateFn -> dict - - return state - - -class VQDResult(VariationalResult, EigensolverResult): - """Deprecated: VQD Result. - - The VQDResult class has been superseded by the - :class:`qiskit.algorithms.eigensolvers.VQDResult` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.eigensolvers.VQDResult``." - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - super().__init__() - self._cost_function_evals: int | None = None - - @property - def cost_function_evals(self) -> int | None: - """Returns number of cost optimizer evaluations""" - return self._cost_function_evals - - @cost_function_evals.setter - def cost_function_evals(self, value: int) -> None: - """Sets number of cost function evaluations""" - self._cost_function_evals = value - - @property - def eigenstates(self) -> np.ndarray | None: - """return eigen state""" - return self._eigenstates - - @eigenstates.setter - def eigenstates(self, value: np.ndarray) -> None: - """set eigen state""" - self._eigenstates = value diff --git a/qiskit/algorithms/eigensolvers/__init__.py b/qiskit/algorithms/eigensolvers/__init__.py deleted file mode 100644 index 2934d6c2a340..000000000000 --- a/qiskit/algorithms/eigensolvers/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -""" -===================================================================== -Eigensolvers Package (:mod:`qiskit.algorithms.eigensolvers`) -===================================================================== - -.. currentmodule:: qiskit.algorithms.eigensolvers - -Eigensolvers -================ - -.. autosummary:: - :toctree: ../stubs/ - - Eigensolver - NumPyEigensolver - VQD - -Results -======= - - .. autosummary:: - :toctree: ../stubs/ - - EigensolverResult - NumPyEigensolverResult - VQDResult - -""" - -from .numpy_eigensolver import NumPyEigensolver, NumPyEigensolverResult -from .eigensolver import Eigensolver, EigensolverResult -from .vqd import VQD, VQDResult - -__all__ = [ - "NumPyEigensolver", - "NumPyEigensolverResult", - "Eigensolver", - "EigensolverResult", - "VQD", - "VQDResult", -] diff --git a/qiskit/algorithms/eigensolvers/eigensolver.py b/qiskit/algorithms/eigensolvers/eigensolver.py deleted file mode 100644 index 30fb7c488844..000000000000 --- a/qiskit/algorithms/eigensolvers/eigensolver.py +++ /dev/null @@ -1,106 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""The eigensolver interface and result.""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import Any -import numpy as np - -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..algorithm_result import AlgorithmResult -from ..list_or_dict import ListOrDict - - -class Eigensolver(ABC): - """The eigensolver interface. - - Algorithms that can compute eigenvalues for an operator - may implement this interface to allow different algorithms to be - used interchangeably. - """ - - @abstractmethod - def compute_eigenvalues( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> "EigensolverResult": - """ - Computes the minimum eigenvalue. The ``operator`` and ``aux_operators`` are supplied here. - While an ``operator`` is required by algorithms, ``aux_operators`` are optional. - - Args: - operator: Qubit operator of the observable. - aux_operators: Optional list of auxiliary operators to be evaluated with the - eigenstate of the minimum eigenvalue main result and their expectation values - returned. For instance, in chemistry, these can be dipole operators and total particle - count operators, so we can get values for these at the ground state. - - Returns: - An eigensolver result. - """ - return EigensolverResult() - - @classmethod - def supports_aux_operators(cls) -> bool: - """Whether computing the expectation value of auxiliary operators is supported. - - If the eigensolver computes the eigenvalues of the main operator, then it can compute - the expectation value of the ``aux_operators`` for that state. Otherwise they will be ignored. - - Returns: - ``True`` if ``aux_operator`` expectations can be evaluated, ``False`` otherwise. - """ - return False - - -class EigensolverResult(AlgorithmResult): - """Eigensolver result.""" - - def __init__(self) -> None: - super().__init__() - self._eigenvalues: np.ndarray | None = None - self._aux_operators_evaluated: list[ - ListOrDict[tuple[complex, dict[str, Any]]] - ] | None = None - - @property - def eigenvalues(self) -> np.ndarray | None: - """Return the eigenvalues.""" - return self._eigenvalues - - @eigenvalues.setter - def eigenvalues(self, value: np.ndarray) -> None: - """Set the eigenvalues.""" - self._eigenvalues = value - - @property - def aux_operators_evaluated( - self, - ) -> list[ListOrDict[tuple[complex, dict[str, Any]]]] | None: - """Return the aux operator expectation values. - - These values are in fact tuples formatted as (mean, metadata). - """ - return self._aux_operators_evaluated - - @aux_operators_evaluated.setter - def aux_operators_evaluated( - self, value: list[ListOrDict[tuple[complex, dict[str, Any]]]] - ) -> None: - """Set the aux operator eigenvalues.""" - self._aux_operators_evaluated = value diff --git a/qiskit/algorithms/eigensolvers/numpy_eigensolver.py b/qiskit/algorithms/eigensolvers/numpy_eigensolver.py deleted file mode 100644 index c0af8382ccd4..000000000000 --- a/qiskit/algorithms/eigensolvers/numpy_eigensolver.py +++ /dev/null @@ -1,327 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""The NumPy eigensolver algorithm.""" - -from __future__ import annotations - -import warnings -from typing import Callable, Union, List, Optional -import logging -import numpy as np -from scipy import sparse as scisparse - -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import SparsePauliOp, Statevector -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.utils.validation import validate_min - -from .eigensolver import Eigensolver, EigensolverResult -from ..exceptions import AlgorithmError -from ..list_or_dict import ListOrDict - -logger = logging.getLogger(__name__) - -FilterType = Callable[[Union[List, np.ndarray], float, Optional[ListOrDict[float]]], bool] - - -class NumPyEigensolver(Eigensolver): - r""" - The NumPy eigensolver algorithm. - - The NumPy Eigensolver computes up to the first :math:`k` eigenvalues of a complex-valued square - matrix of dimension :math:`n \times n`, with :math:`k \leq n`. - - Note: - Operators are automatically converted to SciPy's ``spmatrix`` - as needed and this conversion can be costly in terms of memory and performance as the - operator size, mostly in terms of number of qubits it represents, gets larger. - """ - - def __init__( - self, - k: int = 1, - filter_criterion: FilterType | None = None, - ) -> None: - """ - Args: - k: Number of eigenvalues are to be computed, with a minimum value of 1. - filter_criterion: Callable that allows to filter eigenvalues/eigenstates. Only feasible - eigenstates are returned in the results. The callable has the signature - ``filter(eigenstate, eigenvalue, aux_values)`` and must return a boolean to indicate - whether to keep this value in the final returned result or not. If the number of - elements that satisfies the criterion is smaller than ``k``, then the returned list will - have fewer elements and can even be empty. - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_min("k", k, 1) - - super().__init__() - - self._in_k = k - self._k = k - - self._filter_criterion = filter_criterion - - @property - def k(self) -> int: - """Return k (number of eigenvalues requested).""" - return self._in_k - - @k.setter - def k(self, k: int) -> None: - """Set k (number of eigenvalues requested).""" - validate_min("k", k, 1) - self._in_k = k - self._k = k - - @property - def filter_criterion( - self, - ) -> FilterType | None: - """Return the filter criterion if set.""" - return self._filter_criterion - - @filter_criterion.setter - def filter_criterion(self, filter_criterion: FilterType | None) -> None: - """Set the filter criterion.""" - self._filter_criterion = filter_criterion - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def _check_set_k(self, operator: BaseOperator | PauliSumOp) -> None: - if operator is not None: - if self._in_k > 2**operator.num_qubits: - self._k = 2**operator.num_qubits - logger.debug( - "WARNING: Asked for %s eigenvalues but max possible is %s.", self._in_k, self._k - ) - else: - self._k = self._in_k - - def _solve(self, operator: BaseOperator | PauliSumOp) -> tuple[np.ndarray, np.ndarray]: - if isinstance(operator, PauliSumOp): - op_matrix = operator.to_spmatrix() - else: - try: - op_matrix = operator.to_matrix(sparse=True) - except TypeError: - logger.debug( - "WARNING: operator of type `%s` does not support sparse matrices. " - "Trying dense computation", - type(operator), - ) - try: - op_matrix = operator.to_matrix() - except AttributeError as ex: - raise AlgorithmError(f"Unsupported operator type `{type(operator)}`.") from ex - - if isinstance(op_matrix, scisparse.csr_matrix): - # If matrix is diagonal, the elements on the diagonal are the eigenvalues. Solve by sorting. - if scisparse.csr_matrix(op_matrix.diagonal()).nnz == op_matrix.nnz: - diag = op_matrix.diagonal() - indices = np.argsort(diag)[: self._k] - eigval = diag[indices] - eigvec = np.zeros((op_matrix.shape[0], self._k)) - for i, idx in enumerate(indices): - eigvec[idx, i] = 1.0 - else: - if self._k >= 2**operator.num_qubits - 1: - logger.debug( - "SciPy doesn't support to get all eigenvalues, using NumPy instead." - ) - eigval, eigvec = self._solve_dense(operator.to_matrix()) - else: - eigval, eigvec = self._solve_sparse(op_matrix, self._k) - else: - # Sparse SciPy matrix not supported, use dense NumPy computation. - eigval, eigvec = self._solve_dense(operator.to_matrix()) - - indices = np.argsort(eigval)[: self._k] - eigval = eigval[indices] - eigvec = eigvec[:, indices] - return eigval, eigvec.T - - @staticmethod - def _solve_sparse(op_matrix: scisparse.csr_matrix, k: int) -> tuple[np.ndarray, np.ndarray]: - if (op_matrix != op_matrix.H).nnz == 0: - # Operator is Hermitian - return scisparse.linalg.eigsh(op_matrix, k=k, which="SA") - else: - return scisparse.linalg.eigs(op_matrix, k=k, which="SR") - - @staticmethod - def _solve_dense(op_matrix: np.ndarray) -> tuple[np.ndarray, np.ndarray]: - if op_matrix.all() == op_matrix.conj().T.all(): - # Operator is Hermitian - return np.linalg.eigh(op_matrix) - else: - return np.linalg.eig(op_matrix) - - @staticmethod - def _eval_aux_operators( - aux_operators: ListOrDict[BaseOperator | PauliSumOp], - wavefn: np.ndarray, - threshold: float = 1e-12, - ) -> ListOrDict[tuple[complex, complex]]: - - values: ListOrDict[tuple[complex, complex]] - - # As a list, aux_operators can contain None operators for which None values are returned. - # As a dict, the None operators in aux_operators have been dropped in compute_eigenvalues. - if isinstance(aux_operators, list): - values = [None] * len(aux_operators) - key_op_iterator = enumerate(aux_operators) - else: - values = {} - key_op_iterator = aux_operators.items() - - for key, operator in key_op_iterator: - if operator is None: - continue - - if operator.num_qubits is None or operator.num_qubits < 1: - logger.info( - "The number of qubits of the %s operator must be greater than zero.", key - ) - continue - - op_matrix = None - if isinstance(operator, PauliSumOp): - if operator.coeff != 0: - op_matrix = operator.to_spmatrix() - else: - try: - op_matrix = operator.to_matrix(sparse=True) - except TypeError: - logger.debug( - "WARNING: operator of type `%s` does not support sparse matrices. " - "Trying dense computation", - type(operator), - ) - try: - op_matrix = operator.to_matrix() - except AttributeError as ex: - raise AlgorithmError(f"Unsupported operator type {type(operator)}.") from ex - - if isinstance(op_matrix, scisparse.csr_matrix): - value = op_matrix.dot(wavefn).dot(np.conj(wavefn)) - elif isinstance(op_matrix, np.ndarray): - value = Statevector(wavefn).expectation_value(operator) - else: - value = 0.0 - - value = value if np.abs(value) > threshold else 0.0 - # The value gets wrapped into a tuple: (mean, metadata). - # The metadata includes variance (and, for other eigensolvers, shots). - # Since this is an exact computation, there are no shots - # and the variance is known to be zero. - values[key] = (value, {"variance": 0.0}) - return values - - def compute_eigenvalues( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> NumPyEigensolverResult: - - super().compute_eigenvalues(operator, aux_operators) - - if operator.num_qubits is None or operator.num_qubits < 1: - raise AlgorithmError("The number of qubits of the operator must be greater than zero.") - - self._check_set_k(operator) - - zero_op = SparsePauliOp(["I" * operator.num_qubits], coeffs=[0.0]) - if isinstance(aux_operators, list) and len(aux_operators) > 0: - # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. - aux_operators = [zero_op if op == 0 else op for op in aux_operators] - elif isinstance(aux_operators, dict) and len(aux_operators) > 0: - aux_operators = { - key: zero_op if op == 0 else op # Convert zero values to zero operators - for key, op in aux_operators.items() - if op is not None # Discard None values - } - else: - aux_operators = None - - k_orig = self._k - if self._filter_criterion: - # need to consider all elements if a filter is set - self._k = 2**operator.num_qubits - - eigvals, eigvecs = self._solve(operator) - - # compute energies before filtering, as this also evaluates the aux operators - if aux_operators is not None: - aux_op_vals = [ - self._eval_aux_operators(aux_operators, eigvecs[i]) for i in range(self._k) - ] - else: - aux_op_vals = None - - # if a filter is set, loop over the given values and only keep - if self._filter_criterion: - filt_eigvals = [] - filt_eigvecs = [] - filt_aux_op_vals = [] - count = 0 - for i, (eigval, eigvec) in enumerate(zip(eigvals, eigvecs)): - if aux_op_vals is not None: - aux_op_val = aux_op_vals[i] - else: - aux_op_val = None - - if self._filter_criterion(eigvec, eigval, aux_op_val): - count += 1 - filt_eigvecs.append(eigvec) - filt_eigvals.append(eigval) - if aux_op_vals is not None: - filt_aux_op_vals.append(aux_op_val) - - if count == k_orig: - break - - eigvals = np.array(filt_eigvals) - eigvecs = np.array(filt_eigvecs) - aux_op_vals = filt_aux_op_vals - - self._k = k_orig - - result = NumPyEigensolverResult() - result.eigenvalues = eigvals - result.eigenstates = [Statevector(vec) for vec in eigvecs] - result.aux_operators_evaluated = aux_op_vals - - logger.debug("NumpyEigensolverResult:\n%s", result) - return result - - -class NumPyEigensolverResult(EigensolverResult): - """NumPy eigensolver result.""" - - def __init__(self) -> None: - super().__init__() - self._eigenstates: list[Statevector] | None = None - - @property - def eigenstates(self) -> list[Statevector] | None: - """Return eigenstates.""" - return self._eigenstates - - @eigenstates.setter - def eigenstates(self, value: list[Statevector]) -> None: - """Set eigenstates.""" - self._eigenstates = value diff --git a/qiskit/algorithms/eigensolvers/vqd.py b/qiskit/algorithms/eigensolvers/vqd.py deleted file mode 100644 index 59f07d8a918b..000000000000 --- a/qiskit/algorithms/eigensolvers/vqd.py +++ /dev/null @@ -1,542 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""The Variational Quantum Deflation Algorithm for computing higher energy states. - -See https://arxiv.org/abs/1805.08138. -""" - -from __future__ import annotations - -from collections.abc import Callable, Sequence -from typing import Any -import logging -from time import time - -import numpy as np - -from qiskit.algorithms.state_fidelities import BaseStateFidelity -from qiskit.circuit import QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.quantum_info import SparsePauliOp - -from ..list_or_dict import ListOrDict -from ..optimizers import Optimizer, Minimizer, OptimizerResult -from ..variational_algorithm import VariationalAlgorithm -from .eigensolver import Eigensolver, EigensolverResult -from ..utils import validate_bounds, validate_initial_point -from ..exceptions import AlgorithmError -from ..observables_evaluator import estimate_observables - -# private function as we expect this to be updated in the next release -from ..utils.set_batching import _set_default_batchsize - -logger = logging.getLogger(__name__) - - -class VQD(VariationalAlgorithm, Eigensolver): - r"""The Variational Quantum Deflation algorithm. Implementation using primitives. - - `VQD `__ is a quantum algorithm that uses a - variational technique to find - the k eigenvalues of the Hamiltonian :math:`H` of a given system. - - The algorithm computes excited state energies of generalised hamiltonians - by optimising over a modified cost function where each succesive eigenvalue - is calculated iteratively by introducing an overlap term with all - the previously computed eigenstates that must be minimised, thus ensuring - higher energy eigenstates are found. - - An instance of VQD requires defining three algorithmic sub-components: - an integer k denoting the number of eigenstates to calculate, a trial - state (a.k.a. ansatz) which is a :class:`QuantumCircuit`, - and one instance (or list of) classical :mod:`~qiskit.algorithms.optimizers`. - The optimizer varies the circuit parameters - The trial state :math:`|\psi(\vec\theta)\rangle` is varied by the optimizer, - which modifies the set of ansatz parameters :math:`\vec\theta` - such that the expectation value of the operator on the corresponding - state approaches a minimum. The algorithm does this by iteratively refining - each excited state to be orthogonal to all the previous excited states. - - An optional array of parameter values, via the *initial_point*, may be provided - as the starting point for the search of the minimum eigenvalue. This feature is - particularly useful when there are reasons to believe that the solution point - is close to a particular point. - - The length of the *initial_point* list value must match the number of the parameters - expected by the ansatz. If the *initial_point* is left at the default - of ``None``, then VQD will look to the ansatz for a preferred value, based on its - given initial state. If the ansatz returns ``None``, - then a random point will be generated within the parameter bounds set, as per above. - It is also possible to give a list of initial points, one for every kth eigenvalue. - If the ansatz provides ``None`` as the lower bound, then VQD - will default it to :math:`-2\pi`; similarly, if the ansatz returns ``None`` - as the upper bound, the default value will be :math:`2\pi`. - - The following attributes can be set via the initializer but can also be read and - updated once the VQD object has been constructed. - - Attributes: - estimator (BaseEstimator): The primitive instance used to perform the expectation - estimation as indicated in the VQD paper. - fidelity (BaseStateFidelity): The fidelity class instance used to compute the - overlap estimation as indicated in the VQD paper. - ansatz (QuantumCircuit): A parameterized circuit used as ansatz for the wave function. - optimizer(Optimizer | Sequence[Optimizer]): A classical optimizer or a list of optimizers, - one for every k-th eigenvalue. Can either be a Qiskit optimizer or a callable - that takes an array as input and returns a Qiskit or SciPy optimization result. - k (int): the number of eigenvalues to return. Returns the lowest k eigenvalues. - betas (list[float]): Beta parameters in the VQD paper. - Should have length k - 1, with k the number of excited states. - These hyper-parameters balance the contribution of each overlap term to the cost - function and have a default value computed as the mean square sum of the - coefficients of the observable. - initial point (Sequence[float] | Sequence[Sequence[float]] | None): An optional initial - point (i.e. initial parameter values) or a list of initial points - (one for every k-th eigenvalue) for the optimizer. - If ``None`` then VQD will look to the ansatz for a - preferred point and if not will simply compute a random one. - callback (Callable[[int, np.ndarray, float, dict[str, Any]], None] | None): - A callback that can access the intermediate data - during the optimization. Four parameter values are passed to the callback as - follows during each evaluation by the optimizer: the evaluation count, - the optimizer parameters for the ansatz, the estimated value, the estimation - metadata, and the current step. - """ - - def __init__( - self, - estimator: BaseEstimator, - fidelity: BaseStateFidelity, - ansatz: QuantumCircuit, - optimizer: Optimizer | Minimizer | Sequence[Optimizer | Minimizer], - *, - k: int = 2, - betas: Sequence[float] | None = None, - initial_point: Sequence[float] | Sequence[Sequence[float]] | None = None, - callback: Callable[[int, np.ndarray, float, dict[str, Any]], None] | None = None, - ) -> None: - """ - - Args: - estimator: The estimator primitive. - fidelity: The fidelity class using primitives. - ansatz: A parameterized circuit used as ansatz for the wave function. - optimizer: A classical optimizer or a list of optimizers, one for every k-th eigenvalue. - Can either be a Qiskit optimizer or a callable - that takes an array as input and returns a Qiskit or SciPy optimization result. - k: The number of eigenvalues to return. Returns the lowest k eigenvalues. - betas: Beta parameters in the VQD paper. - Should have length k - 1, with k the number of excited states. - These hyperparameters balance the contribution of each overlap term to the cost - function and have a default value computed as the mean square sum of the - coefficients of the observable. - initial_point: An optional initial point (i.e. initial parameter values) - or a list of initial points (one for every k-th eigenvalue) - for the optimizer. - If ``None`` then VQD will look to the ansatz for a preferred - point and if not will simply compute a random one. - callback: A callback that can access the intermediate data - during the optimization. Four parameter values are passed to the callback as - follows during each evaluation by the optimizer: the evaluation count, - the optimizer parameters for the ansatz, the estimated value, - the estimation metadata, and the current step. - """ - super().__init__() - - self.estimator = estimator - self.fidelity = fidelity - self.ansatz = ansatz - self.optimizer = optimizer - self.k = k - self.betas = betas - # this has to go via getters and setters due to the VariationalAlgorithm interface - self.initial_point = initial_point - self.callback = callback - - self._eval_count = 0 - - @property - def initial_point(self) -> Sequence[float] | Sequence[Sequence[float]] | None: - """Returns initial point.""" - return self._initial_point - - @initial_point.setter - def initial_point(self, initial_point: Sequence[float] | Sequence[Sequence[float]] | None): - """Sets initial point""" - self._initial_point = initial_point - - def _check_operator_ansatz(self, operator: BaseOperator | PauliSumOp): - """Check that the number of qubits of operator and ansatz match.""" - if operator is not None and self.ansatz is not None: - if operator.num_qubits != self.ansatz.num_qubits: - # try to set the number of qubits on the ansatz, if possible - try: - self.ansatz.num_qubits = operator.num_qubits - except AttributeError as exc: - raise AlgorithmError( - "The number of qubits of the ansatz does not match the " - "operator, and the ansatz does not allow setting the " - "number of qubits using `num_qubits`." - ) from exc - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def compute_eigenvalues( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> VQDResult: - super().compute_eigenvalues(operator, aux_operators) - - # this sets the size of the ansatz, so it must be called before the initial point - # validation - self._check_operator_ansatz(operator) - - bounds = validate_bounds(self.ansatz) - - # We need to handle the array entries being zero or Optional i.e. having value None - if aux_operators: - zero_op = SparsePauliOp.from_list([("I" * self.ansatz.num_qubits, 0)]) - - # Convert the None and zero values when aux_operators is a list. - # Drop None and convert zero values when aux_operators is a dict. - if isinstance(aux_operators, list): - key_op_iterator = enumerate(aux_operators) - converted: ListOrDict[BaseOperator | PauliSumOp] = [zero_op] * len(aux_operators) - else: - key_op_iterator = aux_operators.items() - converted = {} - for key, op in key_op_iterator: - if op is not None: - converted[key] = zero_op if op == 0 else op - - aux_operators = converted - - else: - aux_operators = None - - if self.betas is None: - if isinstance(operator, PauliSumOp): - operator = operator.coeff * operator.primitive - - try: - upper_bound = sum(np.abs(operator.coeffs)) - - except Exception as exc: - raise NotImplementedError( - r"Beta autoevaluation is not supported for operators" - f"of type {type(operator)}." - ) from exc - - betas = [upper_bound * 10] * (self.k) - logger.info("beta autoevaluated to %s", betas[0]) - else: - betas = self.betas - - result = self._build_vqd_result() - - if aux_operators is not None: - aux_values = [] - - # We keep a list of the bound circuits with optimal parameters, to avoid re-binding - # the same parameters to the ansatz if we do multiple steps - prev_states = [] - - num_initial_points = 0 - if self.initial_point is not None: - initial_points = np.reshape(self.initial_point, (-1, self.ansatz.num_parameters)) - num_initial_points = len(initial_points) - - # 0 just means the initial point is ``None`` and ``validate_initial_point`` - # will select a random point - if num_initial_points <= 1: - initial_point = validate_initial_point(self.initial_point, self.ansatz) - - for step in range(1, self.k + 1): - if num_initial_points > 1: - initial_point = validate_initial_point(initial_points[step - 1], self.ansatz) - - if step > 1: - prev_states.append(self.ansatz.assign_parameters(result.optimal_points[-1])) - - self._eval_count = 0 - energy_evaluation = self._get_evaluate_energy( - step, operator, betas, prev_states=prev_states - ) - - start_time = time() - - # TODO: add gradient support after FidelityGradients are implemented - if isinstance(self.optimizer, Sequence): - optimizer = self.optimizer[step - 1] - else: - optimizer = self.optimizer # fall back to single optimizer if not list - - if callable(optimizer): - opt_result = optimizer( # pylint: disable=not-callable - fun=energy_evaluation, x0=initial_point, bounds=bounds - ) - else: - # we always want to submit as many estimations per job as possible for minimal - # overhead on the hardware - was_updated = _set_default_batchsize(optimizer) - - opt_result = optimizer.minimize( - fun=energy_evaluation, x0=initial_point, bounds=bounds - ) - - # reset to original value - if was_updated: - optimizer.set_max_evals_grouped(None) - - eval_time = time() - start_time - - self._update_vqd_result(result, opt_result, eval_time, self.ansatz.copy()) - - if aux_operators is not None: - aux_value = estimate_observables( - self.estimator, self.ansatz, aux_operators, result.optimal_points[-1] - ) - aux_values.append(aux_value) - - if step == 1: - logger.info( - "Ground state optimization complete in %s seconds.\n" - "Found opt_params %s in %s evals", - eval_time, - result.optimal_points, - self._eval_count, - ) - else: - logger.info( - ( - "%s excited state optimization complete in %s s.\n" - "Found opt_params %s in %s evals" - ), - str(step - 1), - eval_time, - result.optimal_points, - self._eval_count, - ) - - # To match the signature of EigensolverResult - result.eigenvalues = np.array(result.eigenvalues) - - if aux_operators is not None: - result.aux_operators_evaluated = aux_values - - return result - - def _get_evaluate_energy( - self, - step: int, - operator: BaseOperator | PauliSumOp, - betas: Sequence[float], - prev_states: list[QuantumCircuit] | None = None, - ) -> Callable[[np.ndarray], float | np.ndarray]: - """Returns a function handle to evaluate the ansatz's energy for any given parameters. - This is the objective function to be passed to the optimizer that is used for evaluation. - - Args: - step: level of energy being calculated. 0 for ground, 1 for first excited state... - operator: The operator whose energy to evaluate. - betas: Beta parameters in the VQD paper. - prev_states: List of optimal circuits from previous rounds of optimization. - - Returns: - A callable that computes and returns the energy of the hamiltonian - of each parameter. - - Raises: - AlgorithmError: If the circuit is not parameterized (i.e. has 0 free parameters). - AlgorithmError: If operator was not provided. - RuntimeError: If the previous states array is of the wrong size. - """ - - num_parameters = self.ansatz.num_parameters - if num_parameters == 0: - raise AlgorithmError("The ansatz must be parameterized, but has no free parameters.") - - if step > 1 and (len(prev_states) + 1) != step: - raise RuntimeError( - f"Passed previous states of the wrong size." - f"Passed array has length {str(len(prev_states))}" - ) - - self._check_operator_ansatz(operator) - - def evaluate_energy(parameters: np.ndarray) -> float | np.ndarray: - # handle broadcasting: ensure parameters is of shape [array, array, ...] - if len(parameters.shape) == 1: - parameters = np.reshape(parameters, (-1, num_parameters)) - batch_size = len(parameters) - - estimator_job = self.estimator.run( - batch_size * [self.ansatz], batch_size * [operator], parameters - ) - - total_cost = np.zeros(batch_size) - - if step > 1: - # compute overlap cost - batched_prev_states = [state for state in prev_states for _ in range(batch_size)] - fidelity_job = self.fidelity.run( - batch_size * [self.ansatz] * (step - 1), - batched_prev_states, - np.tile(parameters, (step - 1, 1)), - ) - costs = fidelity_job.result().fidelities - - costs = np.reshape(costs, (step - 1, -1)) - for state, cost in enumerate(costs): - total_cost += np.real(betas[state] * cost) - - try: - estimator_result = estimator_job.result() - - except Exception as exc: - raise AlgorithmError("The primitive job to evaluate the energy failed!") from exc - - values = estimator_result.values + total_cost - - if self.callback is not None: - metadata = estimator_result.metadata - for params, value, meta in zip(parameters, values, metadata): - self._eval_count += 1 - self.callback(self._eval_count, params, value, meta, step) - else: - self._eval_count += len(values) - - return values if len(values) > 1 else values[0] - - return evaluate_energy - - @staticmethod - def _build_vqd_result() -> VQDResult: - result = VQDResult() - result.optimal_points = np.array([]) - result.optimal_parameters = [] - result.optimal_values = np.array([]) - result.cost_function_evals = np.array([], dtype=int) - result.optimizer_times = np.array([]) - result.eigenvalues = [] - result.optimizer_results = [] - result.optimal_circuits = [] - return result - - @staticmethod - def _update_vqd_result( - result: VQDResult, opt_result: OptimizerResult, eval_time, ansatz - ) -> VQDResult: - result.optimal_points = ( - np.concatenate([result.optimal_points, [opt_result.x]]) - if len(result.optimal_points) > 0 - else np.array([opt_result.x]) - ) - result.optimal_parameters.append(dict(zip(ansatz.parameters, opt_result.x))) - result.optimal_values = np.concatenate([result.optimal_values, [opt_result.fun]]) - result.cost_function_evals = np.concatenate([result.cost_function_evals, [opt_result.nfev]]) - result.optimizer_times = np.concatenate([result.optimizer_times, [eval_time]]) - result.eigenvalues.append(opt_result.fun + 0j) - result.optimizer_results.append(opt_result) - result.optimal_circuits.append(ansatz) - return result - - -class VQDResult(EigensolverResult): - """VQD Result.""" - - def __init__(self) -> None: - super().__init__() - - self._cost_function_evals: np.ndarray | None = None - self._optimizer_times: np.ndarray | None = None - self._optimal_values: np.ndarray | None = None - self._optimal_points: np.ndarray | None = None - self._optimal_parameters: list[dict] | None = None - self._optimizer_results: list[OptimizerResult] | None = None - self._optimal_circuits: list[QuantumCircuit] | None = None - - @property - def cost_function_evals(self) -> np.ndarray | None: - """Returns number of cost optimizer evaluations""" - return self._cost_function_evals - - @cost_function_evals.setter - def cost_function_evals(self, value: np.ndarray) -> None: - """Sets number of cost function evaluations""" - self._cost_function_evals = value - - @property - def optimizer_times(self) -> np.ndarray | None: - """Returns time taken for optimization for each step""" - return self._optimizer_times - - @optimizer_times.setter - def optimizer_times(self, value: np.ndarray) -> None: - """Sets time taken for optimization for each step""" - self._optimizer_times = value - - @property - def optimal_values(self) -> np.ndarray | None: - """Returns optimal value for each step""" - return self._optimal_values - - @optimal_values.setter - def optimal_values(self, value: np.ndarray) -> None: - """Sets optimal values""" - self._optimal_values = value - - @property - def optimal_points(self) -> np.ndarray | None: - """Returns optimal point for each step""" - return self._optimal_points - - @optimal_points.setter - def optimal_points(self, value: np.ndarray) -> None: - """Sets optimal points""" - self._optimal_points = value - - @property - def optimal_parameters(self) -> list[dict] | None: - """Returns the optimal parameters for each step""" - return self._optimal_parameters - - @optimal_parameters.setter - def optimal_parameters(self, value: list[dict]) -> None: - """Sets optimal parameters""" - self._optimal_parameters = value - - @property - def optimizer_results(self) -> list[OptimizerResult] | None: - """Returns the optimizer results for each step""" - return self._optimizer_results - - @optimizer_results.setter - def optimizer_results(self, value: list[OptimizerResult]) -> None: - """Sets optimizer results""" - self._optimizer_results = value - - @property - def optimal_circuits(self) -> list[QuantumCircuit] | None: - """The optimal circuits. Along with the optimal parameters, - these can be used to retrieve the different eigenstates.""" - return self._optimal_circuits - - @optimal_circuits.setter - def optimal_circuits(self, optimal_circuits: list[QuantumCircuit]) -> None: - self._optimal_circuits = optimal_circuits diff --git a/qiskit/algorithms/evolvers/__init__.py b/qiskit/algorithms/evolvers/__init__.py deleted file mode 100644 index 990c787b7c15..000000000000 --- a/qiskit/algorithms/evolvers/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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 Time Evolution package.""" - -from .evolution_result import EvolutionResult -from .evolution_problem import EvolutionProblem - -__all__ = [ - "EvolutionResult", - "EvolutionProblem", -] diff --git a/qiskit/algorithms/evolvers/evolution_problem.py b/qiskit/algorithms/evolvers/evolution_problem.py deleted file mode 100644 index 8d87dbd9ae6a..000000000000 --- a/qiskit/algorithms/evolvers/evolution_problem.py +++ /dev/null @@ -1,122 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Evolution problem class.""" - -from __future__ import annotations - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.opflow import OperatorBase, StateFn -from qiskit.utils.deprecation import deprecate_func -from ..list_or_dict import ListOrDict - - -class EvolutionProblem: - """Deprecated: Evolution problem class. - - The EvolutionProblem class has been superseded by the - :class:`qiskit.algorithms.time_evolvers.TimeEvolutionProblem` class. - This class will be deprecated in a future release and subsequently - removed after that. - - This class is the input to time evolution algorithms and must contain information on the total - evolution time, a quantum state to be evolved and under which Hamiltonian the state is evolved. - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.time_evolvers.TimeEvolutionProblem``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - hamiltonian: OperatorBase, - time: float, - initial_state: StateFn | QuantumCircuit | None = None, - aux_operators: ListOrDict[OperatorBase] | None = None, - truncation_threshold: float = 1e-12, - t_param: Parameter | None = None, - param_value_dict: dict[Parameter, complex] | None = None, - ): - """ - Args: - hamiltonian: The Hamiltonian under which to evolve the system. - time: Total time of evolution. - initial_state: The quantum state to be evolved for methods like Trotterization. - For variational time evolutions, where the evolution happens in an ansatz, - this argument is not required. - aux_operators: Optional list of auxiliary operators to be evaluated with the - evolved ``initial_state`` and their expectation values returned. - truncation_threshold: Defines a threshold under which values can be assumed to be 0. - Used when ``aux_operators`` is provided. - t_param: Time parameter in case of a time-dependent Hamiltonian. This - free parameter must be within the ``hamiltonian``. - param_value_dict: Maps free parameters in the problem to values. Depending on the - algorithm, it might refer to e.g. a Hamiltonian or an initial state. - - Raises: - ValueError: If non-positive time of evolution is provided. - """ - - self.t_param = t_param - self.param_value_dict = param_value_dict - self.hamiltonian = hamiltonian - self.time = time - self.initial_state = initial_state - self.aux_operators = aux_operators - self.truncation_threshold = truncation_threshold - - @property - def time(self) -> float: - """Returns time.""" - return self._time - - @time.setter - def time(self, time: float) -> None: - """ - Sets time and validates it. - - Raises: - ValueError: If time is not positive. - """ - if time <= 0: - raise ValueError(f"Evolution time must be > 0 but was {time}.") - self._time = time - - def validate_params(self) -> None: - """ - Checks if all parameters present in the Hamiltonian are also present in the dictionary - that maps them to values. - - Raises: - ValueError: If Hamiltonian parameters cannot be bound with data provided. - """ - if isinstance(self.hamiltonian, OperatorBase): - t_param_set = set() - if self.t_param is not None: - t_param_set.add(self.t_param) - hamiltonian_dict_param_set: set[Parameter] = set() - if self.param_value_dict is not None: - hamiltonian_dict_param_set = hamiltonian_dict_param_set.union( - set(self.param_value_dict.keys()) - ) - params_set = t_param_set.union(hamiltonian_dict_param_set) - hamiltonian_param_set = set(self.hamiltonian.parameters) - - if hamiltonian_param_set != params_set: - raise ValueError( - f"Provided parameters {params_set} do not match Hamiltonian parameters " - f"{hamiltonian_param_set}." - ) diff --git a/qiskit/algorithms/evolvers/evolution_result.py b/qiskit/algorithms/evolvers/evolution_result.py deleted file mode 100644 index 5dd9e103f669..000000000000 --- a/qiskit/algorithms/evolvers/evolution_result.py +++ /dev/null @@ -1,55 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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. - -"""Class for holding evolution result.""" - -from __future__ import annotations - -from qiskit import QuantumCircuit -from qiskit.algorithms.list_or_dict import ListOrDict -from qiskit.opflow import StateFn, OperatorBase -from qiskit.utils.deprecation import deprecate_func -from ..algorithm_result import AlgorithmResult - - -class EvolutionResult(AlgorithmResult): - """Deprecated: Class for holding evolution result. - - The EvolutionResult class has been superseded by the - :class:`qiskit.algorithms.time_evolvers.TimeEvolutionResult` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.time_evolvers.TimeEvolutionResult``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - evolved_state: StateFn | QuantumCircuit | OperatorBase, - aux_ops_evaluated: ListOrDict[tuple[complex, complex]] | None = None, - ): - """ - Args: - evolved_state: An evolved quantum state. - aux_ops_evaluated: Optional list of observables for which expected values on an evolved - state are calculated. These values are in fact tuples formatted as (mean, standard - deviation). - """ - - self.evolved_state = evolved_state - self.aux_ops_evaluated = aux_ops_evaluated diff --git a/qiskit/algorithms/evolvers/imaginary_evolver.py b/qiskit/algorithms/evolvers/imaginary_evolver.py deleted file mode 100644 index 74b301d3e539..000000000000 --- a/qiskit/algorithms/evolvers/imaginary_evolver.py +++ /dev/null @@ -1,55 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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. - -"""Interface for Quantum Imaginary Time Evolution.""" - -from abc import ABC, abstractmethod - -from qiskit.utils.deprecation import deprecate_func -from .evolution_problem import EvolutionProblem -from .evolution_result import EvolutionResult - - -class ImaginaryEvolver(ABC): - """Deprecated: Interface for Quantum Imaginary Time Evolution. - - The ImaginaryEvolver interface has been superseded by the - :class:`qiskit.algorithms.time_evolvers.ImaginaryTimeEvolver` interface. - This interface will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the interface ``qiskit.algorithms.time_evolvers.ImaginaryTimeEvolver``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - pass - - @abstractmethod - def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult: - r"""Perform imaginary time evolution :math:`\exp(-\tau H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for an imaginary time :math:`\tau` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - raise NotImplementedError() diff --git a/qiskit/algorithms/evolvers/real_evolver.py b/qiskit/algorithms/evolvers/real_evolver.py deleted file mode 100644 index 5024143b59b6..000000000000 --- a/qiskit/algorithms/evolvers/real_evolver.py +++ /dev/null @@ -1,55 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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. - -"""Interface for Quantum Real Time Evolution.""" - -from abc import ABC, abstractmethod -from qiskit.utils.deprecation import deprecate_func - -from .evolution_problem import EvolutionProblem -from .evolution_result import EvolutionResult - - -class RealEvolver(ABC): - """Deprecated: Interface for Quantum Real Time Evolution. - - The RealEvolver interface has been superseded by the - :class:`qiskit.algorithms.time_evolvers.RealTimeEvolver` interface. - This interface will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the interface ``qiskit.algorithms.time_evolvers.RealTimeEvolver``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - pass - - @abstractmethod - def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult: - r"""Perform real time evolution :math:`\exp(-i t H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for a time :math:`t` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - raise NotImplementedError() diff --git a/qiskit/algorithms/evolvers/trotterization/__init__.py b/qiskit/algorithms/evolvers/trotterization/__init__.py deleted file mode 100644 index fe1b8d8aedf2..000000000000 --- a/qiskit/algorithms/evolvers/trotterization/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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. -"""This package contains Trotterization-based Quantum Real Time Evolution algorithm. -It is compliant with the new Quantum Time Evolution Framework and makes use of -:class:`qiskit.synthesis.evolution.ProductFormula` and -:class:`~qiskit.circuit.library.PauliEvolutionGate` implementations. """ - -from qiskit.algorithms.evolvers.trotterization.trotter_qrte import ( - TrotterQRTE, -) - -__all__ = ["TrotterQRTE"] diff --git a/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py b/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py deleted file mode 100644 index 538635c67f42..000000000000 --- a/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py +++ /dev/null @@ -1,262 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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. - -"""An algorithm to implement a Trotterization real time-evolution.""" - -from __future__ import annotations - -import warnings - -from qiskit import QuantumCircuit -from qiskit.algorithms.aux_ops_evaluator import eval_observables -from qiskit.algorithms.evolvers import EvolutionProblem, EvolutionResult -from qiskit.algorithms.evolvers.real_evolver import RealEvolver -from qiskit.opflow import ( - SummedOp, - PauliOp, - CircuitOp, - ExpectationBase, - CircuitSampler, - PauliSumOp, - StateFn, - OperatorBase, -) -from qiskit.circuit.library import PauliEvolutionGate -from qiskit.providers import Backend -from qiskit.synthesis import ProductFormula, LieTrotter -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_func - - -class TrotterQRTE(RealEvolver): - """Deprecated: Quantum Real Time Evolution using Trotterization. - - The TrotterQRTE class has been superseded by the - :class:`qiskit.algorithms.time_evolvers.trotterization.TrotterQRTE` class. - This class will be deprecated in a future release and subsequently - removed after that. - - Type of Trotterization is defined by a ProductFormula provided. - - Examples:: - - from qiskit.opflow import X, Z, Zero - from qiskit.algorithms import EvolutionProblem, TrotterQRTE - from qiskit import BasicAer - from qiskit.utils import QuantumInstance - - operator = X + Z - initial_state = Zero - time = 1 - evolution_problem = EvolutionProblem(operator, 1, initial_state) - # LieTrotter with 1 rep - backend = BasicAer.get_backend("statevector_simulator") - quantum_instance = QuantumInstance(backend=backend) - trotter_qrte = TrotterQRTE(quantum_instance=quantum_instance) - evolved_state = trotter_qrte.evolve(evolution_problem).evolved_state - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.time_evolvers.trotterization.TrotterQRTE``." - " See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - product_formula: ProductFormula | None = None, - expectation: ExpectationBase | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - ) -> None: - """ - Args: - product_formula: A Lie-Trotter-Suzuki product formula. The default is the Lie-Trotter - first order product formula with a single repetition. - expectation: An instance of ExpectationBase which defines a method for calculating - expectation values of EvolutionProblem.aux_operators. - quantum_instance: A quantum instance used for calculating expectation values of - EvolutionProblem.aux_operators. - """ - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - if product_formula is None: - product_formula = LieTrotter() - self._product_formula = product_formula - self._quantum_instance = None - self._circuit_sampler: CircuitSampler | None = None - if quantum_instance is not None: - self.quantum_instance = quantum_instance - self._expectation = expectation - - @property - def product_formula(self) -> ProductFormula: - """Returns a product formula used in the algorithm.""" - return self._product_formula - - @product_formula.setter - def product_formula(self, product_formula: ProductFormula) -> None: - """ - Sets a product formula. - Args: - product_formula: A formula that defines the Trotterization algorithm. - """ - self._product_formula = product_formula - - @property - def quantum_instance(self) -> QuantumInstance | None: - """Returns a quantum instance used in the algorithm.""" - return self._quantum_instance - - @quantum_instance.setter - def quantum_instance(self, quantum_instance: QuantumInstance | Backend | None) -> None: - """ - Sets a quantum instance and a circuit sampler. - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - - self._circuit_sampler = None - if quantum_instance is not None: - self._circuit_sampler = CircuitSampler(quantum_instance) - - self._quantum_instance = quantum_instance - - @property - def expectation(self) -> ExpectationBase | None: - """Returns an expectation used in the algorithm.""" - return self._expectation - - @expectation.setter - def expectation(self, expectation: ExpectationBase | None) -> None: - """ - Sets an expectation. - Args: - expectation: An instance of ExpectationBase which defines a method for calculating - expectation values of EvolutionProblem.aux_operators. - """ - self._expectation = expectation - - @classmethod - def supports_aux_operators(cls) -> bool: - """ - Whether computing the expectation value of auxiliary operators is supported. - - Returns: - True if ``aux_operators`` expectations in the EvolutionProblem can be evaluated, False - otherwise. - """ - return True - - def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult: - """ - Evolves a quantum state for a given time using the Trotterization method - based on a product formula provided. The result is provided in the form of a quantum - circuit. If auxiliary operators are included in the ``evolution_problem``, they are - evaluated on an evolved state using a backend provided. - - .. note:: - Time-dependent Hamiltonians are not yet supported. - - Args: - evolution_problem: Instance defining evolution problem. For the included Hamiltonian, - ``PauliOp``, ``SummedOp`` or ``PauliSumOp`` are supported by TrotterQRTE. - - Returns: - Evolution result that includes an evolved state as a quantum circuit and, optionally, - auxiliary operators evaluated for a resulting state on a backend. - - Raises: - ValueError: If ``t_param`` is not set to None in the EvolutionProblem (feature not - currently supported). - ValueError: If the ``initial_state`` is not provided in the EvolutionProblem. - """ - evolution_problem.validate_params() - if evolution_problem.t_param is not None: - raise ValueError( - "TrotterQRTE does not accept a time dependent hamiltonian," - "``t_param`` from the EvolutionProblem should be set to None." - ) - - if evolution_problem.aux_operators is not None and ( - self._quantum_instance is None or self._expectation is None - ): - raise ValueError( - "aux_operators were provided for evaluations but no ``expectation`` or " - "``quantum_instance`` was provided." - ) - hamiltonian = evolution_problem.hamiltonian - if not isinstance(hamiltonian, (PauliOp, PauliSumOp, SummedOp)): - raise ValueError( - "TrotterQRTE only accepts PauliOp | " - f"PauliSumOp | SummedOp, {type(hamiltonian)} provided." - ) - if isinstance(hamiltonian, OperatorBase): - hamiltonian = hamiltonian.assign_parameters(evolution_problem.param_value_dict) - if isinstance(hamiltonian, SummedOp): - hamiltonian = self._summed_op_to_pauli_sum_op(hamiltonian) - # the evolution gate - evolution_gate = CircuitOp( - PauliEvolutionGate(hamiltonian, evolution_problem.time, synthesis=self._product_formula) - ) - - if evolution_problem.initial_state is not None: - initial_state = evolution_problem.initial_state - if isinstance(initial_state, QuantumCircuit): - initial_state = StateFn(initial_state) - evolved_state = evolution_gate @ initial_state - - else: - raise ValueError("``initial_state`` must be provided in the EvolutionProblem.") - - evaluated_aux_ops = None - if evolution_problem.aux_operators is not None: - evaluated_aux_ops = eval_observables( - self._quantum_instance, - evolved_state.primitive, - evolution_problem.aux_operators, - self._expectation, - evolution_problem.truncation_threshold, - ) - - return EvolutionResult(evolved_state, evaluated_aux_ops) - - @staticmethod - def _summed_op_to_pauli_sum_op( - hamiltonian: SummedOp, - ) -> PauliSumOp | PauliOp: - """ - Tries binding parameters in a Hamiltonian. - - Args: - hamiltonian: The Hamiltonian that defines an evolution. - - Returns: - Hamiltonian. - - Raises: - ValueError: If the ``SummedOp`` Hamiltonian contains operators of an invalid type. - """ - # PauliSumOp does not allow parametrized coefficients but after binding the parameters - # we need to convert it into a PauliSumOp for the PauliEvolutionGate. - op_list = [] - for op in hamiltonian.oplist: - if not isinstance(op, PauliOp): - raise ValueError( - "Content of the Hamiltonian not of type PauliOp. The " - f"following type detected: {type(op)}." - ) - op_list.append(op) - return sum(op_list) diff --git a/qiskit/algorithms/exceptions.py b/qiskit/algorithms/exceptions.py deleted file mode 100644 index 1f830a3cba95..000000000000 --- a/qiskit/algorithms/exceptions.py +++ /dev/null @@ -1,21 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# 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. - -"""Exception for errors raised by Algorithms module.""" - -from qiskit.exceptions import QiskitError - - -class AlgorithmError(QiskitError): - """For Algorithm specific errors.""" - - pass diff --git a/qiskit/algorithms/gradients/__init__.py b/qiskit/algorithms/gradients/__init__.py deleted file mode 100644 index ff3a2fceca5e..000000000000 --- a/qiskit/algorithms/gradients/__init__.py +++ /dev/null @@ -1,130 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# 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. - -""" -============================================== -Gradients (:mod:`qiskit.algorithms.gradients`) -============================================== - -.. currentmodule:: qiskit.algorithms.gradients - -Base Classes -============ - -.. autosummary:: - :toctree: ../stubs/ - - BaseEstimatorGradient - BaseQGT - BaseSamplerGradient - EstimatorGradientResult - SamplerGradientResult - QGTResult - -Finite Differences -================== - -.. autosummary:: - :toctree: ../stubs/ - - FiniteDiffEstimatorGradient - FiniteDiffSamplerGradient - -Linear Combination of Unitaries -=============================== - -.. autosummary:: - :toctree: ../stubs/ - - LinCombEstimatorGradient - LinCombSamplerGradient - LinCombQGT - -Parameter Shift Rules -===================== - -.. autosummary:: - :toctree: ../stubs/ - - ParamShiftEstimatorGradient - ParamShiftSamplerGradient - -Quantum Fisher Information -========================== - -.. autosummary:: - :toctree: ../stubs/ - - QFIResult - QFI - -Classical Methods -================= - -.. autosummary:: - :toctree: ../stubs/ - - ReverseEstimatorGradient - ReverseQGT - -Simultaneous Perturbation Stochastic Approximation -================================================== - -.. autosummary:: - :toctree: ../stubs/ - - SPSAEstimatorGradient - SPSASamplerGradient -""" - -from .base.base_estimator_gradient import BaseEstimatorGradient -from .base.base_qgt import BaseQGT -from .base.base_sampler_gradient import BaseSamplerGradient -from .base.estimator_gradient_result import EstimatorGradientResult -from .finite_diff.finite_diff_estimator_gradient import FiniteDiffEstimatorGradient -from .finite_diff.finite_diff_sampler_gradient import FiniteDiffSamplerGradient -from .lin_comb.lin_comb_estimator_gradient import DerivativeType, LinCombEstimatorGradient -from .lin_comb.lin_comb_qgt import LinCombQGT -from .lin_comb.lin_comb_sampler_gradient import LinCombSamplerGradient -from .param_shift.param_shift_estimator_gradient import ParamShiftEstimatorGradient -from .param_shift.param_shift_sampler_gradient import ParamShiftSamplerGradient -from .qfi import QFI -from .qfi_result import QFIResult -from .base.qgt_result import QGTResult -from .base.sampler_gradient_result import SamplerGradientResult -from .spsa.spsa_estimator_gradient import SPSAEstimatorGradient -from .spsa.spsa_sampler_gradient import SPSASamplerGradient -from .reverse.reverse_gradient import ReverseEstimatorGradient -from .reverse.reverse_qgt import ReverseQGT - -__all__ = [ - "BaseEstimatorGradient", - "BaseQGT", - "BaseSamplerGradient", - "DerivativeType", - "EstimatorGradientResult", - "FiniteDiffEstimatorGradient", - "FiniteDiffSamplerGradient", - "LinCombEstimatorGradient", - "LinCombQGT", - "LinCombSamplerGradient", - "ParamShiftEstimatorGradient", - "ParamShiftSamplerGradient", - "QFI", - "QFIResult", - "QGTResult", - "SamplerGradientResult", - "SPSAEstimatorGradient", - "SPSASamplerGradient", - "ReverseEstimatorGradient", - "ReverseQGT", -] diff --git a/qiskit/algorithms/gradients/base/__init__.py b/qiskit/algorithms/gradients/base/__init__.py deleted file mode 100644 index 8f5fd2a37f84..000000000000 --- a/qiskit/algorithms/gradients/base/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# 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. diff --git a/qiskit/algorithms/gradients/base/base_estimator_gradient.py b/qiskit/algorithms/gradients/base/base_estimator_gradient.py deleted file mode 100644 index 0cbf478fa2ec..000000000000 --- a/qiskit/algorithms/gradients/base/base_estimator_gradient.py +++ /dev/null @@ -1,360 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# 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. - -""" -Abstract base class of gradient for ``Estimator``. -""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Sequence -from copy import copy - -import numpy as np - -from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.primitives.utils import _circuit_key -from qiskit.providers import Options -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.transpiler.passes import TranslateParameterizedGates - -from .estimator_gradient_result import EstimatorGradientResult -from ..utils import ( - DerivativeType, - GradientCircuit, - _assign_unique_parameters, - _make_gradient_parameters, - _make_gradient_parameter_values, -) - -from ...algorithm_job import AlgorithmJob - - -class BaseEstimatorGradient(ABC): - """Base class for an ``EstimatorGradient`` to compute the gradients of the expectation value.""" - - def __init__( - self, - estimator: BaseEstimator, - options: Options | None = None, - derivative_type: DerivativeType = DerivativeType.REAL, - ): - r""" - Args: - estimator: The estimator used to compute the gradients. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - derivative_type: The type of derivative. Can be either ``DerivativeType.REAL`` - ``DerivativeType.IMAG``, or ``DerivativeType.COMPLEX``. - - - ``DerivativeType.REAL`` computes :math:`2 \mathrm{Re}[⟨ψ(ω)|O(θ)|dω ψ(ω)〉]`. - - ``DerivativeType.IMAG`` computes :math:`2 \mathrm{Im}[⟨ψ(ω)|O(θ)|dω ψ(ω)〉]`. - - ``DerivativeType.COMPLEX`` computes :math:`2 ⟨ψ(ω)|O(θ)|dω ψ(ω)〉`. - - Defaults to ``DerivativeType.REAL``, as this yields e.g. the commonly-used energy - gradient and this type is the only supported type for function-level schemes like - finite difference. - """ - self._estimator: BaseEstimator = estimator - self._default_options = Options() - if options is not None: - self._default_options.update_options(**options) - self._derivative_type = derivative_type - - self._gradient_circuit_cache: dict[ - tuple, - GradientCircuit, - ] = {} - - @property - def derivative_type(self) -> DerivativeType: - """Return the derivative type (real, imaginary or complex). - - Returns: - The derivative type. - """ - return self._derivative_type - - def run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter] | None] | None = None, - **options, - ) -> AlgorithmJob: - """Run the job of the estimator gradient on the given circuits. - - Args: - circuits: The list of quantum circuits to compute the gradients. - observables: The list of observables. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of - the specified parameters. Each sequence of parameters corresponds to a circuit in - ``circuits``. Defaults to None, which means that the gradients of all parameters in - each circuit are calculated. None in the sequence means that the gradients of all - parameters in the corresponding circuit are calculated. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - - Returns: - The job object of the gradients of the expectation values. The i-th result corresponds to - ``circuits[i]`` evaluated with parameters bound as ``parameter_values[i]``. The j-th - element of the i-th result corresponds to the gradient of the i-th circuit with respect - to the j-th parameter. - - Raises: - ValueError: Invalid arguments are given. - """ - if isinstance(circuits, QuantumCircuit): - # Allow a single circuit to be passed in. - circuits = (circuits,) - if isinstance(observables, (BaseOperator, PauliSumOp)): - # Allow a single observable to be passed in. - observables = (observables,) - - if parameters is None: - # If parameters is None, we calculate the gradients of all parameters in each circuit. - parameters = [circuit.parameters for circuit in circuits] - else: - # If parameters is not None, we calculate the gradients of the specified parameters. - # None in parameters means that the gradients of all parameters in the corresponding - # circuit are calculated. - parameters = [ - params if params is not None else circuits[i].parameters - for i, params in enumerate(parameters) - ] - # Validate the arguments. - self._validate_arguments(circuits, observables, parameter_values, parameters) - # The priority of run option is as follows: - # options in ``run`` method > gradient's default options > primitive's default setting. - opts = copy(self._default_options) - opts.update_options(**options) - # Run the job. - job = AlgorithmJob( - self._run, circuits, observables, parameter_values, parameters, **opts.__dict__ - ) - job.submit() - return job - - @abstractmethod - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - raise NotImplementedError() - - def _preprocess( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - supported_gates: Sequence[str], - ) -> tuple[Sequence[QuantumCircuit], Sequence[Sequence[float]], Sequence[Sequence[Parameter]]]: - """Preprocess the gradient. This makes a gradient circuit for each circuit. The gradient - circuit is a transpiled circuit by using the supported gates, and has unique parameters. - ``parameter_values`` and ``parameters`` are also updated to match the gradient circuit. - - Args: - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - supported_gates: The supported gates used to transpile the circuit. - - Returns: - The list of gradient circuits, the list of parameter values, and the list of parameters. - parameter_values and parameters are updated to match the gradient circuit. - """ - translator = TranslateParameterizedGates(supported_gates) - g_circuits, g_parameter_values, g_parameters = [], [], [] - for circuit, parameter_value_, parameters_ in zip(circuits, parameter_values, parameters): - circuit_key = _circuit_key(circuit) - if circuit_key not in self._gradient_circuit_cache: - unrolled = translator(circuit) - self._gradient_circuit_cache[circuit_key] = _assign_unique_parameters(unrolled) - gradient_circuit = self._gradient_circuit_cache[circuit_key] - g_circuits.append(gradient_circuit.gradient_circuit) - g_parameter_values.append( - _make_gradient_parameter_values(circuit, gradient_circuit, parameter_value_) - ) - g_parameters.append(_make_gradient_parameters(gradient_circuit, parameters_)) - return g_circuits, g_parameter_values, g_parameters - - def _postprocess( - self, - results: EstimatorGradientResult, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - ) -> EstimatorGradientResult: - """Postprocess the gradients. This method computes the gradient of the original circuits - by applying the chain rule to the gradient of the circuits with unique parameters. - - Args: - results: The computed gradients for the circuits with unique parameters. - circuits: The list of original circuits submitted for gradient computation. - parameter_values: The list of parameter values to be bound to the circuits. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - - Returns: - The gradients of the original circuits. - """ - gradients, metadata = [], [] - for idx, (circuit, parameter_values_, parameters_) in enumerate( - zip(circuits, parameter_values, parameters) - ): - gradient = np.zeros(len(parameters_)) - if ( - "derivative_type" in results.metadata[idx] - and results.metadata[idx]["derivative_type"] == DerivativeType.COMPLEX - ): - # If the derivative type is complex, cast the gradient to complex. - gradient = gradient.astype("complex") - gradient_circuit = self._gradient_circuit_cache[_circuit_key(circuit)] - g_parameters = _make_gradient_parameters(gradient_circuit, parameters_) - # Make a map from the gradient parameter to the respective index in the gradient. - g_parameter_indices = {param: i for i, param in enumerate(g_parameters)} - # Compute the original gradient from the gradient of the gradient circuit - # by using the chain rule. - for i, parameter in enumerate(parameters_): - for g_parameter, coeff in gradient_circuit.parameter_map[parameter]: - # Compute the coefficient - if isinstance(coeff, ParameterExpression): - local_map = { - p: parameter_values_[circuit.parameters.data.index(p)] - for p in coeff.parameters - } - bound_coeff = coeff.bind(local_map) - else: - bound_coeff = coeff - # The original gradient is a sum of the gradients of the parameters in the - # gradient circuit multiplied by the coefficients. - gradient[i] += ( - float(bound_coeff) - * results.gradients[idx][g_parameter_indices[g_parameter]] - ) - gradients.append(gradient) - metadata.append({"parameters": parameters_}) - return EstimatorGradientResult( - gradients=gradients, metadata=metadata, options=results.options - ) - - @staticmethod - def _validate_arguments( - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - ) -> None: - """Validate the arguments of the ``run`` method. - - Args: - circuits: The list of quantum circuits to compute the gradients. - observables: The list of observables. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - - Raises: - ValueError: Invalid arguments are given. - """ - if len(circuits) != len(parameter_values): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of parameter value sets ({len(parameter_values)})." - ) - - for i, (circuit, parameter_value) in enumerate(zip(circuits, parameter_values)): - if not circuit.num_parameters: - raise ValueError(f"The {i}-th circuit is not parameterised.") - if len(parameter_value) != circuit.num_parameters: - raise ValueError( - f"The number of values ({len(parameter_value)}) does not match " - f"the number of parameters ({circuit.num_parameters}) for the {i}-th circuit." - ) - - if len(circuits) != len(observables): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of observables ({len(observables)})." - ) - - for i, (circuit, observable) in enumerate(zip(circuits, observables)): - if circuit.num_qubits != observable.num_qubits: - raise ValueError( - f"The number of qubits of the {i}-th circuit ({circuit.num_qubits}) does " - f"not match the number of qubits of the {i}-th observable " - f"({observable.num_qubits})." - ) - - if len(circuits) != len(parameters): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of the list of specified parameters ({len(parameters)})." - ) - - for i, (circuit, parameters_) in enumerate(zip(circuits, parameters)): - if not set(parameters_).issubset(circuit.parameters): - raise ValueError( - f"The {i}-th parameters contains parameters not present in the " - f"{i}-th circuit." - ) - - @property - def options(self) -> Options: - """Return the union of estimator options setting and gradient default options, - where, if the same field is set in both, the gradient's default options override - the primitive's default setting. - - Returns: - The gradient default + estimator options. - """ - return self._get_local_options(self._default_options.__dict__) - - def update_default_options(self, **options): - """Update the gradient's default options setting. - - Args: - **options: The fields to update the default options. - """ - - self._default_options.update_options(**options) - - def _get_local_options(self, options: Options) -> Options: - """Return the union of the primitive's default setting, - the gradient default options, and the options in the ``run`` method. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - - Args: - options: The fields to update the options - - Returns: - The gradient default + estimator + run options. - """ - opts = copy(self._estimator.options) - opts.update_options(**options) - return opts diff --git a/qiskit/algorithms/gradients/base/base_qgt.py b/qiskit/algorithms/gradients/base/base_qgt.py deleted file mode 100644 index f2999a8f2bf0..000000000000 --- a/qiskit/algorithms/gradients/base/base_qgt.py +++ /dev/null @@ -1,383 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -""" -Abstract base class of the Quantum Geometric Tensor (QGT). -""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Sequence -from copy import copy - -import numpy as np - -from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit -from qiskit.primitives import BaseEstimator -from qiskit.primitives.utils import _circuit_key -from qiskit.providers import Options -from qiskit.transpiler.passes import TranslateParameterizedGates - -from .qgt_result import QGTResult -from ..utils import ( - DerivativeType, - GradientCircuit, - _assign_unique_parameters, - _make_gradient_parameters, - _make_gradient_parameter_values, -) - -from ...algorithm_job import AlgorithmJob - - -class BaseQGT(ABC): - r"""Base class to computes the Quantum Geometric Tensor (QGT) given a pure, - parameterized quantum state. QGT is defined as: - - .. math:: - - \mathrm{QGT}_{ij}= \langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle. - """ - - def __init__( - self, - estimator: BaseEstimator, - phase_fix: bool = True, - derivative_type: DerivativeType = DerivativeType.COMPLEX, - options: Options | None = None, - ): - r""" - Args: - estimator: The estimator used to compute the QGT. - phase_fix: Whether to calculate the second term (phase fix) of the QGT, which is - :math:`\langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle`. - Defaults to ``True``. - derivative_type: The type of derivative. Can be either ``DerivativeType.REAL`` - ``DerivativeType.IMAG``, or ``DerivativeType.COMPLEX``. Defaults to - ``DerivativeType.REAL``. - - - ``DerivativeType.REAL`` computes - - .. math:: - - \mathrm{Re(QGT)}_{ij}= \mathrm{Re}[\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - - ``DerivativeType.IMAG`` computes - - .. math:: - - \mathrm{Im(QGT)}_{ij}= \mathrm{Im}[\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - - ``DerivativeType.COMPLEX`` computes - - .. math:: - - \mathrm{QGT}_{ij}= [\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - options: Backend runtime options used for circuit execution. The order of priority is: - options in ``run`` method > QGT's default options > primitive's default - setting. Higher priority setting overrides lower priority setting. - """ - self._estimator: BaseEstimator = estimator - self._phase_fix: bool = phase_fix - self._derivative_type: DerivativeType = derivative_type - self._default_options = Options() - if options is not None: - self._default_options.update_options(**options) - self._qgt_circuit_cache: dict[tuple, GradientCircuit] = {} - self._gradient_circuit_cache: dict[tuple, GradientCircuit] = {} - - @property - def derivative_type(self) -> DerivativeType: - """The derivative type.""" - return self._derivative_type - - @derivative_type.setter - def derivative_type(self, derivative_type: DerivativeType) -> None: - """Set the derivative type.""" - self._derivative_type = derivative_type - - def run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter] | None] | None = None, - **options, - ) -> AlgorithmJob: - """Run the job of the QGTs on the given circuits. - - Args: - circuits: The list of quantum circuits to compute the QGTs. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the QGTs of - the specified parameters. Each sequence of parameters corresponds to a circuit in - ``circuits``. Defaults to None, which means that the QGTs of all parameters in - each circuit are calculated. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > QGT's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting. - - Returns: - The job object of the QGTs of the expectation values. The i-th result corresponds to - ``circuits[i]`` evaluated with parameters bound as ``parameter_values[i]``. - - Raises: - ValueError: Invalid arguments are given. - """ - if isinstance(circuits, QuantumCircuit): - # Allow a single circuit to be passed in. - circuits = (circuits,) - - if parameters is None: - # If parameters is None, we calculate the gradients of all parameters in each circuit. - parameters = [circuit.parameters for circuit in circuits] - else: - # If parameters is not None, we calculate the gradients of the specified parameters. - # None in parameters means that the gradients of all parameters in the corresponding - # circuit are calculated. - parameters = [ - params if params is not None else circuits[i].parameters - for i, params in enumerate(parameters) - ] - # Validate the arguments. - self._validate_arguments(circuits, parameter_values, parameters) - # The priority of run option is as follows: - # options in ``run`` method > QGT's default options > primitive's default setting. - opts = copy(self._default_options) - opts.update_options(**options) - job = AlgorithmJob(self._run, circuits, parameter_values, parameters, **opts.__dict__) - job.submit() - return job - - @abstractmethod - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> QGTResult: - """Compute the QGTs on the given circuits.""" - raise NotImplementedError() - - def _preprocess( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - supported_gates: Sequence[str], - ) -> tuple[Sequence[QuantumCircuit], Sequence[Sequence[float]], Sequence[Sequence[Parameter]]]: - """Preprocess the gradient. This makes a gradient circuit for each circuit. The gradient - circuit is a transpiled circuit by using the supported gates, and has unique parameters. - ``parameter_values`` and ``parameters`` are also updated to match the gradient circuit. - - Args: - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - supported_gates: The supported gates used to transpile the circuit. - - Returns: - The list of gradient circuits, the list of parameter values, and the list of parameters. - parameter_values and parameters are updated to match the gradient circuit. - """ - translator = TranslateParameterizedGates(supported_gates) - g_circuits, g_parameter_values, g_parameters = [], [], [] - for circuit, parameter_value_, parameters_ in zip(circuits, parameter_values, parameters): - circuit_key = _circuit_key(circuit) - if circuit_key not in self._gradient_circuit_cache: - unrolled = translator(circuit) - self._gradient_circuit_cache[circuit_key] = _assign_unique_parameters(unrolled) - gradient_circuit = self._gradient_circuit_cache[circuit_key] - g_circuits.append(gradient_circuit.gradient_circuit) - g_parameter_values.append( - _make_gradient_parameter_values(circuit, gradient_circuit, parameter_value_) - ) - g_parameters_ = [ - g_param - for g_param in gradient_circuit.gradient_circuit.parameters - if g_param in _make_gradient_parameters(gradient_circuit, parameters_) - ] - g_parameters.append(g_parameters_) - return g_circuits, g_parameter_values, g_parameters - - def _postprocess( - self, - results: QGTResult, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - ) -> QGTResult: - """Postprocess the QGTs. This method computes the QGTs of the original circuits - by applying the chain rule to the QGTs of the circuits with unique parameters. - - Args: - results: The computed QGT for the circuits with unique parameters. - circuits: The list of original circuits submitted for gradient computation. - parameter_values: The list of parameter values to be bound to the circuits. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - - Returns: - The QGTs of the original circuits. - """ - qgts, metadata = [], [] - for idx, (circuit, parameter_values_, parameters_) in enumerate( - zip(circuits, parameter_values, parameters) - ): - dtype = complex if self.derivative_type == DerivativeType.COMPLEX else float - qgt: np.ndarray = np.zeros((len(parameters_), len(parameters_)), dtype=dtype) - - gradient_circuit = self._gradient_circuit_cache[_circuit_key(circuit)] - g_parameters = _make_gradient_parameters(gradient_circuit, parameters_) - # Make a map from the gradient parameter to the respective index in the gradient. - # parameters_ = [param for param in circuit.parameters if param in parameters_] - g_parameter_indices = [ - param - for param in gradient_circuit.gradient_circuit.parameters - if param in g_parameters - ] - g_parameter_indices = {param: i for i, param in enumerate(g_parameter_indices)} - rows, cols = np.triu_indices(len(parameters_)) - for row, col in zip(rows, cols): - for g_parameter1, coeff1 in gradient_circuit.parameter_map[parameters_[row]]: - for g_parameter2, coeff2 in gradient_circuit.parameter_map[parameters_[col]]: - if isinstance(coeff1, ParameterExpression): - local_map = { - p: parameter_values_[circuit.parameters.data.index(p)] - for p in coeff1.parameters - } - bound_coeff1 = coeff1.bind(local_map) - else: - bound_coeff1 = coeff1 - if isinstance(coeff2, ParameterExpression): - local_map = { - p: parameter_values_[circuit.parameters.data.index(p)] - for p in coeff2.parameters - } - bound_coeff2 = coeff2.bind(local_map) - else: - bound_coeff2 = coeff2 - qgt[row, col] += ( - float(bound_coeff1) - * float(bound_coeff2) - * results.qgts[idx][ - g_parameter_indices[g_parameter1], g_parameter_indices[g_parameter2] - ] - ) - - if self.derivative_type == DerivativeType.IMAG: - qgt += -1 * np.triu(qgt, k=1).T - else: - qgt += np.triu(qgt, k=1).conjugate().T - qgts.append(qgt) - metadata.append([{"parameters": parameters_}]) - return QGTResult( - qgts=qgts, - derivative_type=self.derivative_type, - metadata=metadata, - options=results.options, - ) - - @staticmethod - def _validate_arguments( - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - ) -> None: - """Validate the arguments of the ``run`` method. - - Args: - circuits: The list of quantum circuits to compute the QGTs. - parameter_values: The list of parameter values to be bound to the circuits. - parameters: The sequence of parameters with respect to which the QGTs should be - computed. - - Raises: - ValueError: Invalid arguments are given. - """ - if len(circuits) != len(parameter_values): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of parameter values ({len(parameter_values)})." - ) - - if len(circuits) != len(parameters): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of the specified parameter sets ({len(parameters)})." - ) - - for i, (circuit, parameter_value) in enumerate(zip(circuits, parameter_values)): - if not circuit.num_parameters: - raise ValueError(f"The {i}-th circuit is not parameterised.") - if len(parameter_value) != circuit.num_parameters: - raise ValueError( - f"The number of values ({len(parameter_value)}) does not match " - f"the number of parameters ({circuit.num_parameters}) for the {i}-th circuit." - ) - - if len(circuits) != len(parameters): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of the list of specified parameters ({len(parameters)})." - ) - - for i, (circuit, parameters_) in enumerate(zip(circuits, parameters)): - if not set(parameters_).issubset(circuit.parameters): - raise ValueError( - f"The {i}-th parameters contains parameters not present in the " - f"{i}-th circuit." - ) - - @property - def options(self) -> Options: - """Return the union of estimator options setting and QGT default options, - where, if the same field is set in both, the QGT's default options override - the primitive's default setting. - - Returns: - The QGT default + estimator options. - """ - return self._get_local_options(self._default_options.__dict__) - - def update_default_options(self, **options): - """Update the gradient's default options setting. - - Args: - **options: The fields to update the default options. - """ - - self._default_options.update_options(**options) - - def _get_local_options(self, options: Options) -> Options: - """Return the union of the primitive's default setting, - the QGT default options, and the options in the ``run`` method. - The order of priority is: options in ``run`` method > QGT's default options > primitive's - default setting. - - Args: - options: The fields to update the options - - Returns: - The QGT default + estimator + run options. - """ - opts = copy(self._estimator.options) - opts.update_options(**options) - return opts diff --git a/qiskit/algorithms/gradients/base/base_sampler_gradient.py b/qiskit/algorithms/gradients/base/base_sampler_gradient.py deleted file mode 100644 index b4947365fd5d..000000000000 --- a/qiskit/algorithms/gradients/base/base_sampler_gradient.py +++ /dev/null @@ -1,296 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# 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. - -""" -Abstract base class of gradient for ``Sampler``. -""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections import defaultdict -from collections.abc import Sequence -from copy import copy - -from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit -from qiskit.primitives import BaseSampler -from qiskit.primitives.utils import _circuit_key -from qiskit.providers import Options -from qiskit.transpiler.passes import TranslateParameterizedGates - -from .sampler_gradient_result import SamplerGradientResult -from ..utils import ( - GradientCircuit, - _assign_unique_parameters, - _make_gradient_parameters, - _make_gradient_parameter_values, -) - -from ...algorithm_job import AlgorithmJob - - -class BaseSamplerGradient(ABC): - """Base class for a ``SamplerGradient`` to compute the gradients of the sampling probability.""" - - def __init__(self, sampler: BaseSampler, options: Options | None = None): - """ - Args: - sampler: The sampler used to compute the gradients. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - """ - self._sampler: BaseSampler = sampler - self._default_options = Options() - if options is not None: - self._default_options.update_options(**options) - self._gradient_circuit_cache: dict[tuple, GradientCircuit] = {} - - def run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter] | None] | None = None, - **options, - ) -> AlgorithmJob: - """Run the job of the sampler gradient on the given circuits. - - Args: - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of - the specified parameters. Each sequence of parameters corresponds to a circuit in - ``circuits``. Defaults to None, which means that the gradients of all parameters in - each circuit are calculated. None in the sequence means that the gradients of all - parameters in the corresponding circuit are calculated. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - Returns: - The job object of the gradients of the sampling probability. The i-th result - corresponds to ``circuits[i]`` evaluated with parameters bound as ``parameter_values[i]``. - The j-th quasi-probability distribution in the i-th result corresponds to the gradients of - the sampling probability for the j-th parameter in ``circuits[i]``. - - Raises: - ValueError: Invalid arguments are given. - """ - if isinstance(circuits, QuantumCircuit): - # Allow a single circuit to be passed in. - circuits = (circuits,) - if parameters is None: - # If parameters is None, we calculate the gradients of all parameters in each circuit. - parameters = [circuit.parameters for circuit in circuits] - else: - # If parameters is not None, we calculate the gradients of the specified parameters. - # None in parameters means that the gradients of all parameters in the corresponding - # circuit are calculated. - parameters = [ - params if params is not None else circuits[i].parameters - for i, params in enumerate(parameters) - ] - # Validate the arguments. - self._validate_arguments(circuits, parameter_values, parameters) - # The priority of run option is as follows: - # options in `run` method > gradient's default options > primitive's default options. - opts = copy(self._default_options) - opts.update_options(**options) - job = AlgorithmJob(self._run, circuits, parameter_values, parameters, **opts.__dict__) - job.submit() - return job - - @abstractmethod - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the sampler gradients on the given circuits.""" - raise NotImplementedError() - - def _preprocess( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - supported_gates: Sequence[str], - ) -> tuple[Sequence[QuantumCircuit], Sequence[Sequence[float]], Sequence[set[Parameter]]]: - """Preprocess the gradient. This makes a gradient circuit for each circuit. The gradient - circuit is a transpiled circuit by using the supported gates, and has unique parameters. - ``parameter_values`` and ``parameters`` are also updated to match the gradient circuit. - - Args: - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - supported_gates: The supported gates used to transpile the circuit. - - Returns: - The list of gradient circuits, the list of parameter values, and the list of parameters. - parameter_values and parameters are updated to match the gradient circuit. - """ - translator = TranslateParameterizedGates(supported_gates) - g_circuits, g_parameter_values, g_parameters = [], [], [] - for circuit, parameter_value_, parameters_ in zip(circuits, parameter_values, parameters): - circuit_key = _circuit_key(circuit) - if circuit_key not in self._gradient_circuit_cache: - unrolled = translator(circuit) - self._gradient_circuit_cache[circuit_key] = _assign_unique_parameters(unrolled) - gradient_circuit = self._gradient_circuit_cache[circuit_key] - g_circuits.append(gradient_circuit.gradient_circuit) - g_parameter_values.append( - _make_gradient_parameter_values(circuit, gradient_circuit, parameter_value_) - ) - g_parameters.append(_make_gradient_parameters(gradient_circuit, parameters_)) - return g_circuits, g_parameter_values, g_parameters - - def _postprocess( - self, - results: SamplerGradientResult, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter] | None], - ) -> SamplerGradientResult: - """Postprocess the gradient. This computes the gradient of the original circuit from the - gradient of the gradient circuit by using the chain rule. - - Args: - results: The results of the gradient of the gradient circuits. - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - - Returns: - The results of the gradient of the original circuits. - """ - gradients, metadata = [], [] - for idx, (circuit, parameter_values_, parameters_) in enumerate( - zip(circuits, parameter_values, parameters) - ): - gradient_circuit = self._gradient_circuit_cache[_circuit_key(circuit)] - g_parameters = _make_gradient_parameters(gradient_circuit, parameters_) - # Make a map from the gradient parameter to the respective index in the gradient. - g_parameter_indices = {param: i for i, param in enumerate(g_parameters)} - # Compute the original gradient from the gradient of the gradient circuit - # by using the chain rule. - gradient = [] - for parameter in parameters_: - grad_dist: dict[int, float] = defaultdict(float) - for g_parameter, coeff in gradient_circuit.parameter_map[parameter]: - # Compute the coefficient - if isinstance(coeff, ParameterExpression): - local_map = { - p: parameter_values_[circuit.parameters.data.index(p)] - for p in coeff.parameters - } - bound_coeff = coeff.bind(local_map) - else: - bound_coeff = coeff - # The original gradient is a sum of the gradients of the parameters in the - # gradient circuit multiplied by the coefficients. - unique_gradient = results.gradients[idx][g_parameter_indices[g_parameter]] - for key, value in unique_gradient.items(): - grad_dist[key] += float(bound_coeff) * value - gradient.append(dict(grad_dist)) - gradients.append(gradient) - metadata.append([{"parameters": parameters_}]) - return SamplerGradientResult( - gradients=gradients, metadata=metadata, options=results.options - ) - - @staticmethod - def _validate_arguments( - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - ) -> None: - """Validate the arguments of the ``run`` method. - - Args: - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - - Raises: - ValueError: Invalid arguments are given. - """ - if len(circuits) != len(parameter_values): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of parameter value sets ({len(parameter_values)})." - ) - - for i, (circuit, parameter_value) in enumerate(zip(circuits, parameter_values)): - if not circuit.num_parameters: - raise ValueError(f"The {i}-th circuit is not parameterised.") - - if len(parameter_value) != circuit.num_parameters: - raise ValueError( - f"The number of values ({len(parameter_value)}) does not match " - f"the number of parameters ({circuit.num_parameters}) for the {i}-th circuit." - ) - - if len(circuits) != len(parameters): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of the specified parameter sets ({len(parameters)})." - ) - - for i, (circuit, parameters_) in enumerate(zip(circuits, parameters)): - if not set(parameters_).issubset(circuit.parameters): - raise ValueError( - f"The {i}-th parameter set contains parameters not present in the " - f"{i}-th circuit." - ) - - @property - def options(self) -> Options: - """Return the union of sampler options setting and gradient default options, - where, if the same field is set in both, the gradient's default options override - the primitive's default setting. - - Returns: - The gradient default + sampler options. - """ - return self._get_local_options(self._default_options.__dict__) - - def update_default_options(self, **options): - """Update the gradient's default options setting. - - Args: - **options: The fields to update the default options. - """ - - self._default_options.update_options(**options) - - def _get_local_options(self, options: Options) -> Options: - """Return the union of the primitive's default setting, - the gradient default options, and the options in the ``run`` method. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - - Args: - options: The fields to update the options - - Returns: - The gradient default + sampler + run options. - """ - opts = copy(self._sampler.options) - opts.update_options(**options) - return opts diff --git a/qiskit/algorithms/gradients/base/estimator_gradient_result.py b/qiskit/algorithms/gradients/base/estimator_gradient_result.py deleted file mode 100644 index ada3bdb2b7bf..000000000000 --- a/qiskit/algorithms/gradients/base/estimator_gradient_result.py +++ /dev/null @@ -1,35 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -""" -Estimator result class -""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any - -import numpy as np - -from qiskit.providers import Options - - -@dataclass(frozen=True) -class EstimatorGradientResult: - """Result of EstimatorGradient.""" - - gradients: list[np.ndarray] - """The gradients of the expectation values.""" - metadata: list[dict[str, Any]] - """Additional information about the job.""" - options: Options - """Primitive runtime options for the execution of the job.""" diff --git a/qiskit/algorithms/gradients/base/qgt_result.py b/qiskit/algorithms/gradients/base/qgt_result.py deleted file mode 100644 index f110e1c68381..000000000000 --- a/qiskit/algorithms/gradients/base/qgt_result.py +++ /dev/null @@ -1,39 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. -""" -QGT result class -""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any - -import numpy as np - -from qiskit.providers import Options - -from ..utils import DerivativeType - - -@dataclass(frozen=True) -class QGTResult: - """Result of QGT.""" - - qgts: list[np.ndarray] - """The QGT.""" - derivative_type: DerivativeType - """The type of derivative.""" - metadata: list[dict[str, Any]] - """Additional information about the job.""" - options: Options - """Primitive runtime options for the execution of the job.""" diff --git a/qiskit/algorithms/gradients/base/sampler_gradient_result.py b/qiskit/algorithms/gradients/base/sampler_gradient_result.py deleted file mode 100644 index b78b468f1b9f..000000000000 --- a/qiskit/algorithms/gradients/base/sampler_gradient_result.py +++ /dev/null @@ -1,33 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -""" -Sampler result class -""" - -from __future__ import annotations - -from typing import Any -from dataclasses import dataclass - -from qiskit.providers import Options - - -@dataclass(frozen=True) -class SamplerGradientResult: - """Result of SamplerGradient.""" - - gradients: list[list[dict[int, float]]] - """The gradients of the sample probabilities.""" - metadata: list[dict[str, Any]] - """Additional information about the job.""" - options: Options - """Primitive runtime options for the execution of the job.""" diff --git a/qiskit/algorithms/gradients/finite_diff/__init__.py b/qiskit/algorithms/gradients/finite_diff/__init__.py deleted file mode 100644 index 8f5fd2a37f84..000000000000 --- a/qiskit/algorithms/gradients/finite_diff/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# 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. diff --git a/qiskit/algorithms/gradients/finite_diff/finite_diff_estimator_gradient.py b/qiskit/algorithms/gradients/finite_diff/finite_diff_estimator_gradient.py deleted file mode 100644 index ea1b987c2ff2..000000000000 --- a/qiskit/algorithms/gradients/finite_diff/finite_diff_estimator_gradient.py +++ /dev/null @@ -1,148 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Gradient of Sampler with Finite difference method.""" - -from __future__ import annotations - -from collections.abc import Sequence -from typing import Literal - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.providers import Options -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..base.base_estimator_gradient import BaseEstimatorGradient -from ..base.estimator_gradient_result import EstimatorGradientResult - -from ...exceptions import AlgorithmError - - -class FiniteDiffEstimatorGradient(BaseEstimatorGradient): - """ - Compute the gradients of the expectation values by finite difference method [1]. - - **Reference:** - [1] `Finite difference method `_ - """ - - def __init__( - self, - estimator: BaseEstimator, - epsilon: float, - options: Options | None = None, - *, - method: Literal["central", "forward", "backward"] = "central", - ): - r""" - Args: - estimator: The estimator used to compute the gradients. - epsilon: The offset size for the finite difference gradients. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - method: The computation method of the gradients. - - - ``central`` computes :math:`\frac{f(x+e)-f(x-e)}{2e}`, - - ``forward`` computes :math:`\frac{f(x+e) - f(x)}{e}`, - - ``backward`` computes :math:`\frac{f(x)-f(x-e)}{e}` - - where :math:`e` is epsilon. - - Raises: - ValueError: If ``epsilon`` is not positive. - TypeError: If ``method`` is invalid. - """ - if epsilon <= 0: - raise ValueError(f"epsilon ({epsilon}) should be positive.") - self._epsilon = epsilon - if method not in ("central", "forward", "backward"): - raise TypeError( - f"The argument method should be central, forward, or backward: {method} is given." - ) - self._method = method - super().__init__(estimator, options) - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - job_circuits, job_observables, job_param_values, metadata = [], [], [], [] - all_n = [] - - for circuit, observable, parameter_values_, parameters_ in zip( - circuits, observables, parameter_values, parameters - ): - # Indices of parameters to be differentiated - indices = [circuit.parameters.data.index(p) for p in parameters_] - metadata.append({"parameters": parameters_}) - - # Combine inputs into a single job to reduce overhead. - offset = np.identity(circuit.num_parameters)[indices, :] - if self._method == "central": - plus = parameter_values_ + self._epsilon * offset - minus = parameter_values_ - self._epsilon * offset - n = 2 * len(indices) - job_circuits.extend([circuit] * n) - job_observables.extend([observable] * n) - job_param_values.extend(plus.tolist() + minus.tolist()) - all_n.append(n) - elif self._method == "forward": - plus = parameter_values_ + self._epsilon * offset - n = len(indices) + 1 - job_circuits.extend([circuit] * n) - job_observables.extend([observable] * n) - job_param_values.extend([parameter_values_] + plus.tolist()) - all_n.append(n) - elif self._method == "backward": - minus = parameter_values_ - self._epsilon * offset - n = len(indices) + 1 - job_circuits.extend([circuit] * n) - job_observables.extend([observable] * n) - job_param_values.extend([parameter_values_] + minus.tolist()) - all_n.append(n) - - # Run the single job with all circuits. - job = self._estimator.run(job_circuits, job_observables, job_param_values, **options) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - - # Compute the gradients - gradients = [] - partial_sum_n = 0 - for n in all_n: - if self._method == "central": - result = results.values[partial_sum_n : partial_sum_n + n] - gradient = (result[: n // 2] - result[n // 2 :]) / (2 * self._epsilon) - elif self._method == "forward": - result = results.values[partial_sum_n : partial_sum_n + n] - gradient = (result[1:] - result[0]) / self._epsilon - elif self._method == "backward": - result = results.values[partial_sum_n : partial_sum_n + n] - gradient = (result[0] - result[1:]) / self._epsilon - partial_sum_n += n - gradients.append(gradient) - - opt = self._get_local_options(options) - return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/finite_diff/finite_diff_sampler_gradient.py b/qiskit/algorithms/gradients/finite_diff/finite_diff_sampler_gradient.py deleted file mode 100644 index bc250286c828..000000000000 --- a/qiskit/algorithms/gradients/finite_diff/finite_diff_sampler_gradient.py +++ /dev/null @@ -1,161 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Gradient of Sampler with Finite difference method.""" - -from __future__ import annotations - -from collections import defaultdict -from typing import Literal, Sequence - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives import BaseSampler -from qiskit.providers import Options - -from ..base.base_sampler_gradient import BaseSamplerGradient -from ..base.sampler_gradient_result import SamplerGradientResult - -from ...exceptions import AlgorithmError - - -class FiniteDiffSamplerGradient(BaseSamplerGradient): - """ - Compute the gradients of the sampling probability by finite difference method [1]. - - **Reference:** - [1] `Finite difference method `_ - """ - - def __init__( - self, - sampler: BaseSampler, - epsilon: float, - options: Options | None = None, - *, - method: Literal["central", "forward", "backward"] = "central", - ): - r""" - Args: - sampler: The sampler used to compute the gradients. - epsilon: The offset size for the finite difference gradients. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - method: The computation method of the gradients. - - - ``central`` computes :math:`\frac{f(x+e)-f(x-e)}{2e}`, - - ``forward`` computes :math:`\frac{f(x+e) - f(x)}{e}`, - - ``backward`` computes :math:`\frac{f(x)-f(x-e)}{e}` - - where :math:`e` is epsilon. - - Raises: - ValueError: If ``epsilon`` is not positive. - TypeError: If ``method`` is invalid. - """ - if epsilon <= 0: - raise ValueError(f"epsilon ({epsilon}) should be positive.") - self._epsilon = epsilon - if method not in ("central", "forward", "backward"): - raise TypeError( - f"The argument method should be central, forward, or backward: {method} is given." - ) - self._method = method - super().__init__(sampler, options) - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the sampler gradients on the given circuits.""" - job_circuits, job_param_values, metadata = [], [], [] - all_n = [] - for circuit, parameter_values_, parameters_ in zip(circuits, parameter_values, parameters): - # Indices of parameters to be differentiated - indices = [circuit.parameters.data.index(p) for p in parameters_] - metadata.append({"parameters": parameters_}) - # Combine inputs into a single job to reduce overhead. - offset = np.identity(circuit.num_parameters)[indices, :] - if self._method == "central": - plus = parameter_values_ + self._epsilon * offset - minus = parameter_values_ - self._epsilon * offset - n = 2 * len(indices) - job_circuits.extend([circuit] * n) - job_param_values.extend(plus.tolist() + minus.tolist()) - all_n.append(n) - elif self._method == "forward": - plus = parameter_values_ + self._epsilon * offset - n = len(indices) + 1 - job_circuits.extend([circuit] * n) - job_param_values.extend([parameter_values_] + plus.tolist()) - all_n.append(n) - elif self._method == "backward": - minus = parameter_values_ - self._epsilon * offset - n = len(indices) + 1 - job_circuits.extend([circuit] * n) - job_param_values.extend([parameter_values_] + minus.tolist()) - all_n.append(n) - - # Run the single job with all circuits. - job = self._sampler.run(job_circuits, job_param_values, **options) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Sampler job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for n in all_n: - gradient = [] - if self._method == "central": - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - for dist_plus, dist_minus in zip(result[: n // 2], result[n // 2 :]): - grad_dist: dict[int, float] = defaultdict(float) - for key, value in dist_plus.items(): - grad_dist[key] += value / (2 * self._epsilon) - for key, value in dist_minus.items(): - grad_dist[key] -= value / (2 * self._epsilon) - gradient.append(dict(grad_dist)) - elif self._method == "forward": - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - dist_zero = result[0] - for dist_plus in result[1:]: - grad_dist = defaultdict(float) - for key, value in dist_plus.items(): - grad_dist[key] += value / self._epsilon - for key, value in dist_zero.items(): - grad_dist[key] -= value / self._epsilon - gradient.append(dict(grad_dist)) - - elif self._method == "backward": - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - dist_zero = result[0] - for dist_minus in result[1:]: - grad_dist = defaultdict(float) - for key, value in dist_zero.items(): - grad_dist[key] += value / self._epsilon - for key, value in dist_minus.items(): - grad_dist[key] -= value / self._epsilon - gradient.append(dict(grad_dist)) - - partial_sum_n += n - gradients.append(gradient) - - opt = self._get_local_options(options) - return SamplerGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/lin_comb/__init__.py b/qiskit/algorithms/gradients/lin_comb/__init__.py deleted file mode 100644 index 8f5fd2a37f84..000000000000 --- a/qiskit/algorithms/gradients/lin_comb/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# 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. diff --git a/qiskit/algorithms/gradients/lin_comb/lin_comb_estimator_gradient.py b/qiskit/algorithms/gradients/lin_comb/lin_comb_estimator_gradient.py deleted file mode 100644 index 93deb48b5820..000000000000 --- a/qiskit/algorithms/gradients/lin_comb/lin_comb_estimator_gradient.py +++ /dev/null @@ -1,195 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. -""" -Gradient of probabilities with linear combination of unitaries (LCU) -""" - -from __future__ import annotations - -from collections.abc import Sequence - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.primitives.utils import init_observable, _circuit_key -from qiskit.providers import Options -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..base.base_estimator_gradient import BaseEstimatorGradient -from ..base.estimator_gradient_result import EstimatorGradientResult -from ..utils import DerivativeType, _make_lin_comb_gradient_circuit, _make_lin_comb_observables - -from ...exceptions import AlgorithmError - - -class LinCombEstimatorGradient(BaseEstimatorGradient): - """Compute the gradients of the expectation values. - This method employs a linear combination of unitaries [1]. - - **Reference:** - [1] Schuld et al., Evaluating analytic gradients on quantum hardware, 2018 - `arXiv:1811.11184 `_ - """ - - SUPPORTED_GATES = [ - "rx", - "ry", - "rz", - "rzx", - "rzz", - "ryy", - "rxx", - "cx", - "cy", - "cz", - "ccx", - "swap", - "iswap", - "h", - "t", - "s", - "sdg", - "x", - "y", - "z", - ] - - def __init__( - self, - estimator: BaseEstimator, - derivative_type: DerivativeType = DerivativeType.REAL, - options: Options | None = None, - ): - r""" - Args: - estimator: The estimator used to compute the gradients. - derivative_type: The type of derivative. Can be either ``DerivativeType.REAL`` - ``DerivativeType.IMAG``, or ``DerivativeType.COMPLEX``. Defaults to - ``DerivativeType.REAL``. - - - ``DerivativeType.REAL`` computes :math:`2 \mathrm{Re}[⟨ψ(ω)|O(θ)|dω ψ(ω)〉]`. - - ``DerivativeType.IMAG`` computes :math:`2 \mathrm{Im}[⟨ψ(ω)|O(θ)|dω ψ(ω)〉]`. - - ``DerivativeType.COMPLEX`` computes :math:`2 ⟨ψ(ω)|O(θ)|dω ψ(ω)〉`. - - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting. - """ - self._lin_comb_cache: dict[tuple, dict[Parameter, QuantumCircuit]] = {} - super().__init__(estimator, options, derivative_type=derivative_type) - - @BaseEstimatorGradient.derivative_type.setter - def derivative_type(self, derivative_type: DerivativeType) -> None: - """Set the derivative type.""" - self._derivative_type = derivative_type - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique( - g_circuits, observables, g_parameter_values, g_parameters, **options - ) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - job_circuits, job_observables, job_param_values, metadata = [], [], [], [] - all_n = [] - for circuit, observable, parameter_values_, parameters_ in zip( - circuits, observables, parameter_values, parameters - ): - # Prepare circuits for the gradient of the specified parameters. - meta = {"parameters": parameters_} - circuit_key = _circuit_key(circuit) - if circuit_key not in self._lin_comb_cache: - # Cache the circuits for the linear combination of unitaries. - # We only cache the circuits for the specified parameters in the future. - self._lin_comb_cache[circuit_key] = _make_lin_comb_gradient_circuit( - circuit, add_measurement=False - ) - lin_comb_circuits = self._lin_comb_cache[circuit_key] - gradient_circuits = [] - for param in parameters_: - gradient_circuits.append(lin_comb_circuits[param]) - n = len(gradient_circuits) - # Make the observable as :class:`~qiskit.quantum_info.SparsePauliOp` and - # add an ancillary operator to compute the gradient. - observable = init_observable(observable) - observable_1, observable_2 = _make_lin_comb_observables( - observable, self._derivative_type - ) - # If its derivative type is `DerivativeType.COMPLEX`, calculate the gradient - # of the real and imaginary parts separately. - meta["derivative_type"] = self.derivative_type - metadata.append(meta) - # Combine inputs into a single job to reduce overhead. - if self._derivative_type == DerivativeType.COMPLEX: - job_circuits.extend(gradient_circuits * 2) - job_observables.extend([observable_1] * n + [observable_2] * n) - job_param_values.extend([parameter_values_] * 2 * n) - all_n.append(2 * n) - else: - job_circuits.extend(gradient_circuits) - job_observables.extend([observable_1] * n) - job_param_values.extend([parameter_values_] * n) - all_n.append(n) - - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) - try: - results = job.result() - except AlgorithmError as exc: - raise AlgorithmError("Estimator job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for n in all_n: - # this disable is needed as Pylint does not understand derivative_type is a property if - # it is only defined in the base class and the getter is in the child - # pylint: disable=comparison-with-callable - if self.derivative_type == DerivativeType.COMPLEX: - gradient = np.zeros(n // 2, dtype="complex") - gradient.real = results.values[partial_sum_n : partial_sum_n + n // 2] - gradient.imag = results.values[partial_sum_n + n // 2 : partial_sum_n + n] - - else: - gradient = np.real(results.values[partial_sum_n : partial_sum_n + n]) - partial_sum_n += n - gradients.append(gradient) - - opt = self._get_local_options(options) - return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/lin_comb/lin_comb_qgt.py b/qiskit/algorithms/gradients/lin_comb/lin_comb_qgt.py deleted file mode 100644 index 0a9e05a9344a..000000000000 --- a/qiskit/algorithms/gradients/lin_comb/lin_comb_qgt.py +++ /dev/null @@ -1,257 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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 class for the Linear Combination Quantum Gradient Tensor. -""" - -from __future__ import annotations - -from collections.abc import Sequence - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives import BaseEstimator -from qiskit.primitives.utils import _circuit_key -from qiskit.providers import Options -from qiskit.quantum_info import SparsePauliOp - -from ..base.base_qgt import BaseQGT -from .lin_comb_estimator_gradient import LinCombEstimatorGradient -from ..base.qgt_result import QGTResult -from ..utils import DerivativeType, _make_lin_comb_qgt_circuit, _make_lin_comb_observables - -from ...exceptions import AlgorithmError - - -class LinCombQGT(BaseQGT): - """Computes the Quantum Geometric Tensor (QGT) given a pure, parameterized quantum state. - - This method employs a linear combination of unitaries [1]. - - **Reference:** - - [1]: Schuld et al., "Evaluating analytic gradients on quantum hardware" (2018). - `arXiv:1811.11184 `_ - """ - - SUPPORTED_GATES = [ - "rx", - "ry", - "rz", - "rzx", - "rzz", - "ryy", - "rxx", - "cx", - "cy", - "cz", - "ccx", - "swap", - "iswap", - "h", - "t", - "s", - "sdg", - "x", - "y", - "z", - ] - - def __init__( - self, - estimator: BaseEstimator, - phase_fix: bool = True, - derivative_type: DerivativeType = DerivativeType.COMPLEX, - options: Options | None = None, - ): - r""" - Args: - estimator: The estimator used to compute the QGT. - phase_fix: Whether to calculate the second term (phase fix) of the QGT, which is - :math:`\langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle`. - Default to ``True``. - derivative_type: The type of derivative. Can be either ``DerivativeType.REAL`` - ``DerivativeType.IMAG``, or ``DerivativeType.COMPLEX``. Defaults to - ``DerivativeType.REAL``. - - - ``DerivativeType.REAL`` computes - - .. math:: - - \mathrm{Re(QGT)}_{ij}= \mathrm{Re}[\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - - ``DerivativeType.IMAG`` computes - - .. math:: - - \mathrm{Re(QGT)}_{ij}= \mathrm{Im}[\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - - ``DerivativeType.COMPLEX`` computes - - .. math:: - - \mathrm{QGT}_{ij}= [\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - options: Backend runtime options used for circuit execution. The order of priority is: - options in ``run`` method > QGT's default options > primitive's default - setting. Higher priority setting overrides lower priority setting. - """ - super().__init__(estimator, phase_fix, derivative_type, options=options) - self._gradient = LinCombEstimatorGradient( - estimator, derivative_type=DerivativeType.COMPLEX, options=options - ) - self._lin_comb_qgt_circuit_cache: dict[ - tuple, dict[tuple[Parameter, Parameter], QuantumCircuit] - ] = {} - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> QGTResult: - """Compute the QGT on the given circuits.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique(g_circuits, g_parameter_values, g_parameters, **options) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> QGTResult: - """Compute the QGTs on the given circuits.""" - job_circuits, job_observables, job_param_values, metadata = [], [], [], [] - all_n, all_m, phase_fixes = [], [], [] - - for circuit, parameter_values_, parameters_ in zip(circuits, parameter_values, parameters): - # Prepare circuits for the gradient of the specified parameters. - parameters_ = [p for p in circuit.parameters if p in parameters_] - meta = {"parameters": parameters_} - metadata.append(meta) - - # Compute the first term in the QGT - circuit_key = _circuit_key(circuit) - if circuit_key not in self._lin_comb_qgt_circuit_cache: - # generate the all of the circuits for the first term in the QGT and cache them. - # Only the circuit related to specified parameters will be executed. - # In the future, we can generate the specified circuits on demand. - self._lin_comb_qgt_circuit_cache[circuit_key] = _make_lin_comb_qgt_circuit(circuit) - lin_comb_qgt_circuits = self._lin_comb_qgt_circuit_cache[circuit_key] - - qgt_circuits = [] - rows, cols = np.triu_indices(len(parameters_)) - for row, col in zip(rows, cols): - param_i = parameters_[row] - param_j = parameters_[col] - qgt_circuits.append(lin_comb_qgt_circuits[(param_i, param_j)]) - - observable = SparsePauliOp.from_list([("I" * circuit.num_qubits, 1)]) - observable_1, observable_2 = _make_lin_comb_observables( - observable, self._derivative_type - ) - - n = len(qgt_circuits) - if self._derivative_type == DerivativeType.COMPLEX: - job_circuits.extend(qgt_circuits * 2) - job_observables.extend([observable_1] * n + [observable_2] * n) - job_param_values.extend([parameter_values_] * 2 * n) - all_m.append(len(parameters_)) - all_n.append(2 * n) - else: - job_circuits.extend(qgt_circuits) - job_observables.extend([observable_1] * n) - job_param_values.extend([parameter_values_] * n) - all_m.append(len(parameters_)) - all_n.append(n) - - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) - - if self._phase_fix: - # Compute the second term in the QGT if phase fix is enabled. - phase_fix_obs = [ - SparsePauliOp.from_list([("I" * circuit.num_qubits, 1)]) for circuit in circuits - ] - phase_fix_job = self._gradient.run( - circuits=circuits, - observables=phase_fix_obs, - parameter_values=parameter_values, - parameters=parameters, - **options, - ) - - try: - results = job.result() - if self._phase_fix: - gradient_results = phase_fix_job.result() - except AlgorithmError as exc: - raise AlgorithmError("Estimator job or gradient job failed.") from exc - - # Compute the phase fix - if self._phase_fix: - for gradient in gradient_results.gradients: - phase_fix = np.outer(np.conjugate(gradient), gradient) - # Select the real or imaginary part of the phase fix if needed - if self.derivative_type == DerivativeType.REAL: - phase_fix = np.real(phase_fix) - elif self.derivative_type == DerivativeType.IMAG: - phase_fix = np.imag(phase_fix) - phase_fixes.append(phase_fix) - else: - phase_fixes = [0 for i in range(len(circuits))] - # Compute the QGT - qgts = [] - partial_sum_n = 0 - for i, (n, m) in enumerate(zip(all_n, all_m)): - qgt = np.zeros((m, m), dtype="complex") - # Compute the first term in the QGT - if self.derivative_type == DerivativeType.COMPLEX: - qgt[np.triu_indices(m)] = results.values[partial_sum_n : partial_sum_n + n // 2] - qgt[np.triu_indices(m)] += ( - 1j * results.values[partial_sum_n + n // 2 : partial_sum_n + n] - ) - elif self.derivative_type == DerivativeType.REAL: - qgt[np.triu_indices(m)] = results.values[partial_sum_n : partial_sum_n + n] - elif self.derivative_type == DerivativeType.IMAG: - qgt[np.triu_indices(m)] = 1j * results.values[partial_sum_n : partial_sum_n + n] - - # Add the conjugate of the upper triangle to the lower triangle - qgt += np.triu(qgt, k=1).conjugate().T - if self.derivative_type == DerivativeType.REAL: - qgt = np.real(qgt) - elif self.derivative_type == DerivativeType.IMAG: - qgt = np.imag(qgt) - - # Subtract the phase fix from the QGT - qgt = qgt - phase_fixes[i] - partial_sum_n += n - qgts.append(qgt / 4) - - opt = self._get_local_options(options) - return QGTResult( - qgts=qgts, derivative_type=self.derivative_type, metadata=metadata, options=opt - ) diff --git a/qiskit/algorithms/gradients/lin_comb/lin_comb_sampler_gradient.py b/qiskit/algorithms/gradients/lin_comb/lin_comb_sampler_gradient.py deleted file mode 100644 index 759e77d460bb..000000000000 --- a/qiskit/algorithms/gradients/lin_comb/lin_comb_sampler_gradient.py +++ /dev/null @@ -1,147 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -""" -Gradient of probabilities with linear combination of unitaries (LCU) -""" - -from __future__ import annotations - -from collections import defaultdict -from collections.abc import Sequence - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives import BaseSampler -from qiskit.primitives.utils import _circuit_key -from qiskit.providers import Options - -from ..base.base_sampler_gradient import BaseSamplerGradient -from ..base.sampler_gradient_result import SamplerGradientResult -from ..utils import _make_lin_comb_gradient_circuit - -from ...exceptions import AlgorithmError - - -class LinCombSamplerGradient(BaseSamplerGradient): - """Compute the gradients of the sampling probability. - This method employs a linear combination of unitaries [1]. - - **Reference:** - [1] Schuld et al., Evaluating analytic gradients on quantum hardware, 2018 - `arXiv:1811.11184 `_ - """ - - SUPPORTED_GATES = [ - "rx", - "ry", - "rz", - "rzx", - "rzz", - "ryy", - "rxx", - "cx", - "cy", - "cz", - "ccx", - "swap", - "iswap", - "h", - "t", - "s", - "sdg", - "x", - "y", - "z", - ] - - def __init__(self, sampler: BaseSampler, options: Options | None = None): - """ - Args: - sampler: The sampler used to compute the gradients. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - """ - self._lin_comb_cache: dict[tuple, dict[Parameter, QuantumCircuit]] = {} - super().__init__(sampler, options) - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the estimator gradients on the given circuits.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique(g_circuits, g_parameter_values, g_parameters, **options) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the sampler gradients on the given circuits.""" - job_circuits, job_param_values, metadata = [], [], [] - all_n = [] - for circuit, parameter_values_, parameters_ in zip(circuits, parameter_values, parameters): - # Prepare circuits for the gradient of the specified parameters. - metadata.append({"parameters": parameters_}) - circuit_key = _circuit_key(circuit) - if circuit_key not in self._lin_comb_cache: - # Cache the circuits for the linear combination of unitaries. - # We only cache the circuits for the specified parameters in the future. - self._lin_comb_cache[circuit_key] = _make_lin_comb_gradient_circuit( - circuit, add_measurement=True - ) - lin_comb_circuits = self._lin_comb_cache[circuit_key] - gradient_circuits = [] - for param in parameters_: - gradient_circuits.append(lin_comb_circuits[param]) - # Combine inputs into a single job to reduce overhead. - n = len(gradient_circuits) - job_circuits.extend(gradient_circuits) - job_param_values.extend([parameter_values_] * n) - all_n.append(n) - - # Run the single job with all circuits. - job = self._sampler.run(job_circuits, job_param_values, **options) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Sampler job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for i, n in enumerate(all_n): - gradient = [] - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - m = 2 ** circuits[i].num_qubits - for dist in result: - grad_dist: dict[int, float] = defaultdict(float) - for key, value in dist.items(): - if key < m: - grad_dist[key] += value - else: - grad_dist[key - m] -= value - gradient.append(dict(grad_dist)) - gradients.append(gradient) - partial_sum_n += n - - opt = self._get_local_options(options) - return SamplerGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/param_shift/__init__.py b/qiskit/algorithms/gradients/param_shift/__init__.py deleted file mode 100644 index 8f5fd2a37f84..000000000000 --- a/qiskit/algorithms/gradients/param_shift/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# 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. diff --git a/qiskit/algorithms/gradients/param_shift/param_shift_estimator_gradient.py b/qiskit/algorithms/gradients/param_shift/param_shift_estimator_gradient.py deleted file mode 100644 index ef334a291e6e..000000000000 --- a/qiskit/algorithms/gradients/param_shift/param_shift_estimator_gradient.py +++ /dev/null @@ -1,123 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -""" -Gradient of probabilities with parameter shift -""" - -from __future__ import annotations - -from collections.abc import Sequence - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..base.base_estimator_gradient import BaseEstimatorGradient -from ..base.estimator_gradient_result import EstimatorGradientResult -from ..utils import _make_param_shift_parameter_values - -from ...exceptions import AlgorithmError - - -class ParamShiftEstimatorGradient(BaseEstimatorGradient): - """ - Compute the gradients of the expectation values by the parameter shift rule [1]. - - **Reference:** - [1] Schuld, M., Bergholm, V., Gogolin, C., Izaac, J., and Killoran, N. Evaluating analytic - gradients on quantum hardware, `DOI `_ - """ - - SUPPORTED_GATES = [ - "x", - "y", - "z", - "h", - "rx", - "ry", - "rz", - "p", - "cx", - "cy", - "cz", - "ryy", - "rxx", - "rzz", - "rzx", - ] - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the gradients of the expectation values by the parameter shift rule.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique( - g_circuits, observables, g_parameter_values, g_parameters, **options - ) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - job_circuits, job_observables, job_param_values, metadata = [], [], [], [] - all_n = [] - for circuit, observable, parameter_values_, parameters_ in zip( - circuits, observables, parameter_values, parameters - ): - metadata.append({"parameters": parameters_}) - # Make parameter values for the parameter shift rule. - param_shift_parameter_values = _make_param_shift_parameter_values( - circuit, parameter_values_, parameters_ - ) - # Combine inputs into a single job to reduce overhead. - n = len(param_shift_parameter_values) - job_circuits.extend([circuit] * n) - job_observables.extend([observable] * n) - job_param_values.extend(param_shift_parameter_values) - all_n.append(n) - - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for n in all_n: - result = results.values[partial_sum_n : partial_sum_n + n] - gradient_ = (result[: n // 2] - result[n // 2 :]) / 2 - gradients.append(gradient_) - partial_sum_n += n - - opt = self._get_local_options(options) - return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/param_shift/param_shift_sampler_gradient.py b/qiskit/algorithms/gradients/param_shift/param_shift_sampler_gradient.py deleted file mode 100644 index 642f4b002cd9..000000000000 --- a/qiskit/algorithms/gradients/param_shift/param_shift_sampler_gradient.py +++ /dev/null @@ -1,117 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -""" -Gradient of probabilities with parameter shift -""" - -from __future__ import annotations - -from collections import defaultdict -from collections.abc import Sequence - -from qiskit.circuit import Parameter, QuantumCircuit - -from ..base.base_sampler_gradient import BaseSamplerGradient -from ..base.sampler_gradient_result import SamplerGradientResult -from ..utils import _make_param_shift_parameter_values - -from ...exceptions import AlgorithmError - - -class ParamShiftSamplerGradient(BaseSamplerGradient): - """ - Compute the gradients of the sampling probability by the parameter shift rule [1]. - - **Reference:** - [1] Schuld, M., Bergholm, V., Gogolin, C., Izaac, J., and Killoran, N. Evaluating analytic - gradients on quantum hardware, `DOI `_ - """ - - SUPPORTED_GATES = [ - "x", - "y", - "z", - "h", - "rx", - "ry", - "rz", - "p", - "cx", - "cy", - "cz", - "ryy", - "rxx", - "rzz", - "rzx", - ] - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the estimator gradients on the given circuits.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique(g_circuits, g_parameter_values, g_parameters, **options) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the sampler gradients on the given circuits.""" - job_circuits, job_param_values, metadata = [], [], [] - all_n = [] - for circuit, parameter_values_, parameters_ in zip(circuits, parameter_values, parameters): - metadata.append({"parameters": parameters_}) - # Make parameter values for the parameter shift rule. - param_shift_parameter_values = _make_param_shift_parameter_values( - circuit, parameter_values_, parameters_ - ) - # Combine inputs into a single job to reduce overhead. - n = len(param_shift_parameter_values) - job_circuits.extend([circuit] * n) - job_param_values.extend(param_shift_parameter_values) - all_n.append(n) - - # Run the single job with all circuits. - job = self._sampler.run(job_circuits, job_param_values, **options) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for n in all_n: - gradient = [] - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - for dist_plus, dist_minus in zip(result[: n // 2], result[n // 2 :]): - grad_dist: dict[int, float] = defaultdict(float) - for key, val in dist_plus.items(): - grad_dist[key] += val / 2 - for key, val in dist_minus.items(): - grad_dist[key] -= val / 2 - gradient.append(dict(grad_dist)) - gradients.append(gradient) - partial_sum_n += n - - opt = self._get_local_options(options) - return SamplerGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/qfi.py b/qiskit/algorithms/gradients/qfi.py deleted file mode 100644 index 94aa86fde56a..000000000000 --- a/qiskit/algorithms/gradients/qfi.py +++ /dev/null @@ -1,171 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# 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 class for the Quantum Fisher Information. -""" - -from __future__ import annotations - -from abc import ABC -from collections.abc import Sequence -from copy import copy - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.providers import Options - -from .base.base_qgt import BaseQGT -from .lin_comb.lin_comb_estimator_gradient import DerivativeType -from .qfi_result import QFIResult - -from ..algorithm_job import AlgorithmJob -from ..exceptions import AlgorithmError - - -class QFI(ABC): - r"""Computes the Quantum Fisher Information (QFI) given a pure, - parameterized quantum state. QFI is defined as: - - .. math:: - - \mathrm{QFI}_{ij}= 4 \mathrm{Re}[\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - """ - - def __init__( - self, - qgt: BaseQGT, - options: Options | None = None, - ): - r""" - Args: - qgt: The quantum geometric tensor used to compute the QFI. - options: Backend runtime options used for circuit execution. The order of priority is: - options in ``run`` method > QFI's default options > primitive's default - setting. Higher priority setting overrides lower priority setting. - """ - self._qgt: BaseQGT = qgt - self._default_options = Options() - if options is not None: - self._default_options.update_options(**options) - - def run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter] | None] | None = None, - **options, - ) -> AlgorithmJob: - """Run the job of the QFIs on the given circuits. - - Args: - circuits: The list of quantum circuits to compute the QFIs. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the QFIs of - the specified parameters. Each sequence of parameters corresponds to a circuit in - ``circuits``. Defaults to None, which means that the QFIs of all parameters in - each circuit are calculated. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > QFI's - default options > QGT's default setting. - Higher priority setting overrides lower priority setting. - - Returns: - The job object of the QFIs of the expectation values. The i-th result corresponds to - ``circuits[i]`` evaluated with parameters bound as ``parameter_values[i]``. - """ - - if isinstance(circuits, QuantumCircuit): - # Allow a single circuit to be passed in. - circuits = (circuits,) - - if parameters is None: - # If parameters is None, we calculate the gradients of all parameters in each circuit. - parameters = [circuit.parameters for circuit in circuits] - else: - # If parameters is not None, we calculate the gradients of the specified parameters. - # None in parameters means that the gradients of all parameters in the corresponding - # circuit are calculated. - parameters = [ - params if params is not None else circuits[i].parameters - for i, params in enumerate(parameters) - ] - # The priority of run option is as follows: - # options in ``run`` method > QFI's default options > QGT's default setting. - opts = copy(self._default_options) - opts.update_options(**options) - job = AlgorithmJob(self._run, circuits, parameter_values, parameters, **opts.__dict__) - job.submit() - return job - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> QFIResult: - """Compute the QFI on the given circuits.""" - # Set the derivative type to real - temp_derivative_type, self._qgt.derivative_type = ( - self._qgt.derivative_type, - DerivativeType.REAL, - ) - job = self._qgt.run(circuits, parameter_values, parameters, **options) - - try: - result = job.result() - except AlgorithmError as exc: - raise AlgorithmError("Estimator job or gradient job failed.") from exc - - self._qgt.derivative_type = temp_derivative_type - - return QFIResult( - qfis=[4 * qgt.real for qgt in result.qgts], - metadata=result.metadata, - options=result.options, - ) - - @property - def options(self) -> Options: - """Return the union of QGT's options setting and QFI's default options, - where, if the same field is set in both, the QFI's default options override - the QGT's default setting. - - Returns: - The QFI default + QGT options. - """ - return self._get_local_options(self._default_options.__dict__) - - def update_default_options(self, **options): - """Update the gradient's default options setting. - - Args: - **options: The fields to update the default options. - """ - - self._default_options.update_options(**options) - - def _get_local_options(self, options: Options) -> Options: - """Return the union of the QFI default setting, - the QGT default options, and the options in the ``run`` method. - The order of priority is: options in ``run`` method > QFI's default options > QGT's - default setting. - - Args: - options: The fields to update the options - - Returns: - The QFI default + QGT default + run options. - """ - opts = copy(self._qgt.options) - opts.update_options(**options) - return opts diff --git a/qiskit/algorithms/gradients/qfi_result.py b/qiskit/algorithms/gradients/qfi_result.py deleted file mode 100644 index 47a04021584d..000000000000 --- a/qiskit/algorithms/gradients/qfi_result.py +++ /dev/null @@ -1,35 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -""" -QFI result class -""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any - -import numpy as np - -from qiskit.providers import Options - - -@dataclass(frozen=True) -class QFIResult: - """Result of QFI.""" - - qfis: list[np.ndarray] - """The QFI.""" - metadata: list[dict[str, Any]] - """Additional information about the job.""" - options: Options - """Primitive runtime options for the execution of the job.""" diff --git a/qiskit/algorithms/gradients/reverse/__init__.py b/qiskit/algorithms/gradients/reverse/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/qiskit/algorithms/gradients/reverse/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. diff --git a/qiskit/algorithms/gradients/reverse/bind.py b/qiskit/algorithms/gradients/reverse/bind.py deleted file mode 100644 index 7660f7c836d0..000000000000 --- a/qiskit/algorithms/gradients/reverse/bind.py +++ /dev/null @@ -1,53 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Bind values to a parametrized circuit, accepting binds for non-existing parameters in the circuit.""" - -from __future__ import annotations -from collections.abc import Iterable - -from qiskit.circuit import QuantumCircuit, Parameter - -# pylint: disable=inconsistent-return-statements -def bind( - circuits: QuantumCircuit | Iterable[QuantumCircuit], - parameter_binds: dict[Parameter, float], - inplace: bool = False, -) -> QuantumCircuit | Iterable[QuantumCircuit] | None: - """Bind parameters in a circuit (or list of circuits). - - This method also allows passing parameter binds to parameters that are not in the circuit, - and thereby differs to :meth:`.QuantumCircuit.assign_parameters`. - - Args: - circuits: Input circuit(s). - parameter_binds: A dictionary with ``{Parameter: float}`` pairs determining the values to - which the free parameters in the circuit(s) are bound. - inplace: If ``True``, bind the values in place, otherwise return circuit copies. - - Returns: - The bound circuits, if ``inplace=False``, otherwise None. - - """ - if not isinstance(circuits, Iterable): - circuits = [circuits] - return_list = False - else: - return_list = True - - bound = [] - for circuit in circuits: - existing_parameter_binds = {p: parameter_binds[p] for p in circuit.parameters} - bound.append(circuit.assign_parameters(existing_parameter_binds, inplace=inplace)) - - if not inplace: - return bound if return_list else bound[0] diff --git a/qiskit/algorithms/gradients/reverse/derive_circuit.py b/qiskit/algorithms/gradients/reverse/derive_circuit.py deleted file mode 100644 index 96a2bead60cd..000000000000 --- a/qiskit/algorithms/gradients/reverse/derive_circuit.py +++ /dev/null @@ -1,157 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Split a circuit into subcircuits, each containing a single parameterized gate.""" - -from __future__ import annotations -import itertools -from collections.abc import Sequence - -from qiskit.circuit import QuantumCircuit, Parameter, Gate -from qiskit.circuit.library import RXGate, RYGate, RZGate, CRXGate, CRYGate, CRZGate - - -def gradient_lookup(gate: Gate) -> list[tuple[complex, QuantumCircuit]]: - """Returns a circuit implementing the gradient of the input gate. - - Args: - gate: The gate whose derivative is returned. - - Returns: - The derivative of the input gate as list of ``(coeff, circuit)`` pairs, - where the sum of all ``coeff * circuit`` elements describes the full derivative. - The circuit is the unitary part of the derivative with a potential separate ``coeff``. - The output is a list as derivatives of e.g. controlled gates can only be described - as a sum of ``coeff * circuit`` pairs. - - Raises: - NotImplementedError: If the derivative of ``gate`` is not implemented. - """ - - param = gate.params[0] - if isinstance(gate, RXGate): - derivative = QuantumCircuit(gate.num_qubits) - derivative.rx(param, 0) - derivative.x(0) - return [(-0.5j, derivative)] - if isinstance(gate, RYGate): - derivative = QuantumCircuit(gate.num_qubits) - derivative.ry(param, 0) - derivative.y(0) - return [(-0.5j, derivative)] - if isinstance(gate, RZGate): - derivative = QuantumCircuit(gate.num_qubits) - derivative.rz(param, 0) - derivative.z(0) - return [(-0.5j, derivative)] - if isinstance(gate, CRXGate): - proj1 = QuantumCircuit(gate.num_qubits) - proj1.rx(param, 1) - proj1.x(1) - - proj2 = QuantumCircuit(gate.num_qubits) - proj2.z(0) - proj2.rx(param, 1) - proj2.x(1) - - return [(-0.25j, proj1), (0.25j, proj2)] - if isinstance(gate, CRYGate): - proj1 = QuantumCircuit(gate.num_qubits) - proj1.ry(param, 1) - proj1.y(1) - - proj2 = QuantumCircuit(gate.num_qubits) - proj2.z(0) - proj2.ry(param, 1) - proj2.y(1) - - return [(-0.25j, proj1), (0.25j, proj2)] - if isinstance(gate, CRZGate): - proj1 = QuantumCircuit(gate.num_qubits) - proj1.rz(param, 1) - proj1.z(1) - - proj2 = QuantumCircuit(gate.num_qubits) - proj2.z(0) - proj2.rz(param, 1) - proj2.z(1) - - return [(-0.25j, proj1), (0.25j, proj2)] - raise NotImplementedError("Cannot implement gradient for", gate) - - -def derive_circuit( - circuit: QuantumCircuit, parameter: Parameter -) -> Sequence[tuple[complex, QuantumCircuit]]: - """Return the analytic gradient expression of the input circuit wrt. a single parameter. - - Returns a list of ``(coeff, gradient_circuit)`` tuples, where the derivative of the circuit is - given by the sum of the gradient circuits multiplied by their coefficient. - - For example, the circuit:: - - ┌───┐┌───────┐┌─────┐ - q: ┤ H ├┤ Rx(x) ├┤ Sdg ├ - └───┘└───────┘└─────┘ - - returns the coefficient `-0.5j` and the circuit equivalent to:: - - ┌───┐┌───────┐┌───┐┌─────┐ - q: ┤ H ├┤ Rx(x) ├┤ X ├┤ Sdg ├ - └───┘└───────┘└───┘└─────┘ - - as the derivative of `Rx(x)` is `-0.5j Rx(x) X`. - - Args: - circuit: The quantum circuit to derive. - parameter: The parameter with respect to which we derive. - - Returns: - A list of ``(coeff, gradient_circuit)`` tuples. - - Raises: - ValueError: If ``parameter`` is of the wrong type. - ValueError: If ``parameter`` is not in this circuit. - NotImplementedError: If a non-unique parameter is added, as the product rule is not yet - supported in this function. - """ - # this is added as useful user-warning, since sometimes ``ParameterExpression``s are - # passed around instead of ``Parameter``s - if not isinstance(parameter, Parameter): - raise ValueError(f"parameter must be of type Parameter, not {type(parameter)}.") - - if parameter not in circuit.parameters: - raise ValueError(f"The parameter {parameter} is not in this circuit.") - - if len(circuit._parameter_table[parameter]) > 1: - raise NotImplementedError("No product rule support yet, circuit parameters must be unique.") - - summands, op_context = [], [] - for i, op in enumerate(circuit.data): - gate = op.operation - op_context.append((op.qubits, op.clbits)) - if parameter in gate.params: - coeffs_and_grads = gradient_lookup(gate) - summands += [coeffs_and_grads] - else: - summands += [[(1, gate)]] - - gradient = [] - for product_rule_term in itertools.product(*summands): - summand_circuit = QuantumCircuit(*circuit.qregs) - c = 1 - for i, term in enumerate(product_rule_term): - c *= term[0] - summand_circuit.data.append([term[1], *op_context[i]]) - gradient += [(c, summand_circuit.copy())] - - return gradient diff --git a/qiskit/algorithms/gradients/reverse/reverse_gradient.py b/qiskit/algorithms/gradients/reverse/reverse_gradient.py deleted file mode 100644 index c3bf8005d377..000000000000 --- a/qiskit/algorithms/gradients/reverse/reverse_gradient.py +++ /dev/null @@ -1,200 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Estimator gradients with the classically efficient reverse mode.""" - -from __future__ import annotations -from collections.abc import Sequence -import logging - -import numpy as np - -from qiskit.circuit import QuantumCircuit, Parameter -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.quantum_info import Statevector -from qiskit.opflow import PauliSumOp -from qiskit.primitives import Estimator - -from .bind import bind -from .derive_circuit import derive_circuit -from .split_circuits import split - -from ..base.base_estimator_gradient import BaseEstimatorGradient -from ..base.estimator_gradient_result import EstimatorGradientResult -from ..utils import DerivativeType - -logger = logging.getLogger(__name__) - - -class ReverseEstimatorGradient(BaseEstimatorGradient): - """Estimator gradients with the classically efficient reverse mode. - - .. note:: - - This gradient implementation is based on statevector manipulations and scales - exponentially with the number of qubits. However, for small system sizes it can be very fast - compared to circuit-based gradients. - - This class implements the calculation of the expectation gradient as described in - [1]. By keeping track of two statevectors and iteratively sweeping through each parameterized - gate, this method scales only linearly with the number of parameters. - - **References:** - - [1]: Jones, T. and Gacon, J. "Efficient calculation of gradients in classical simulations - of variational quantum algorithms" (2020). - `arXiv:2009.02823 `_. - - """ - - SUPPORTED_GATES = ["rx", "ry", "rz", "cp", "crx", "cry", "crz"] - - def __init__(self, derivative_type: DerivativeType = DerivativeType.REAL): - """ - Args: - derivative_type: Defines whether the real, imaginary or real plus imaginary part - of the gradient is returned. - """ - dummy_estimator = Estimator() # this is required by the base class, but not used - super().__init__(dummy_estimator, derivative_type=derivative_type) - - @BaseEstimatorGradient.derivative_type.setter - def derivative_type(self, derivative_type: DerivativeType) -> None: - """Set the derivative type.""" - self._derivative_type = derivative_type - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the gradients of the expectation values by the parameter shift rule.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique( - g_circuits, observables, g_parameter_values, g_parameters, **options - ) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, # pylint: disable=unused-argument - ) -> EstimatorGradientResult: - num_gradients = len(circuits) - gradients = [] - metadata = [] - - for i in range(num_gradients): - # temporary variables for easier access - circuit = circuits[i] - parameters_ = parameters[i] - observable = observables[i] - values = parameter_values[i] - - # the metadata only contains the parameters as there are no run configs here - metadata.append( - { - "parameters": parameters_, - "derivative_type": self.derivative_type, - } - ) - - # keep track of the parameter order of the circuit, as the circuit splitting might - # produce a list of unitaries in a different order - # original_parameter_order = [p for p in circuit.parameters if p in parameters_] - - # split the circuit and generate lists of unitaries [U_1, U_2, ...] and - # parameters [p_1, p_2, ...] in these unitaries - unitaries, paramlist = split(circuit, parameters=parameters_) - - parameter_binds = dict(zip(circuit.parameters, values)) - bound_circuit = bind(circuit, parameter_binds) - - # initialize state variables -- we use the same naming as in the paper - phi = Statevector(bound_circuit) - lam = _evolve_by_operator(observable, phi) - - # store gradients in a dictionary to return them in the correct order - grads = {param: 0j for param in parameters_} - - num_parameters = len(unitaries) - for j in reversed(range(num_parameters)): - unitary_j = unitaries[j] - - # We currently only support gates with a single parameter -- which is reflected - # in self.SUPPORTED_GATES -- but generally we could also support gates with multiple - # parameters per gate - parameter_j = paramlist[j][0] - - # get the analytic gradient d U_j / d p_j and bind the gate - deriv = derive_circuit(unitary_j, parameter_j) - for _, gate in deriv: - bind(gate, parameter_binds, inplace=True) - - # iterate the state variable - unitary_j_dagger = bind(unitary_j, parameter_binds).inverse() - phi = phi.evolve(unitary_j_dagger) - - # compute current gradient - grad = sum( - coeff * lam.conjugate().data.dot(phi.evolve(gate).data) for coeff, gate in deriv - ) - - # Compute the full gradient (real and complex parts) as all information is available. - # Later, based on the derivative type, cast to real/imag/complex. - grads[parameter_j] += grad - - if j > 0: - lam = lam.evolve(unitary_j_dagger) - - gradient = np.array(list(grads.values())) - gradients.append(self._to_derivtype(gradient)) - - result = EstimatorGradientResult(gradients, metadata=metadata, options={}) - return result - - def _to_derivtype(self, gradient): - # this disable is needed as Pylint does not understand derivative_type is a property if - # it is only defined in the base class and the getter is in the child - # pylint: disable=comparison-with-callable - if self.derivative_type == DerivativeType.REAL: - return 2 * np.real(gradient) - if self.derivative_type == DerivativeType.IMAG: - return 2 * np.imag(gradient) - - return 2 * gradient - - -def _evolve_by_operator(operator, state): - """Evolve the Statevector state by operator.""" - - # try casting to sparse matrix and use sparse matrix-vector multiplication, which is - # a lot faster than using Statevector.evolve - if isinstance(operator, PauliSumOp): - operator = operator.primitive * operator.coeff - - try: - spmatrix = operator.to_matrix(sparse=True) - evolved = spmatrix @ state.data - return Statevector(evolved) - except (TypeError, AttributeError): - logger.info("Operator is not castable to a sparse matrix, using Statevector.evolve.") - - return state.evolve(operator) diff --git a/qiskit/algorithms/gradients/reverse/reverse_qgt.py b/qiskit/algorithms/gradients/reverse/reverse_qgt.py deleted file mode 100644 index 0b845ee96961..000000000000 --- a/qiskit/algorithms/gradients/reverse/reverse_qgt.py +++ /dev/null @@ -1,252 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""QGT with the classically efficient reverse mode.""" - -from __future__ import annotations -from collections.abc import Sequence -import logging - -import numpy as np - -from qiskit.circuit import QuantumCircuit, Parameter -from qiskit.quantum_info import Statevector -from qiskit.providers import Options -from qiskit.primitives import Estimator - -from ..base.base_qgt import BaseQGT -from ..base.qgt_result import QGTResult -from ..utils import DerivativeType - -from .split_circuits import split -from .bind import bind -from .derive_circuit import derive_circuit - -logger = logging.getLogger(__name__) - - -class ReverseQGT(BaseQGT): - """QGT calculation with the classically efficient reverse mode. - - .. note:: - - This QGT implementation is based on statevector manipulations and scales exponentially - with the number of qubits. However, for small system sizes it can be very fast - compared to circuit-based gradients. - - This class implements the calculation of the QGT as described in [1]. - By keeping track of three statevectors and iteratively sweeping through each parameterized - gate, this method scales only quadratically with the number of parameters. - - **References:** - - [1]: Jones, T. "Efficient classical calculation of the Quantum Natural Gradient" (2020). - `arXiv:2011.02991 `_. - - """ - - SUPPORTED_GATES = ["rx", "ry", "rz", "cp", "crx", "cry", "crz"] - - def __init__( - self, phase_fix: bool = True, derivative_type: DerivativeType = DerivativeType.COMPLEX - ): - """ - Args: - phase_fix: Whether or not to include the phase fix. - derivative_type: Determines whether the complex QGT or only the real or imaginary - parts are calculated. - """ - dummy_estimator = Estimator() # this method does not need an estimator - super().__init__(dummy_estimator, phase_fix, derivative_type) - - @property - def options(self) -> Options: - """There are no options for the reverse QGT, returns an empty options dict. - - Returns: - Empty options. - """ - return Options() - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameter_sets: Sequence[set[Parameter]], - **options, - ) -> QGTResult: - """Compute the QGT on the given circuits.""" - g_circuits, g_parameter_values, g_parameter_sets = self._preprocess( - circuits, parameter_values, parameter_sets, self.SUPPORTED_GATES - ) - results = self._run_unique(g_circuits, g_parameter_values, g_parameter_sets, **options) - return self._postprocess(results, circuits, parameter_values, parameter_sets) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameter_sets: Sequence[set[Parameter]], - **options, # pylint: disable=unused-argument - ) -> QGTResult: - num_qgts = len(circuits) - qgts = [] - metadata = [] - - for k in range(num_qgts): - values = np.asarray(parameter_values[k]) - circuit = circuits[k] - parameters = list(parameter_sets[k]) - - num_parameters = len(parameters) - original_parameter_order = [p for p in circuit.parameters if p in parameters] - metadata.append({"parameters": original_parameter_order}) - - unitaries, paramlist = split(circuit, parameters=parameters) - - # initialize the phase fix vector and the hessian part ``metric`` - num_parameters = len(unitaries) - phase_fixes = np.zeros(num_parameters, dtype=complex) - metric = np.zeros((num_parameters, num_parameters), dtype=complex) - - # initialize the state variables -- naming convention is the same as the paper - parameter_binds = dict(zip(circuit.parameters, values)) - bound_unitaries = bind(unitaries, parameter_binds) - - chi = Statevector(bound_unitaries[0]) - psi = chi.copy() - phi = Statevector.from_int(0, (2,) * circuit.num_qubits) - - # Get the analytic gradient of the first unitary - # Note: We currently only support gates with a single parameter -- which is reflected - # in self.SUPPORTED_GATES -- but generally we could also support gates with multiple - # parameters per gate. This is the reason for the second 0-index. - deriv = derive_circuit(unitaries[0], paramlist[0][0]) - for _, gate in deriv: - bind(gate, parameter_binds, inplace=True) - - grad_coeffs = [coeff for coeff, _ in deriv] - grad_states = [phi.evolve(gate) for _, gate in deriv] - - # compute phase fix (optional) and the hessian part - if self._phase_fix: - phase_fixes[0] = _phasefix_term(chi, grad_coeffs, grad_states) - - metric[0, 0] = _l_term(grad_coeffs, grad_states, grad_coeffs, grad_states) - - for j in range(1, num_parameters): - lam = psi.copy() - phi = psi.copy() - - # get the analytic gradient d U_j / d p_j and apply it - deriv = derive_circuit(unitaries[j], paramlist[j][0]) - - for _, gate in deriv: - bind(gate, parameter_binds, inplace=True) - - # compute |phi> (in general it's a sum of states and coeffs) - grad_coeffs = [coeff for coeff, _ in deriv] - grad_states = [phi.evolve(gate) for _, gate in deriv] - - # compute the digaonal element L_{j, j} - metric[j, j] += _l_term(grad_coeffs, grad_states, grad_coeffs, grad_states) - - # compute the off diagonal elements L_{i, j} - for i in reversed(range(j)): - # apply U_{i + 1}_dg - unitary_ip_inv = bound_unitaries[i + 1].inverse() - grad_states = [state.evolve(unitary_ip_inv) for state in grad_states] - - lam = lam.evolve(bound_unitaries[i].inverse()) - - # get the gradient d U_i / d p_i and apply it - deriv = derive_circuit(unitaries[i], paramlist[i][0]) - for _, gate in deriv: - bind(gate, parameter_binds, inplace=True) - - grad_coeffs_mu = [coeff for coeff, _ in deriv] - grad_states_mu = [lam.evolve(gate) for _, gate in deriv] - - metric[i, j] += _l_term( - grad_coeffs_mu, grad_states_mu, grad_coeffs, grad_states - ) - - if self._phase_fix: - phase_fixes[j] += _phasefix_term(chi, grad_coeffs, grad_states) - - psi = psi.evolve(bound_unitaries[j]) - - # The following code stacks the QGT together and maps the values into the - # correct original order of parameters - - # map circuit parameter to global index in the circuit - param_to_circuit = { - param: index for index, param in enumerate(original_parameter_order) - } - # map global index to the local index used in the calculation, the new index can - # now be accessed by remap[index] - remap = { - index: param_to_circuit[_extract_parameter(plist[0])] - for index, plist in enumerate(paramlist) - } - - qgt = np.zeros((num_parameters, num_parameters), dtype=complex) - for i in range(num_parameters): - iloc = remap[i] - for j in range(num_parameters): - jloc = remap[j] - if i <= j: - qgt[iloc, jloc] += metric[i, j] - else: - qgt[iloc, jloc] += np.conj(metric[j, i]) - - qgt[iloc, jloc] -= np.conj(phase_fixes[i]) * phase_fixes[j] - - # append and cast to real/imag if required - qgts.append(self._to_derivtype(qgt)) - - result = QGTResult(qgts, self.derivative_type, metadata, options=None) - return result - - def _to_derivtype(self, qgt): - if self.derivative_type == DerivativeType.REAL: - return np.real(qgt) - if self.derivative_type == DerivativeType.IMAG: - return np.imag(qgt) - - return qgt - - -def _l_term(coeffs_i, states_i, coeffs_j, states_j): - return sum( - sum( - np.conj(coeff_i) * coeff_j * np.conj(state_i.data).dot(state_j.data) - for coeff_i, state_i in zip(coeffs_i, states_i) - ) - for coeff_j, state_j in zip(coeffs_j, states_j) - ) - - -def _phasefix_term(chi, coeffs, states): - return sum( - coeff_i * np.conj(chi.data).dot(state_i.data) for coeff_i, state_i in zip(coeffs, states) - ) - - -def _extract_parameter(expression): - if isinstance(expression, Parameter): - return expression - - if len(expression.parameters) > 1: - raise ValueError("Expression has more than one parameter.") - - return list(expression.parameters)[0] diff --git a/qiskit/algorithms/gradients/reverse/split_circuits.py b/qiskit/algorithms/gradients/reverse/split_circuits.py deleted file mode 100644 index b2bdf2b5375b..000000000000 --- a/qiskit/algorithms/gradients/reverse/split_circuits.py +++ /dev/null @@ -1,68 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Split a circuit into subcircuits, each containing a single parameterized gate.""" - -from __future__ import annotations - -from collections.abc import Iterable -from qiskit.circuit import QuantumCircuit, ParameterExpression, Parameter - - -def split( - circuit: QuantumCircuit, - parameters: Iterable[Parameter] | None = None, -) -> tuple[list[QuantumCircuit], list[list[Parameter]]]: - """Split the circuit at ParameterExpressions. - - Args: - circuit: The circuit to split. - parameters: The parameters at which to split. If None, split at each parameter. - - Returns: - A list of the split circuits along with a list of which parameters are in the subcircuits. - """ - circuits = [] - corresponding_parameters = [] - - sub = QuantumCircuit(*circuit.qregs, *circuit.cregs) - for inst in circuit.data: - # check if new split must be created - if parameters is None: - params = [ - param - for param in inst.operation.params - if isinstance(param, ParameterExpression) and len(param.parameters) > 0 - ] - else: - if inst.operation.definition is not None: - free_inst_params = inst.operation.definition.parameters - else: - free_inst_params = {} - - params = [p for p in parameters if p in free_inst_params] - - new_split = bool(len(params) > 0) - - if new_split: - sub.append(inst) - circuits.append(sub) - corresponding_parameters.append(params) - sub = QuantumCircuit(*circuit.qregs, *circuit.cregs) - else: - sub.append(inst) - - # handle leftover gates - if len(sub.data) > 0: - circuits[-1].compose(sub, inplace=True) - - return circuits, corresponding_parameters diff --git a/qiskit/algorithms/gradients/spsa/__init__.py b/qiskit/algorithms/gradients/spsa/__init__.py deleted file mode 100644 index 8f5fd2a37f84..000000000000 --- a/qiskit/algorithms/gradients/spsa/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# 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. diff --git a/qiskit/algorithms/gradients/spsa/spsa_estimator_gradient.py b/qiskit/algorithms/gradients/spsa/spsa_estimator_gradient.py deleted file mode 100644 index 021bcb5803f0..000000000000 --- a/qiskit/algorithms/gradients/spsa/spsa_estimator_gradient.py +++ /dev/null @@ -1,135 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Gradient of Sampler with Finite difference method.""" - -from __future__ import annotations - -from collections.abc import Sequence - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.providers import Options -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..base.base_estimator_gradient import BaseEstimatorGradient -from ..base.estimator_gradient_result import EstimatorGradientResult - -from ...exceptions import AlgorithmError - - -class SPSAEstimatorGradient(BaseEstimatorGradient): - """ - Compute the gradients of the expectation value by the Simultaneous Perturbation Stochastic - Approximation (SPSA) [1]. - - **Reference:** - [1] J. C. Spall, Adaptive stochastic approximation by the simultaneous perturbation method in - IEEE Transactions on Automatic Control, vol. 45, no. 10, pp. 1839-1853, Oct 2020, - `doi: 10.1109/TAC.2000.880982 `_ - """ - - def __init__( - self, - estimator: BaseEstimator, - epsilon: float, - batch_size: int = 1, - seed: int | None = None, - options: Options | None = None, - ): - """ - Args: - estimator: The estimator used to compute the gradients. - epsilon: The offset size for the SPSA gradients. - batch_size: The number of gradients to average. - seed: The seed for a random perturbation vector. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - - Raises: - ValueError: If ``epsilon`` is not positive. - """ - if epsilon <= 0: - raise ValueError(f"epsilon ({epsilon}) should be positive.") - self._epsilon = epsilon - self._batch_size = batch_size - self._seed = np.random.default_rng(seed) - - super().__init__(estimator, options) - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - job_circuits, job_observables, job_param_values, metadata, offsets = [], [], [], [], [] - all_n = [] - for circuit, observable, parameter_values_, parameters_ in zip( - circuits, observables, parameter_values, parameters - ): - # Indices of parameters to be differentiated. - indices = [circuit.parameters.data.index(p) for p in parameters_] - metadata.append({"parameters": parameters_}) - # Make random perturbation vectors. - offset = [ - (-1) ** (self._seed.integers(0, 2, len(circuit.parameters))) - for _ in range(self._batch_size) - ] - plus = [parameter_values_ + self._epsilon * offset_ for offset_ in offset] - minus = [parameter_values_ - self._epsilon * offset_ for offset_ in offset] - offsets.append(offset) - - # Combine inputs into a single job to reduce overhead. - job_circuits.extend([circuit] * 2 * self._batch_size) - job_observables.extend([observable] * 2 * self._batch_size) - job_param_values.extend(plus + minus) - all_n.append(2 * self._batch_size) - - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for i, n in enumerate(all_n): - result = results.values[partial_sum_n : partial_sum_n + n] - partial_sum_n += n - n = len(result) // 2 - diffs = (result[:n] - result[n:]) / (2 * self._epsilon) - # Calculate the gradient for each batch. Note that (``diff`` / ``offset``) is the gradient - # since ``offset`` is a perturbation vector of 1s and -1s. - batch_gradients = np.array([diff / offset for diff, offset in zip(diffs, offsets[i])]) - # Take the average of the batch gradients. - gradient = np.mean(batch_gradients, axis=0) - indices = [circuits[i].parameters.data.index(p) for p in metadata[i]["parameters"]] - gradients.append(gradient[indices]) - - opt = self._get_local_options(options) - return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/spsa/spsa_sampler_gradient.py b/qiskit/algorithms/gradients/spsa/spsa_sampler_gradient.py deleted file mode 100644 index be23274d7ebc..000000000000 --- a/qiskit/algorithms/gradients/spsa/spsa_sampler_gradient.py +++ /dev/null @@ -1,136 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Gradient of Sampler with Finite difference method.""" - -from __future__ import annotations - -from collections import defaultdict -from collections.abc import Sequence - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives import BaseSampler -from qiskit.providers import Options - -from ..base.base_sampler_gradient import BaseSamplerGradient -from ..base.sampler_gradient_result import SamplerGradientResult - -from ...exceptions import AlgorithmError - - -class SPSASamplerGradient(BaseSamplerGradient): - """ - Compute the gradients of the sampling probability by the Simultaneous Perturbation Stochastic - Approximation (SPSA) [1]. - - **Reference:** - [1] J. C. Spall, Adaptive stochastic approximation by the simultaneous perturbation method in - IEEE Transactions on Automatic Control, vol. 45, no. 10, pp. 1839-1853, Oct 2020, - `doi: 10.1109/TAC.2000.880982 `_. - """ - - def __init__( - self, - sampler: BaseSampler, - epsilon: float, - batch_size: int = 1, - seed: int | None = None, - options: Options | None = None, - ): - """ - Args: - sampler: The sampler used to compute the gradients. - epsilon: The offset size for the SPSA gradients. - batch_size: number of gradients to average. - seed: The seed for a random perturbation vector. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - - Raises: - ValueError: If ``epsilon`` is not positive. - """ - if epsilon <= 0: - raise ValueError(f"epsilon ({epsilon}) should be positive.") - self._batch_size = batch_size - self._epsilon = epsilon - self._seed = np.random.default_rng(seed) - - super().__init__(sampler, options) - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the sampler gradients on the given circuits.""" - job_circuits, job_param_values, metadata, offsets = [], [], [], [] - all_n = [] - for circuit, parameter_values_, parameters_ in zip(circuits, parameter_values, parameters): - # Indices of parameters to be differentiated. - indices = [circuit.parameters.data.index(p) for p in parameters_] - metadata.append({"parameters": parameters_}) - offset = np.array( - [ - (-1) ** (self._seed.integers(0, 2, len(circuit.parameters))) - for _ in range(self._batch_size) - ] - ) - plus = [parameter_values_ + self._epsilon * offset_ for offset_ in offset] - minus = [parameter_values_ - self._epsilon * offset_ for offset_ in offset] - offsets.append(offset) - - # Combine inputs into a single job to reduce overhead. - n = 2 * self._batch_size - job_circuits.extend([circuit] * n) - job_param_values.extend(plus + minus) - all_n.append(n) - - # Run the single job with all circuits. - job = self._sampler.run(job_circuits, job_param_values, **options) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Sampler job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for i, n in enumerate(all_n): - dist_diffs = {} - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - for j, (dist_plus, dist_minus) in enumerate(zip(result[: n // 2], result[n // 2 :])): - dist_diff: dict[int, float] = defaultdict(float) - for key, value in dist_plus.items(): - dist_diff[key] += value / (2 * self._epsilon) - for key, value in dist_minus.items(): - dist_diff[key] -= value / (2 * self._epsilon) - dist_diffs[j] = dist_diff - gradient = [] - indices = [circuits[i].parameters.data.index(p) for p in metadata[i]["parameters"]] - for j in indices: - gradient_j: dict[int, float] = defaultdict(float) - for k in range(self._batch_size): - for key, value in dist_diffs[k].items(): - gradient_j[key] += value * offsets[i][k][j] - gradient_j = {key: value / self._batch_size for key, value in gradient_j.items()} - gradient.append(gradient_j) - gradients.append(gradient) - partial_sum_n += n - - opt = self._get_local_options(options) - return SamplerGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/utils.py b/qiskit/algorithms/gradients/utils.py deleted file mode 100644 index f46911ee26ff..000000000000 --- a/qiskit/algorithms/gradients/utils.py +++ /dev/null @@ -1,375 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -""" -Utility functions for gradients -""" - -from __future__ import annotations - -from collections import defaultdict -from collections.abc import Sequence -from dataclasses import dataclass -from enum import Enum - -import numpy as np - -from qiskit.circuit import ( - ClassicalRegister, - Gate, - Instruction, - Parameter, - ParameterExpression, - QuantumCircuit, - QuantumRegister, -) -from qiskit.circuit.library.standard_gates import ( - CXGate, - CYGate, - CZGate, - RXGate, - RXXGate, - RYGate, - RYYGate, - RZGate, - RZXGate, - RZZGate, - XGate, -) -from qiskit.quantum_info import SparsePauliOp - - -################################################################################ -## Gradient circuits and Enum -################################################################################ -class DerivativeType(Enum): - """Types of derivative.""" - - REAL = "real" - IMAG = "imag" - COMPLEX = "complex" - - -@dataclass -class GradientCircuit: - """Gradient circuit with unique parameters and mapping information.""" - - gradient_circuit: QuantumCircuit - """An internal quantum circuit with unique parameters used to calculate the gradient""" - parameter_map: dict[Parameter, list[tuple[Parameter, float | ParameterExpression]]] - """A dictionary maps the parameters of ``circuit`` to the parameters of ``gradient_circuit`` with - coefficients""" - gradient_parameter_map: dict[Parameter, ParameterExpression] - """A dictionary maps the parameters of ``gradient_circuit`` to the parameter expressions of - ``circuit``""" - - -@dataclass -class LinearCombGradientCircuit: - """Gradient circuit for the linear combination of unitaries method.""" - - gradient_circuit: QuantumCircuit - """A gradient circuit for the linear combination of unitaries method.""" - coeff: float | ParameterExpression - """A coefficient corresponds to the gradient circuit.""" - - -################################################################################ -## Parameter shift gradient -################################################################################ -def _make_param_shift_parameter_values( - circuit: QuantumCircuit, - parameter_values: np.ndarray | list[float], - parameters: Sequence[Parameter], -) -> list[np.ndarray]: - """Returns a list of parameter values with offsets for parameter shift rule. - - Args: - circuit: The original quantum circuit - parameter_values: parameter values to be added to the base parameter values. - parameters: The parameters to be shifted. - - Returns: - A list of parameter values with offsets for parameter shift rule. - """ - indices = [circuit.parameters.data.index(p) for p in parameters] - offset = np.identity(circuit.num_parameters)[indices, :] - plus_offsets = parameter_values + offset * np.pi / 2 - minus_offsets = parameter_values - offset * np.pi / 2 - return plus_offsets.tolist() + minus_offsets.tolist() - - -################################################################################ -## Linear combination gradient and Linear combination QGT -################################################################################ -def _make_lin_comb_gradient_circuit( - circuit: QuantumCircuit, add_measurement: bool = False -) -> dict[Parameter, QuantumCircuit]: - """Makes a circuit that computes the linear combination of the gradient circuits.""" - circuit_temp = circuit.copy() - qr_aux = QuantumRegister(1, "qr_aux") - cr_aux = ClassicalRegister(1, "cr_aux") - circuit_temp.add_register(qr_aux) - circuit_temp.add_register(cr_aux) - circuit_temp.h(qr_aux) - circuit_temp.data.insert(0, circuit_temp.data.pop()) - circuit_temp.sdg(qr_aux) - circuit_temp.data.insert(1, circuit_temp.data.pop()) - - lin_comb_circuits = {} - for i, instruction in enumerate(circuit_temp.data): - if instruction.operation.is_parameterized(): - for p in instruction.operation.params[0].parameters: - gate = _gate_gradient(instruction.operation) - lin_comb_circuit = circuit_temp.copy() - # insert `gate` to i-th position - lin_comb_circuit.append(gate, [qr_aux[0]] + list(instruction.qubits), []) - lin_comb_circuit.data.insert(i, lin_comb_circuit.data.pop()) - lin_comb_circuit.h(qr_aux) - if add_measurement: - lin_comb_circuit.measure(qr_aux, cr_aux) - lin_comb_circuits[p] = lin_comb_circuit - - return lin_comb_circuits - - -def _gate_gradient(gate: Gate) -> Instruction: - """Returns the derivative of the gate""" - # pylint: disable=too-many-return-statements - if isinstance(gate, RXGate): - return CXGate() - if isinstance(gate, RYGate): - return CYGate() - if isinstance(gate, RZGate): - return CZGate() - if isinstance(gate, RXXGate): - cxx_circ = QuantumCircuit(3) - cxx_circ.cx(0, 1) - cxx_circ.cx(0, 2) - cxx = cxx_circ.to_instruction() - return cxx - if isinstance(gate, RYYGate): - cyy_circ = QuantumCircuit(3) - cyy_circ.cy(0, 1) - cyy_circ.cy(0, 2) - cyy = cyy_circ.to_instruction() - return cyy - if isinstance(gate, RZZGate): - czz_circ = QuantumCircuit(3) - czz_circ.cz(0, 1) - czz_circ.cz(0, 2) - czz = czz_circ.to_instruction() - return czz - if isinstance(gate, RZXGate): - czx_circ = QuantumCircuit(3) - czx_circ.cx(0, 2) - czx_circ.cz(0, 1) - czx = czx_circ.to_instruction() - return czx - raise TypeError(f"Unrecognized parameterized gate, {gate}") - - -def _make_lin_comb_qgt_circuit( - circuit: QuantumCircuit, add_measurement: bool = False -) -> dict[tuple[Parameter, Parameter], QuantumCircuit]: - """Makes a circuit that computes the linear combination of the QGT circuits.""" - circuit_temp = circuit.copy() - qr_aux = QuantumRegister(1, "aux") - circuit_temp.add_register(qr_aux) - if add_measurement: - cr_aux = ClassicalRegister(1, "aux") - circuit_temp.add_bits(cr_aux) - circuit_temp.h(qr_aux) - circuit_temp.data.insert(0, circuit_temp.data.pop()) - - lin_comb_qgt_circuits = {} - for i, instruction_i in enumerate(circuit_temp.data): - if not instruction_i.operation.is_parameterized(): - continue - for j, instruction_j in enumerate(circuit_temp.data): - if not instruction_j.operation.is_parameterized(): - continue - # Calculate the QGT of the i-th gate with respect to the j-th gate. - param_i = instruction_i.operation.params[0] - param_j = instruction_j.operation.params[0] - - for p_i in param_i.parameters: - for p_j in param_j.parameters: - if circuit_temp.parameters.data.index(p_i) > circuit_temp.parameters.data.index( - p_j - ): - continue - gate_i = _gate_gradient(instruction_i.operation) - gate_j = _gate_gradient(instruction_j.operation) - lin_comb_qgt_circuit = circuit_temp.copy() - if i < j: - # insert gate_j to j-th position - lin_comb_qgt_circuit.append( - gate_j, [qr_aux[0]] + list(instruction_j.qubits), [] - ) - lin_comb_qgt_circuit.data.insert(j, lin_comb_qgt_circuit.data.pop()) - # insert gate_i to i-th position with two X gates at its sides - lin_comb_qgt_circuit.append(XGate(), [qr_aux[0]], []) - lin_comb_qgt_circuit.data.insert(i, lin_comb_qgt_circuit.data.pop()) - lin_comb_qgt_circuit.append( - gate_i, [qr_aux[0]] + list(instruction_i.qubits), [] - ) - lin_comb_qgt_circuit.data.insert(i, lin_comb_qgt_circuit.data.pop()) - lin_comb_qgt_circuit.append(XGate(), [qr_aux[0]], []) - lin_comb_qgt_circuit.data.insert(i, lin_comb_qgt_circuit.data.pop()) - else: - # insert gate_i to i-th position - lin_comb_qgt_circuit.append( - gate_i, [qr_aux[0]] + list(instruction_i.qubits), [] - ) - lin_comb_qgt_circuit.data.insert(i, lin_comb_qgt_circuit.data.pop()) - # insert gate_j to j-th position with two X gates at its sides - lin_comb_qgt_circuit.append(XGate(), [qr_aux[0]], []) - lin_comb_qgt_circuit.data.insert(j, lin_comb_qgt_circuit.data.pop()) - lin_comb_qgt_circuit.append( - gate_j, [qr_aux[0]] + list(instruction_j.qubits), [] - ) - lin_comb_qgt_circuit.data.insert(j, lin_comb_qgt_circuit.data.pop()) - lin_comb_qgt_circuit.append(XGate(), [qr_aux[0]], []) - lin_comb_qgt_circuit.data.insert(j, lin_comb_qgt_circuit.data.pop()) - - lin_comb_qgt_circuit.h(qr_aux) - if add_measurement: - lin_comb_qgt_circuit.measure(qr_aux, cr_aux) - lin_comb_qgt_circuits[(p_i, p_j)] = lin_comb_qgt_circuit - - return lin_comb_qgt_circuits - - -def _make_lin_comb_observables( - observable: SparsePauliOp, - derivative_type: DerivativeType, -) -> tuple[SparsePauliOp, SparsePauliOp | None]: - """Make the observable with an ancillary operator for the linear combination gradient. - - Args: - observable: The observable. - derivative_type: The type of derivative. Can be either ``DerivativeType.REAL`` - ``DerivativeType.IMAG``, or ``DerivativeType.COMPLEX``. - - Returns: - The observable with an ancillary operator for the linear combination gradient. - - Raises: - ValueError: If the derivative type is not supported. - """ - if derivative_type == DerivativeType.REAL: - return observable.expand(SparsePauliOp.from_list([("Z", 1)])), None - elif derivative_type == DerivativeType.IMAG: - return observable.expand(SparsePauliOp.from_list([("Y", -1)])), None - elif derivative_type == DerivativeType.COMPLEX: - return observable.expand(SparsePauliOp.from_list([("Z", 1)])), observable.expand( - SparsePauliOp.from_list([("Y", -1)]) - ) - else: - raise ValueError(f"Derivative type {derivative_type} is not supported.") - - -################################################################################ -## Preprocess -################################################################################ -def _assign_unique_parameters( - circuit: QuantumCircuit, -) -> GradientCircuit: - """Assign unique parameters to the circuit. - - Args: - circuit: The circuit to assign unique parameters. - - Returns: - The circuit with unique parameters and the mapping from the original parameters to the - unique parameters. - """ - gradient_circuit = circuit.copy_empty_like(f"{circuit.name}_gradient") - parameter_map = defaultdict(list) - gradient_parameter_map = {} - num_gradient_parameters = 0 - for instruction in circuit.data: - if instruction.operation.is_parameterized(): - new_op_params = [] - for angle in instruction.operation.params: - new_parameter = Parameter(f"__gθ{num_gradient_parameters}") - new_op_params.append(new_parameter) - num_gradient_parameters += 1 - for parameter in angle.parameters: - parameter_map[parameter].append((new_parameter, angle.gradient(parameter))) - gradient_parameter_map[new_parameter] = angle - instruction.operation.params = new_op_params - gradient_circuit.append(instruction.operation, instruction.qubits, instruction.clbits) - # For the global phase - gradient_circuit.global_phase = circuit.global_phase - if isinstance(gradient_circuit.global_phase, ParameterExpression): - substitution_map = {} - for parameter in gradient_circuit.global_phase.parameters: - if parameter in parameter_map: - substitution_map[parameter] = parameter_map[parameter][0][0] - else: - new_parameter = Parameter(f"__gθ{num_gradient_parameters}") - substitution_map[parameter] = new_parameter - parameter_map[parameter].append((new_parameter, 1)) - num_gradient_parameters += 1 - gradient_circuit.global_phase = gradient_circuit.global_phase.subs(substitution_map) - return GradientCircuit(gradient_circuit, parameter_map, gradient_parameter_map) - - -def _make_gradient_parameter_values( - circuit: QuantumCircuit, - gradient_circuit: GradientCircuit, - parameter_values: np.ndarray, -) -> np.ndarray: - """Makes parameter values for the gradient circuit. - - Args: - circuit: The original quantum circuit - gradient_circuit: The gradient circuit - parameter_values: The parameter values for the original circuit - parameter_set: The parameter set to calculate gradients - - Returns: - The parameter values for the gradient circuit. - """ - g_circuit = gradient_circuit.gradient_circuit - g_parameter_values = np.empty(len(g_circuit.parameters)) - for i, g_parameter in enumerate(g_circuit.parameters): - expr = gradient_circuit.gradient_parameter_map[g_parameter] - bound_expr = expr.bind( - {p: parameter_values[circuit.parameters.data.index(p)] for p in expr.parameters} - ) - g_parameter_values[i] = float(bound_expr) - return g_parameter_values - - -def _make_gradient_parameters( - gradient_circuit: GradientCircuit, - parameters: Sequence[Parameter], -) -> Sequence[Parameter]: - """Makes parameter set for the gradient circuit. - - Args: - gradient_circuit: The gradient circuit - parameters: The parameters in the original circuit to calculate gradients - - Returns: - The parameters in the gradient circuit to calculate gradients. - """ - g_parameters = [ - g_parameter - for parameter in parameters - for g_parameter, _ in gradient_circuit.parameter_map[parameter] - ] - # make g_parameters unique and return it. - return list(dict.fromkeys(g_parameters)) diff --git a/qiskit/algorithms/list_or_dict.py b/qiskit/algorithms/list_or_dict.py deleted file mode 100644 index 95314dd79a3b..000000000000 --- a/qiskit/algorithms/list_or_dict.py +++ /dev/null @@ -1,18 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Introduced new type to maintain readability.""" - -from typing import TypeVar, List, Union, Optional, Dict - -_T = TypeVar("_T") # Pylint does not allow single character class names. -ListOrDict = Union[List[Optional[_T]], Dict[str, _T]] diff --git a/qiskit/algorithms/minimum_eigen_solvers/__init__.py b/qiskit/algorithms/minimum_eigen_solvers/__init__.py deleted file mode 100644 index 3d7d18023b75..000000000000 --- a/qiskit/algorithms/minimum_eigen_solvers/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Minimum Eigen Solvers Package""" - -from .vqe import VQE, VQEResult -from .qaoa import QAOA -from .numpy_minimum_eigen_solver import NumPyMinimumEigensolver -from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult - -__all__ = [ - "VQE", - "VQEResult", - "QAOA", - "NumPyMinimumEigensolver", - "MinimumEigensolver", - "MinimumEigensolverResult", -] diff --git a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py deleted file mode 100644 index 6625cf30eaeb..000000000000 --- a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py +++ /dev/null @@ -1,141 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# 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. - -"""The Minimum Eigensolver interface""" -from __future__ import annotations - -from abc import ABC, abstractmethod - -import numpy as np - -from qiskit.opflow import OperatorBase -from qiskit.utils.deprecation import deprecate_func -from ..algorithm_result import AlgorithmResult -from ..list_or_dict import ListOrDict - - -class MinimumEigensolver(ABC): - """Deprecated: Minimum Eigensolver Interface. - - The Minimum Eigensolver interface has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.MinimumEigensolver` interface. - This interface will be deprecated in a future release and subsequently - removed after that. - - Algorithms that can compute a minimum eigenvalue for an operator - may implement this interface to allow different algorithms to be - used interchangeably. - """ - - @deprecate_func( - additional_msg=( - "Instead, use the interface " - "``qiskit.algorithms.minimum_eigensolvers.MinimumEigensolver``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - pass - - @abstractmethod - def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> "MinimumEigensolverResult": - """ - Computes minimum eigenvalue. Operator and aux_operators can be supplied here and - if not None will override any already set into algorithm so it can be reused with - different operators. While an operator is required by algorithms, aux_operators - are optional. To 'remove' a previous aux_operators array use an empty list here. - - Args: - operator: Qubit operator of the Observable - aux_operators: Optional list of auxiliary operators to be evaluated with the - eigenstate of the minimum eigenvalue main result and their expectation values - returned. For instance in chemistry these can be dipole operators, total particle - count operators so we can get values for these at the ground state. - - Returns: - MinimumEigensolverResult - """ - return MinimumEigensolverResult() - - @classmethod - def supports_aux_operators(cls) -> bool: - """Whether computing the expectation value of auxiliary operators is supported. - - If the minimum eigensolver computes an eigenstate of the main operator then it - can compute the expectation value of the aux_operators for that state. Otherwise - they will be ignored. - - Returns: - True if aux_operator expectations can be evaluated, False otherwise - """ - return False - - -class MinimumEigensolverResult(AlgorithmResult): - """Deprecated: Minimum Eigensolver Result. - - The MinimumEigensolverResult class has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.MinimumEigensolverResult` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class " - "``qiskit.algorithms.minimum_eigensolvers.MinimumEigensolverResult``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - super().__init__() - self._eigenvalue: complex | None = None - self._eigenstate: np.ndarray | None = None - self._aux_operator_eigenvalues: ListOrDict[tuple[complex, complex]] | None = None - - @property - def eigenvalue(self) -> complex | None: - """returns eigen value""" - return self._eigenvalue - - @eigenvalue.setter - def eigenvalue(self, value: complex) -> None: - """set eigen value""" - self._eigenvalue = value - - @property - def eigenstate(self) -> np.ndarray | None: - """return eigen state""" - return self._eigenstate - - @eigenstate.setter - def eigenstate(self, value: np.ndarray) -> None: - """set eigen state""" - self._eigenstate = value - - @property - def aux_operator_eigenvalues(self) -> ListOrDict[tuple[complex, complex]] | None: - """Return aux operator expectation values. - - These values are in fact tuples formatted as (mean, standard deviation). - """ - return self._aux_operator_eigenvalues - - @aux_operator_eigenvalues.setter - def aux_operator_eigenvalues(self, value: ListOrDict[tuple[complex, complex]]) -> None: - """set aux operator eigen values""" - self._aux_operator_eigenvalues = value diff --git a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py deleted file mode 100644 index 83623666b715..000000000000 --- a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py +++ /dev/null @@ -1,105 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# 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. - -"""The Numpy Minimum Eigensolver algorithm.""" -from __future__ import annotations - -import logging -import warnings -from collections.abc import Callable - -import numpy as np - -from qiskit.opflow import OperatorBase -from qiskit.utils.deprecation import deprecate_func -from ..eigen_solvers.numpy_eigen_solver import NumPyEigensolver -from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult -from ..list_or_dict import ListOrDict - -logger = logging.getLogger(__name__) - - -class NumPyMinimumEigensolver(MinimumEigensolver): - """ - Deprecated: Numpy Minimum Eigensolver algorithm. - - The NumPyMinimumEigensolver class has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.NumPyMinimumEigensolver` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class " - "``qiskit.algorithms.minimum_eigensolvers.NumPyMinimumEigensolver``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - filter_criterion: Callable[ - [list | np.ndarray, float, ListOrDict[float] | None], bool - ] = None, - ) -> None: - """ - Args: - filter_criterion: callable that allows to filter eigenvalues/eigenstates. The minimum - eigensolver is only searching over feasible states and returns an eigenstate that - has the smallest eigenvalue among feasible states. The callable has the signature - `filter(eigenstate, eigenvalue, aux_values)` and must return a boolean to indicate - whether to consider this value or not. If there is no - feasible element, the result can even be empty. - """ - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - self._ces = NumPyEigensolver(filter_criterion=filter_criterion) - self._ret = MinimumEigensolverResult() - - @property - def filter_criterion( - self, - ) -> Callable[[list | np.ndarray, float, ListOrDict[float] | None], bool] | None: - """returns the filter criterion if set""" - return self._ces.filter_criterion - - @filter_criterion.setter - def filter_criterion( - self, - filter_criterion: Callable[[list | np.ndarray, float, ListOrDict[float] | None], bool] - | None, - ) -> None: - """set the filter criterion""" - self._ces.filter_criterion = filter_criterion - - @classmethod - def supports_aux_operators(cls) -> bool: - return NumPyEigensolver.supports_aux_operators() - - def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> MinimumEigensolverResult: - super().compute_minimum_eigenvalue(operator, aux_operators) - result_ces = self._ces.compute_eigenvalues(operator, aux_operators) - self._ret = MinimumEigensolverResult() - if result_ces.eigenvalues is not None and len(result_ces.eigenvalues) > 0: - self._ret.eigenvalue = result_ces.eigenvalues[0] - self._ret.eigenstate = result_ces.eigenstates[0] - if result_ces.aux_operator_eigenvalues: - self._ret.aux_operator_eigenvalues = result_ces.aux_operator_eigenvalues[0] - - logger.debug("MinimumEigensolver:\n%s", self._ret) - - return self._ret diff --git a/qiskit/algorithms/minimum_eigen_solvers/qaoa.py b/qiskit/algorithms/minimum_eigen_solvers/qaoa.py deleted file mode 100644 index fc18be860218..000000000000 --- a/qiskit/algorithms/minimum_eigen_solvers/qaoa.py +++ /dev/null @@ -1,185 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# 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. - -"""The Quantum Approximate Optimization Algorithm.""" -from __future__ import annotations - -import warnings -from collections.abc import Callable - -import numpy as np - -from qiskit.algorithms.optimizers import Minimizer, Optimizer -from qiskit.circuit import QuantumCircuit -from qiskit.opflow import OperatorBase, ExpectationBase -from qiskit.opflow.gradients import GradientBase -from qiskit.providers import Backend -from qiskit.utils.quantum_instance import QuantumInstance -from qiskit.utils.validation import validate_min -from qiskit.utils.deprecation import deprecate_func -from qiskit.circuit.library.n_local.qaoa_ansatz import QAOAAnsatz -from qiskit.algorithms.minimum_eigen_solvers.vqe import VQE - - -class QAOA(VQE): - """ - Deprecated: Quantum Approximate Optimization Algorithm. - - The QAOA class has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.QAOA` class. - This class will be deprecated in a future release and subsequently - removed after that. - - `QAOA `__ is a well-known algorithm for finding approximate - solutions to combinatorial-optimization problems. - - The QAOA implementation directly extends :class:`VQE` and inherits VQE's optimization structure. - However, unlike VQE, which can be configured with arbitrary ansatzes, - QAOA uses its own fine-tuned ansatz, which comprises :math:`p` parameterized global - :math:`x` rotations and :math:`p` different parameterizations of the problem hamiltonian. - QAOA is thus principally configured by the single integer parameter, *p*, - which dictates the depth of the ansatz, and thus affects the approximation quality. - - An optional array of :math:`2p` parameter values, as the *initial_point*, may be provided as the - starting **beta** and **gamma** parameters (as identically named in the - original `QAOA paper `__) for the QAOA ansatz. - - An operator or a parameterized quantum circuit may optionally also be provided as a custom - `mixer` Hamiltonian. This allows, as discussed in - `this paper `__ for quantum annealing, - and in `this paper `__ for QAOA, - to run constrained optimization problems where the mixer constrains - the evolution to a feasible subspace of the full Hilbert space. - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.minimum_eigensolvers.QAOA``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - optimizer: Optimizer | Minimizer | None = None, - reps: int = 1, - initial_state: QuantumCircuit | None = None, - mixer: QuantumCircuit | OperatorBase = None, - initial_point: np.ndarray | None = None, - gradient: GradientBase | Callable[[np.ndarray | list], list] | None = None, - expectation: ExpectationBase | None = None, - include_custom: bool = False, - max_evals_grouped: int = 1, - callback: Callable[[int, np.ndarray, float, float], None] | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - ) -> None: - """ - Args: - optimizer: A classical optimizer, see also :class:`~qiskit.algorithms.VQE` for - more details on the possible types. - reps: the integer parameter :math:`p` as specified in https://arxiv.org/abs/1411.4028, - Has a minimum valid value of 1. - initial_state: An optional initial state to prepend the QAOA circuit with - mixer: the mixer Hamiltonian to evolve with or a custom quantum circuit. Allows support - of optimizations in constrained subspaces as per https://arxiv.org/abs/1709.03489 - as well as warm-starting the optimization as introduced - in http://arxiv.org/abs/2009.10095. - initial_point: An optional initial point (i.e. initial parameter values) - for the optimizer. If ``None`` then it will simply compute a random one. - gradient: An optional gradient operator respectively a gradient function used for - optimization. - expectation: The Expectation converter for taking the average value of the - Observable over the ansatz state function. When None (the default) an - :class:`~qiskit.opflow.expectations.ExpectationFactory` is used to select - an appropriate expectation based on the operator and backend. When using Aer - qasm_simulator backend, with paulis, it is however much faster to leverage custom - Aer function for the computation but, although VQE performs much faster - with it, the outcome is ideal, with no shot noise, like using a state vector - simulator. If you are just looking for the quickest performance when choosing Aer - qasm_simulator and the lack of shot noise is not an issue then set `include_custom` - parameter here to True (defaults to False). - include_custom: When `expectation` parameter here is None setting this to True will - allow the factory to include the custom Aer pauli expectation. - max_evals_grouped: Max number of evaluations performed simultaneously. Signals the - given optimizer that more than one set of parameters can be supplied so that - potentially the expectation values can be computed in parallel. Typically this is - possible when a finite difference gradient is used by the optimizer such that - multiple points to compute the gradient can be passed and if computed in parallel - improve overall execution time. Ignored if a gradient operator or function is - given. - callback: a callback that can access the intermediate data during the optimization. - Four parameter values are passed to the callback as follows during each evaluation - by the optimizer for its current set of parameters as it works towards the minimum. - These are: the evaluation count, the optimizer parameters for the - ansatz, the evaluated mean and the evaluated standard deviation. - quantum_instance: Quantum Instance or Backend - """ - validate_min("reps", reps, 1) - - self._reps = reps - self._mixer = mixer - self._initial_state = initial_state - self._cost_operator: OperatorBase | None = None - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__( - ansatz=None, - optimizer=optimizer, - initial_point=initial_point, - gradient=gradient, - expectation=expectation, - include_custom=include_custom, - max_evals_grouped=max_evals_grouped, - callback=callback, - quantum_instance=quantum_instance, - ) - - def _check_operator_ansatz(self, operator: OperatorBase) -> None: - # Recreates a circuit based on operator parameter. - if operator != self._cost_operator: - self._cost_operator = operator - self.ansatz = QAOAAnsatz( - operator, self._reps, initial_state=self._initial_state, mixer_operator=self._mixer - ).decompose() # TODO remove decompose once #6674 is fixed - - @property - def initial_state(self) -> QuantumCircuit | None: - """ - Returns: - Returns the initial state. - """ - return self._initial_state - - @initial_state.setter - def initial_state(self, initial_state: QuantumCircuit | None) -> None: - """ - Args: - initial_state: Initial state to set. - """ - self._initial_state = initial_state - - @property - def mixer(self) -> QuantumCircuit | OperatorBase: - """ - Returns: - Returns the mixer. - """ - return self._mixer - - @mixer.setter - def mixer(self, mixer: QuantumCircuit | OperatorBase) -> None: - """ - Args: - mixer: Mixer to set. - """ - self._mixer = mixer diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py deleted file mode 100644 index 3ca342f9894d..000000000000 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ /dev/null @@ -1,749 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""The Variational Quantum Eigensolver algorithm. - -See https://arxiv.org/abs/1304.3061 -""" - -from __future__ import annotations - -import logging -import warnings -from collections.abc import Callable -from time import time - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.circuit.library import RealAmplitudes -from qiskit.opflow import ( - CircuitSampler, - CircuitStateFn, - ExpectationBase, - ExpectationFactory, - ListOp, - OperatorBase, - PauliSumOp, - StateFn, -) -from qiskit.opflow.gradients import GradientBase -from qiskit.providers import Backend -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.utils.backend_utils import is_aer_provider -from qiskit.utils.validation import validate_min -from qiskit.utils.deprecation import deprecate_func - -from ..aux_ops_evaluator import eval_observables -from ..exceptions import AlgorithmError -from ..list_or_dict import ListOrDict -from ..optimizers import SLSQP, Minimizer, Optimizer -from ..variational_algorithm import VariationalAlgorithm, VariationalResult -from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult - -logger = logging.getLogger(__name__) - - -class VQE(VariationalAlgorithm, MinimumEigensolver): - r"""Deprecated: Variational Quantum Eigensolver algorithm. - - The VQE class has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.VQE` class. - This class will be deprecated in a future release and subsequently - removed after that. - - `VQE `__ is a quantum algorithm that uses a - variational technique to find - the minimum eigenvalue of the Hamiltonian :math:`H` of a given system. - - An instance of VQE requires defining two algorithmic sub-components: - a trial state (a.k.a. ansatz) which is a :class:`QuantumCircuit`, and one of the classical - :mod:`~qiskit.algorithms.optimizers`. The ansatz is varied, via its set of parameters, by the - optimizer, such that it works towards a state, as determined by the parameters applied to the - ansatz, that will result in the minimum expectation value being measured of the input operator - (Hamiltonian). - - An optional array of parameter values, via the *initial_point*, may be provided as the - starting point for the search of the minimum eigenvalue. This feature is particularly useful - such as when there are reasons to believe that the solution point is close to a particular - point. As an example, when building the dissociation profile of a molecule, - it is likely that using the previous computed optimal solution as the starting - initial point for the next interatomic distance is going to reduce the number of iterations - necessary for the variational algorithm to converge. It provides an - `initial point tutorial `__ detailing this use case. - - The length of the *initial_point* list value must match the number of the parameters - expected by the ansatz being used. If the *initial_point* is left at the default - of ``None``, then VQE will look to the ansatz for a preferred value, based on its - given initial state. If the ansatz returns ``None``, - then a random point will be generated within the parameter bounds set, as per above. - If the ansatz provides ``None`` as the lower bound, then VQE - will default it to :math:`-2\pi`; similarly, if the ansatz returns ``None`` - as the upper bound, the default value will be :math:`2\pi`. - - The optimizer can either be one of Qiskit's optimizers, such as - :class:`~qiskit.algorithms.optimizers.SPSA` or a callable with the following signature: - - .. note:: - - The callable _must_ have the argument names ``fun, x0, jac, bounds`` as indicated - in the following code block. - - .. code-block:: python - - from qiskit.algorithms.optimizers import OptimizerResult - - def my_minimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: - # Note that the callable *must* have these argument names! - # Args: - # fun (callable): the function to minimize - # x0 (np.ndarray): the initial point for the optimization - # jac (callable, optional): the gradient of the objective function - # bounds (list, optional): a list of tuples specifying the parameter bounds - - result = OptimizerResult() - result.x = # optimal parameters - result.fun = # optimal function value - return result - - The above signature also allows to directly pass any SciPy minimizer, for instance as - - .. code-block:: python - - from functools import partial - from scipy.optimize import minimize - - optimizer = partial(minimize, method="L-BFGS-B") - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.minimum_eigensolvers.VQE``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - ansatz: QuantumCircuit | None = None, - optimizer: Optimizer | Minimizer | None = None, - initial_point: np.ndarray | None = None, - gradient: GradientBase | Callable | None = None, - expectation: ExpectationBase | None = None, - include_custom: bool = False, - max_evals_grouped: int = 1, - callback: Callable[[int, np.ndarray, float, float], None] | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - ) -> None: - """ - - Args: - ansatz: A parameterized circuit used as Ansatz for the wave function. - optimizer: A classical optimizer. Can either be a Qiskit optimizer or a callable - that takes an array as input and returns a Qiskit or SciPy optimization result. - initial_point: An optional initial point (i.e. initial parameter values) - for the optimizer. If ``None`` then VQE will look to the ansatz for a preferred - point and if not will simply compute a random one. - gradient: An optional gradient function or operator for optimizer. - expectation: The Expectation converter for taking the average value of the - Observable over the ansatz state function. When ``None`` (the default) an - :class:`~qiskit.opflow.expectations.ExpectationFactory` is used to select - an appropriate expectation based on the operator and backend. When using Aer - qasm_simulator backend, with paulis, it is however much faster to leverage custom - Aer function for the computation but, although VQE performs much faster - with it, the outcome is ideal, with no shot noise, like using a state vector - simulator. If you are just looking for the quickest performance when choosing Aer - qasm_simulator and the lack of shot noise is not an issue then set `include_custom` - parameter here to ``True`` (defaults to ``False``). - include_custom: When `expectation` parameter here is None setting this to ``True`` will - allow the factory to include the custom Aer pauli expectation. - max_evals_grouped: Max number of evaluations performed simultaneously. Signals the - given optimizer that more than one set of parameters can be supplied so that - potentially the expectation values can be computed in parallel. Typically this is - possible when a finite difference gradient is used by the optimizer such that - multiple points to compute the gradient can be passed and if computed in parallel - improve overall execution time. Deprecated if a gradient operator or function is - given. - callback: a callback that can access the intermediate data during the optimization. - Four parameter values are passed to the callback as follows during each evaluation - by the optimizer for its current set of parameters as it works towards the minimum. - These are: the evaluation count, the optimizer parameters for the - ansatz, the evaluated mean and the evaluated standard deviation.` - quantum_instance: Quantum Instance or Backend - - """ - validate_min("max_evals_grouped", max_evals_grouped, 1) - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - - self._max_evals_grouped = max_evals_grouped - self._circuit_sampler: CircuitSampler | None = None - self._expectation = None - self.expectation = expectation - self._include_custom = include_custom - - self._ansatz: QuantumCircuit | None = None - self.ansatz = ansatz - - self._optimizer: Optimizer | None = None - self.optimizer = optimizer - - self._initial_point: np.ndarray | None = None - self.initial_point = initial_point - self._gradient: GradientBase | Callable | None = None - self.gradient = gradient - self._quantum_instance: QuantumInstance | None = None - - if quantum_instance is not None: - self.quantum_instance = quantum_instance - - self._eval_time = None - self._eval_count = 0 - self._callback: Callable[[int, np.ndarray, float, float], None] | None = None - self.callback = callback - - logger.info(self.print_settings()) - - # TODO remove this once the stateful methods are deleted - self._ret: VQEResult | None = None - - @property - def ansatz(self) -> QuantumCircuit: - """Returns the ansatz.""" - return self._ansatz - - @ansatz.setter - def ansatz(self, ansatz: QuantumCircuit | None): - """Sets the ansatz. - - Args: - ansatz: The parameterized circuit used as an ansatz. - If None is passed, RealAmplitudes is used by default. - - """ - if ansatz is None: - ansatz = RealAmplitudes() - - self._ansatz = ansatz - - @property - def gradient(self) -> GradientBase | Callable | None: - """Returns the gradient.""" - return self._gradient - - @gradient.setter - def gradient(self, gradient: GradientBase | Callable | None): - """Sets the gradient.""" - self._gradient = gradient - - @property - def quantum_instance(self) -> QuantumInstance | None: - """Returns quantum instance.""" - return self._quantum_instance - - @quantum_instance.setter - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Sets quantum_instance""" - if not isinstance(quantum_instance, QuantumInstance): - quantum_instance = QuantumInstance(quantum_instance) - - self._quantum_instance = quantum_instance - self._circuit_sampler = CircuitSampler( - quantum_instance, param_qobj=is_aer_provider(quantum_instance.backend) - ) - - @property - def initial_point(self) -> np.ndarray | None: - """Returns initial point""" - return self._initial_point - - @initial_point.setter - def initial_point(self, initial_point: np.ndarray): - """Sets initial point""" - self._initial_point = initial_point - - @property - def max_evals_grouped(self) -> int: - """Returns max_evals_grouped""" - return self._max_evals_grouped - - @max_evals_grouped.setter - def max_evals_grouped(self, max_evals_grouped: int): - """Sets max_evals_grouped""" - self._max_evals_grouped = max_evals_grouped - self.optimizer.set_max_evals_grouped(max_evals_grouped) - - @property - def include_custom(self) -> bool: - """Returns include_custom""" - return self._include_custom - - @include_custom.setter - def include_custom(self, include_custom: bool): - """Sets include_custom. If set to another value than the one that was previsously set, - the expectation attribute is reset to None. - """ - if include_custom != self._include_custom: - self._include_custom = include_custom - self.expectation = None - - @property - def callback(self) -> Callable[[int, np.ndarray, float, float], None] | None: - """Returns callback""" - return self._callback - - @callback.setter - def callback(self, callback: Callable[[int, np.ndarray, float, float], None] | None): - """Sets callback""" - self._callback = callback - - @property - def expectation(self) -> ExpectationBase | None: - """The expectation value algorithm used to construct the expectation measurement from - the observable.""" - return self._expectation - - @expectation.setter - def expectation(self, exp: ExpectationBase | None) -> None: - self._expectation = exp - - def _check_operator_ansatz(self, operator: OperatorBase): - """Check that the number of qubits of operator and ansatz match.""" - if operator is not None and self.ansatz is not None: - if operator.num_qubits != self.ansatz.num_qubits: - # try to set the number of qubits on the ansatz, if possible - try: - self.ansatz.num_qubits = operator.num_qubits - except AttributeError as ex: - raise AlgorithmError( - "The number of qubits of the ansatz does not match the " - "operator, and the ansatz does not allow setting the " - "number of qubits using `num_qubits`." - ) from ex - - @property - def optimizer(self) -> Optimizer: - """Returns optimizer""" - return self._optimizer - - @optimizer.setter - def optimizer(self, optimizer: Optimizer | None): - """Sets the optimizer attribute. - - Args: - optimizer: The optimizer to be used. If None is passed, SLSQP is used by default. - - """ - if optimizer is None: - optimizer = SLSQP() - - if isinstance(optimizer, Optimizer): - optimizer.set_max_evals_grouped(self.max_evals_grouped) - - self._optimizer = optimizer - - @property - def setting(self): - """Prepare the setting of VQE as a string.""" - ret = f"Algorithm: {self.__class__.__name__}\n" - params = "" - for key, value in self.__dict__.items(): - if key[0] == "_": - if "initial_point" in key and value is None: - params += "-- {}: {}\n".format(key[1:], "Random seed") - else: - params += f"-- {key[1:]}: {value}\n" - ret += f"{params}" - return ret - - def print_settings(self): - """ - Preparing the setting of VQE into a string. - - Returns: - str: the formatted setting of VQE - """ - ret = "\n" - ret += "==================== Setting of {} ============================\n".format( - self.__class__.__name__ - ) - ret += f"{self.setting}" - ret += "===============================================================\n" - if self.ansatz is not None: - ret += "{}".format(self.ansatz.draw(output="text")) - else: - ret += "ansatz has not been set" - ret += "===============================================================\n" - if callable(self.optimizer): - ret += "Optimizer is custom callable\n" - else: - ret += f"{self._optimizer.setting}" - ret += "===============================================================\n" - return ret - - def construct_expectation( - self, - parameter: list[float] | list[Parameter] | np.ndarray, - operator: OperatorBase, - return_expectation: bool = False, - ) -> OperatorBase | tuple[OperatorBase, ExpectationBase]: - r""" - Generate the ansatz circuit and expectation value measurement, and return their - runnable composition. - - Args: - parameter: Parameters for the ansatz circuit. - operator: Qubit operator of the Observable - return_expectation: If True, return the ``ExpectationBase`` expectation converter used - in the construction of the expectation value. Useful e.g. to compute the standard - deviation of the expectation value. - - Returns: - The Operator equalling the measurement of the ansatz :class:`StateFn` by the - Observable's expectation :class:`StateFn`, and, optionally, the expectation converter. - - Raises: - AlgorithmError: If no operator has been provided. - AlgorithmError: If no expectation is passed and None could be inferred via the - ExpectationFactory. - """ - if operator is None: - raise AlgorithmError("The operator was never provided.") - - self._check_operator_ansatz(operator) - - # if expectation was never created, try to create one - if self.expectation is None: - expectation = ExpectationFactory.build( - operator=operator, - backend=self.quantum_instance, - include_custom=self._include_custom, - ) - else: - expectation = self.expectation - - wave_function = self.ansatz.assign_parameters(parameter) - - observable_meas = expectation.convert(StateFn(operator, is_measurement=True)) - ansatz_circuit_op = CircuitStateFn(wave_function) - expect_op = observable_meas.compose(ansatz_circuit_op).reduce() - - if return_expectation: - return expect_op, expectation - - return expect_op - - def construct_circuit( - self, - parameter: list[float] | list[Parameter] | np.ndarray, - operator: OperatorBase, - ) -> list[QuantumCircuit]: - """Return the circuits used to compute the expectation value. - - Args: - parameter: Parameters for the ansatz circuit. - operator: Qubit operator of the Observable - - Returns: - A list of the circuits used to compute the expectation value. - """ - expect_op = self.construct_expectation(parameter, operator).to_circuit_op() - - circuits = [] - - # recursively extract circuits - def extract_circuits(op): - if isinstance(op, CircuitStateFn): - circuits.append(op.primitive) - elif isinstance(op, ListOp): - for op_i in op.oplist: - extract_circuits(op_i) - - extract_circuits(expect_op) - - return circuits - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> MinimumEigensolverResult: - super().compute_minimum_eigenvalue(operator, aux_operators) - - if self.quantum_instance is None: - raise AlgorithmError( - "A QuantumInstance or Backend must be supplied to run the quantum algorithm." - ) - self.quantum_instance.circuit_summary = True - - # this sets the size of the ansatz, so it must be called before the initial point - # validation - self._check_operator_ansatz(operator) - - # set an expectation for this algorithm run (will be reset to None at the end) - initial_point = _validate_initial_point(self.initial_point, self.ansatz) - - bounds = _validate_bounds(self.ansatz) - # We need to handle the array entries being zero or Optional i.e. having value None - if aux_operators: - zero_op = PauliSumOp.from_list([("I" * self.ansatz.num_qubits, 0)]) - - # Convert the None and zero values when aux_operators is a list. - # Drop None and convert zero values when aux_operators is a dict. - if isinstance(aux_operators, list): - key_op_iterator = enumerate(aux_operators) - converted: ListOrDict[OperatorBase] = [zero_op] * len(aux_operators) - else: - key_op_iterator = aux_operators.items() - converted = {} - for key, op in key_op_iterator: - if op is not None: - converted[key] = zero_op if op == 0 else op - - aux_operators = converted - - else: - aux_operators = None - - # Convert the gradient operator into a callable function that is compatible with the - # optimization routine. - if isinstance(self._gradient, GradientBase): - gradient = self._gradient.gradient_wrapper( - ~StateFn(operator) @ StateFn(self.ansatz), - bind_params=list(self.ansatz.parameters), - backend=self._quantum_instance, - ) - else: - gradient = self._gradient - - self._eval_count = 0 - energy_evaluation, expectation = self.get_energy_evaluation( - operator, return_expectation=True - ) - - start_time = time() - - if callable(self.optimizer): - opt_result = self.optimizer( # pylint: disable=not-callable - fun=energy_evaluation, x0=initial_point, jac=gradient, bounds=bounds - ) - else: - opt_result = self.optimizer.minimize( - fun=energy_evaluation, x0=initial_point, jac=gradient, bounds=bounds - ) - - eval_time = time() - start_time - - result = VQEResult() - result.optimal_point = opt_result.x - result.optimal_parameters = dict(zip(self.ansatz.parameters, opt_result.x)) - result.optimal_value = opt_result.fun - result.cost_function_evals = opt_result.nfev - result.optimizer_time = eval_time - result.eigenvalue = opt_result.fun + 0j - result.eigenstate = self._get_eigenstate(result.optimal_parameters) - - logger.info( - "Optimization complete in %s seconds.\nFound opt_params %s in %s evals", - eval_time, - result.optimal_point, - self._eval_count, - ) - - # TODO delete as soon as get_optimal_vector etc are removed - self._ret = result - - if aux_operators is not None: - bound_ansatz = self.ansatz.assign_parameters(result.optimal_point) - - aux_values = eval_observables( - self.quantum_instance, bound_ansatz, aux_operators, expectation=expectation - ) - result.aux_operator_eigenvalues = aux_values - - return result - - def get_energy_evaluation( - self, - operator: OperatorBase, - return_expectation: bool = False, - ) -> Callable[[np.ndarray], float | list[float]] | tuple[ - Callable[[np.ndarray], float | list[float]], ExpectationBase - ]: - """Returns a function handle to evaluates the energy at given parameters for the ansatz. - - This is the objective function to be passed to the optimizer that is used for evaluation. - - Args: - operator: The operator whose energy to evaluate. - return_expectation: If True, return the ``ExpectationBase`` expectation converter used - in the construction of the expectation value. Useful e.g. to evaluate other - operators with the same expectation value converter. - - - Returns: - Energy of the hamiltonian of each parameter, and, optionally, the expectation - converter. - - Raises: - RuntimeError: If the circuit is not parameterized (i.e. has 0 free parameters). - - """ - num_parameters = self.ansatz.num_parameters - if num_parameters == 0: - raise RuntimeError("The ansatz must be parameterized, but has 0 free parameters.") - - ansatz_params = self.ansatz.parameters - expect_op, expectation = self.construct_expectation( - ansatz_params, operator, return_expectation=True - ) - - def energy_evaluation(parameters): - parameter_sets = np.reshape(parameters, (-1, num_parameters)) - # Create dict associating each parameter with the lists of parameterization values for it - param_bindings = dict(zip(ansatz_params, parameter_sets.transpose().tolist())) - - start_time = time() - sampled_expect_op = self._circuit_sampler.convert(expect_op, params=param_bindings) - means = np.real(sampled_expect_op.eval()) - - if self._callback is not None: - variance = np.real(expectation.compute_variance(sampled_expect_op)) - estimator_error = np.sqrt(variance / self.quantum_instance.run_config.shots) - for i, param_set in enumerate(parameter_sets): - self._eval_count += 1 - self._callback(self._eval_count, param_set, means[i], estimator_error[i]) - else: - self._eval_count += len(means) - - end_time = time() - logger.info( - "Energy evaluation returned %s - %.5f (ms), eval count: %s", - means, - (end_time - start_time) * 1000, - self._eval_count, - ) - - return means if len(means) > 1 else means[0] - - if return_expectation: - return energy_evaluation, expectation - - return energy_evaluation - - def _get_eigenstate(self, optimal_parameters) -> list[float] | dict[str, int]: - """Get the simulation outcome of the ansatz, provided with parameters.""" - optimal_circuit = self.ansatz.assign_parameters(optimal_parameters) - state_fn = self._circuit_sampler.convert(StateFn(optimal_circuit)).eval() - if self.quantum_instance.is_statevector: - state = state_fn.primitive.data # VectorStateFn -> Statevector -> np.array - else: - state = state_fn.to_dict_fn().primitive # SparseVectorStateFn -> DictStateFn -> dict - - return state - - -class VQEResult(VariationalResult, MinimumEigensolverResult): - """Deprecated: VQE Result. - - The VQEResult class has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.VQEResult` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.minimum_eigensolvers.VQEResult``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - self._cost_function_evals: int | None = None - - @property - def cost_function_evals(self) -> int | None: - """Returns number of cost optimizer evaluations""" - return self._cost_function_evals - - @cost_function_evals.setter - def cost_function_evals(self, value: int) -> None: - """Sets number of cost function evaluations""" - self._cost_function_evals = value - - @property - def eigenstate(self) -> np.ndarray | None: - """return eigen state""" - return self._eigenstate - - @eigenstate.setter - def eigenstate(self, value: np.ndarray) -> None: - """set eigen state""" - self._eigenstate = value - - -def _validate_initial_point(point, ansatz): - expected_size = ansatz.num_parameters - - # try getting the initial point from the ansatz - if point is None and hasattr(ansatz, "preferred_init_points"): - point = ansatz.preferred_init_points - # if the point is None choose a random initial point - - if point is None: - # get bounds if ansatz has them set, otherwise use [-2pi, 2pi] for each parameter - bounds = getattr(ansatz, "parameter_bounds", None) - if bounds is None: - bounds = [(-2 * np.pi, 2 * np.pi)] * expected_size - - # replace all Nones by [-2pi, 2pi] - lower_bounds = [] - upper_bounds = [] - for lower, upper in bounds: - lower_bounds.append(lower if lower is not None else -2 * np.pi) - upper_bounds.append(upper if upper is not None else 2 * np.pi) - - # sample from within bounds - point = algorithm_globals.random.uniform(lower_bounds, upper_bounds) - - elif len(point) != expected_size: - raise ValueError( - f"The dimension of the initial point ({len(point)}) does not match the " - f"number of parameters in the circuit ({expected_size})." - ) - - return point - - -def _validate_bounds(ansatz): - if hasattr(ansatz, "parameter_bounds") and ansatz.parameter_bounds is not None: - bounds = ansatz.parameter_bounds - if len(bounds) != ansatz.num_parameters: - raise ValueError( - f"The number of bounds ({len(bounds)}) does not match the number of " - f"parameters in the circuit ({ansatz.num_parameters})." - ) - else: - bounds = [(None, None)] * ansatz.num_parameters - - return bounds diff --git a/qiskit/algorithms/minimum_eigensolvers/__init__.py b/qiskit/algorithms/minimum_eigensolvers/__init__.py deleted file mode 100644 index d7406c09860b..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/__init__.py +++ /dev/null @@ -1,66 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -""" -============================================================================ -Minimum Eigensolvers Package (:mod:`qiskit.algorithms.minimum_eigensolvers`) -============================================================================ - -.. currentmodule:: qiskit.algorithms.minimum_eigensolvers - -Minimum Eigensolvers -==================== -.. autosummary:: - :toctree: ../stubs/ - - MinimumEigensolver - NumPyMinimumEigensolver - VQE - AdaptVQE - SamplingMinimumEigensolver - SamplingVQE - QAOA - -.. autosummary:: - :toctree: ../stubs/ - - MinimumEigensolverResult - NumPyMinimumEigensolverResult - VQEResult - AdaptVQEResult - SamplingMinimumEigensolverResult - SamplingVQEResult -""" - -from .adapt_vqe import AdaptVQE, AdaptVQEResult -from .minimum_eigensolver import MinimumEigensolver, MinimumEigensolverResult -from .numpy_minimum_eigensolver import NumPyMinimumEigensolver, NumPyMinimumEigensolverResult -from .vqe import VQE, VQEResult -from .sampling_mes import SamplingMinimumEigensolver, SamplingMinimumEigensolverResult -from .sampling_vqe import SamplingVQE, SamplingVQEResult -from .qaoa import QAOA - -__all__ = [ - "AdaptVQE", - "AdaptVQEResult", - "MinimumEigensolver", - "MinimumEigensolverResult", - "NumPyMinimumEigensolver", - "NumPyMinimumEigensolverResult", - "VQE", - "VQEResult", - "SamplingMinimumEigensolver", - "SamplingMinimumEigensolverResult", - "SamplingVQE", - "SamplingVQEResult", - "QAOA", -] diff --git a/qiskit/algorithms/minimum_eigensolvers/adapt_vqe.py b/qiskit/algorithms/minimum_eigensolvers/adapt_vqe.py deleted file mode 100644 index 4398d63e5107..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/adapt_vqe.py +++ /dev/null @@ -1,419 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""An implementation of the AdaptVQE algorithm.""" -from __future__ import annotations - -from collections.abc import Sequence -from enum import Enum - -import re -import logging -import warnings -from typing import Any - -import numpy as np - -from qiskit import QiskitError -from qiskit.algorithms.list_or_dict import ListOrDict -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.opflow import OperatorBase, PauliSumOp -from qiskit.circuit.library import EvolvedOperatorAnsatz -from qiskit.utils.deprecation import deprecate_arg, deprecate_func -from qiskit.utils.validation import validate_min - -from .minimum_eigensolver import MinimumEigensolver -from .vqe import VQE, VQEResult -from ..observables_evaluator import estimate_observables -from ..variational_algorithm import VariationalAlgorithm - - -logger = logging.getLogger(__name__) - - -class TerminationCriterion(Enum): - """A class enumerating the various finishing criteria.""" - - CONVERGED = "Threshold converged" - CYCLICITY = "Aborted due to a cyclic selection of evolution operators" - MAXIMUM = "Maximum number of iterations reached" - - -class AdaptVQE(VariationalAlgorithm, MinimumEigensolver): - """The Adaptive Variational Quantum Eigensolver algorithm. - - `AdaptVQE `__ is a quantum algorithm which creates a compact - ansatz from a set of evolution operators. It iteratively extends the ansatz circuit, by - selecting the building block that leads to the largest gradient from a set of candidates. In - chemistry, this is usually a list of orbital excitations. Thus, a common choice of ansatz to be - used with this algorithm is the Unitary Coupled Cluster ansatz implemented in Qiskit Nature. - This results in a wavefunction ansatz which is uniquely adapted to the operator whose minimum - eigenvalue is being determined. This class relies on a supplied instance of :class:`~.VQE` to - find the minimum eigenvalue. The performance of AdaptVQE significantly depends on the - minimization routine. - - .. code-block:: python - - from qiskit.algorithms.minimum_eigensolvers import AdaptVQE, VQE - from qiskit.algorithms.optimizers import SLSQP - from qiskit.primitives import Estimator - from qiskit.circuit.library import EvolvedOperatorAnsatz - - # get your Hamiltonian - hamiltonian = ... - - # construct your ansatz - ansatz = EvolvedOperatorAnsatz(...) - - vqe = VQE(Estimator(), ansatz, SLSQP()) - - adapt_vqe = AdaptVQE(vqe) - - eigenvalue, _ = adapt_vqe.compute_minimum_eigenvalue(hamiltonian) - - The following attributes can be set via the initializer but can also be read and updated once - the AdaptVQE object has been constructed. - - Attributes: - solver: a :class:`~.VQE` instance used internally to compute the minimum eigenvalues. - It is a requirement that the :attr:`~.VQE.ansatz` of this solver is of type - :class:`~qiskit.circuit.library.EvolvedOperatorAnsatz`. - gradient_threshold: once all gradients have an absolute value smaller than this threshold, - the algorithm has converged and terminates. - eigenvalue_threshold: once the eigenvalue has changed by less than this threshold from one - iteration to the next, the algorithm has converged and terminates. When this case - occurs, the excitation included in the final iteration did not result in a significant - improvement of the eigenvalue and, thus, the results from this iteration are not - considered. - max_iterations: the maximum number of iterations for the adaptive loop. If ``None``, the - algorithm is not bound in its number of iterations. - """ - - @deprecate_arg( - "threshold", - since="0.24.0", - pending=True, - new_alias="gradient_threshold", - ) - def __init__( - self, - solver: VQE, - *, - gradient_threshold: float = 1e-5, - eigenvalue_threshold: float = 1e-5, - max_iterations: int | None = None, - threshold: float | None = None, # pylint: disable=unused-argument - ) -> None: - """ - Args: - solver: a :class:`~.VQE` instance used internally to compute the minimum eigenvalues. - It is a requirement that the :attr:`~.VQE.ansatz` of this solver is of type - :class:`~qiskit.circuit.library.EvolvedOperatorAnsatz`. - gradient_threshold: once all gradients have an absolute value smaller than this - threshold, the algorithm has converged and terminates. - eigenvalue_threshold: once the eigenvalue has changed by less than this threshold from - one iteration to the next, the algorithm has converged and terminates. When this - case occurs, the excitation included in the final iteration did not result in a - significant improvement of the eigenvalue and, thus, the results from this iteration - are not considered. - max_iterations: the maximum number of iterations for the adaptive loop. If ``None``, the - algorithm is not bound in its number of iterations. - threshold: once all gradients have an absolute value smaller than this threshold, the - algorithm has converged and terminates. Defaults to ``1e-5``. - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_min("gradient_threshold", gradient_threshold, 1e-15) - validate_min("eigenvalue_threshold", eigenvalue_threshold, 1e-15) - - self.solver = solver - self.gradient_threshold = gradient_threshold - self.eigenvalue_threshold = eigenvalue_threshold - self.max_iterations = max_iterations - self._tmp_ansatz: EvolvedOperatorAnsatz | None = None - self._excitation_pool: list[OperatorBase] = [] - self._excitation_list: list[OperatorBase] = [] - - @property - @deprecate_func( - since="0.24.0", - pending=True, - is_property=True, - additional_msg="Instead, use the gradient_threshold attribute.", - ) - def threshold(self) -> float: - """The threshold for the gradients. - - Once all gradients have an absolute value smaller than this threshold, the algorithm has - converged and terminates. - """ - return self.gradient_threshold - - @threshold.setter - @deprecate_func( - since="0.24.0", - pending=True, - is_property=True, - additional_msg="Instead, use the gradient_threshold attribute.", - ) - def threshold(self, threshold: float) -> None: - self.gradient_threshold = threshold - - @property - def initial_point(self) -> Sequence[float] | None: - """Returns the initial point of the internal :class:`~.VQE` solver.""" - return self.solver.initial_point - - @initial_point.setter - def initial_point(self, value: Sequence[float] | None) -> None: - """Sets the initial point of the internal :class:`~.VQE` solver.""" - self.solver.initial_point = value - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def _compute_gradients( - self, - theta: list[float], - operator: BaseOperator | OperatorBase, - ) -> list[tuple[complex, dict[str, Any]]]: - """ - Computes the gradients for all available excitation operators. - - Args: - theta: List of (up to now) optimal parameters. - operator: operator whose gradient needs to be computed. - Returns: - List of pairs consisting of the computed gradient and excitation operator. - """ - # The excitations operators are applied later as exp(i*theta*excitation). - # For this commutator, we need to explicitly pull in the imaginary phase. - commutators = [1j * (operator @ exc - exc @ operator) for exc in self._excitation_pool] - res = estimate_observables(self.solver.estimator, self.solver.ansatz, commutators, theta) - return res - - @staticmethod - def _check_cyclicity(indices: list[int]) -> bool: - """ - Auxiliary function to check for cycles in the indices of the selected excitations. - - Args: - indices: The list of chosen gradient indices. - - Returns: - Whether repeating sequences of indices have been detected. - """ - cycle_regex = re.compile(r"(\b.+ .+\b)( \b\1\b)+") - # reg-ex explanation: - # 1. (\b.+ .+\b) will match at least two numbers and try to match as many as possible. The - # word boundaries in the beginning and end ensure that now numbers are split into digits. - # 2. the match of this part is placed into capture group 1 - # 3. ( \b\1\b)+ will match a space followed by the contents of capture group 1 (again - # delimited by word boundaries to avoid separation into digits). - # -> this results in any sequence of at least two numbers being detected - match = cycle_regex.search(" ".join(map(str, indices))) - logger.debug("Cycle detected: %s", match) - # Additionally we also need to check whether the last two numbers are identical, because the - # reg-ex above will only find cycles of at least two consecutive numbers. - # It is sufficient to assert that the last two numbers are different due to the iterative - # nature of the algorithm. - return match is not None or (len(indices) > 1 and indices[-2] == indices[-1]) - - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> AdaptVQEResult: - """Computes the minimum eigenvalue. - - Args: - operator: Operator whose minimum eigenvalue we want to find. - aux_operators: Additional auxiliary operators to evaluate. - - Raises: - TypeError: If an ansatz other than :class:`~.EvolvedOperatorAnsatz` is provided. - QiskitError: If all evaluated gradients lie below the convergence threshold in the first - iteration of the algorithm. - - Returns: - An :class:`~.AdaptVQEResult` which is a :class:`~.VQEResult` but also but also - includes runtime information about the AdaptVQE algorithm like the number of iterations, - termination criterion, and the final maximum gradient. - """ - if not isinstance(self.solver.ansatz, EvolvedOperatorAnsatz): - raise TypeError("The AdaptVQE ansatz must be of the EvolvedOperatorAnsatz type.") - - # Overwrite the solver's ansatz with the initial state - self._tmp_ansatz = self.solver.ansatz - self._excitation_pool = self._tmp_ansatz.operators - self.solver.ansatz = self._tmp_ansatz.initial_state - - prev_op_indices: list[int] = [] - prev_raw_vqe_result: VQEResult | None = None - raw_vqe_result: VQEResult | None = None - theta: list[float] = [] - max_grad: tuple[complex, dict[str, Any] | None] = (0.0, None) - self._excitation_list = [] - history: list[complex] = [] - iteration = 0 - while self.max_iterations is None or iteration < self.max_iterations: - iteration += 1 - logger.info("--- Iteration #%s ---", str(iteration)) - # compute gradients - logger.debug("Computing gradients") - cur_grads = self._compute_gradients(theta, operator) - # pick maximum gradient - max_grad_index, max_grad = max( - enumerate(cur_grads), key=lambda item: np.abs(item[1][0]) - ) - logger.info( - "Found maximum gradient %s at index %s", - str(np.abs(max_grad[0])), - str(max_grad_index), - ) - # log gradients - if np.abs(max_grad[0]) < self.gradient_threshold: - if iteration == 1: - raise QiskitError( - "All gradients have been evaluated to lie below the convergence threshold " - "during the first iteration of the algorithm. Try to either tighten the " - "convergence threshold or pick a different ansatz." - ) - logger.info( - "AdaptVQE terminated successfully with a final maximum gradient: %s", - str(np.abs(max_grad[0])), - ) - termination_criterion = TerminationCriterion.CONVERGED - break - # store maximum gradient's index for cycle detection - prev_op_indices.append(max_grad_index) - # check indices of picked gradients for cycles - if self._check_cyclicity(prev_op_indices): - logger.info("Alternating sequence found. Finishing.") - logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) - termination_criterion = TerminationCriterion.CYCLICITY - break - # add new excitation to self._ansatz - logger.info( - "Adding new operator to the ansatz: %s", str(self._excitation_pool[max_grad_index]) - ) - self._excitation_list.append(self._excitation_pool[max_grad_index]) - theta.append(0.0) - # setting up the ansatz for the VQE iteration - self._tmp_ansatz.operators = self._excitation_list - self.solver.ansatz = self._tmp_ansatz - self.solver.initial_point = theta - # evaluating the eigenvalue with the internal VQE - prev_raw_vqe_result = raw_vqe_result - raw_vqe_result = self.solver.compute_minimum_eigenvalue(operator) - theta = raw_vqe_result.optimal_point.tolist() - # checking convergence based on the change in eigenvalue - if iteration > 1: - eigenvalue_diff = np.abs(raw_vqe_result.eigenvalue - history[-1]) - if eigenvalue_diff < self.eigenvalue_threshold: - logger.info( - "AdaptVQE terminated successfully with a final change in eigenvalue: %s", - str(eigenvalue_diff), - ) - termination_criterion = TerminationCriterion.CONVERGED - logger.debug( - "Reverting the addition of the last excitation to the ansatz since it " - "resulted in a change of the eigenvalue below the configured threshold." - ) - self._excitation_list.pop() - theta.pop() - self._tmp_ansatz.operators = self._excitation_list - self.solver.ansatz = self._tmp_ansatz - self.solver.initial_point = theta - raw_vqe_result = prev_raw_vqe_result - break - # appending the computed eigenvalue to the tracking history - history.append(raw_vqe_result.eigenvalue) - logger.info("Current eigenvalue: %s", str(raw_vqe_result.eigenvalue)) - else: - # reached maximum number of iterations - termination_criterion = TerminationCriterion.MAXIMUM - logger.info("Maximum number of iterations reached. Finishing.") - logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) - - result = AdaptVQEResult() - result.combine(raw_vqe_result) - result.num_iterations = iteration - result.final_max_gradient = max_grad[0] - result.termination_criterion = termination_criterion - result.eigenvalue_history = history - - # once finished evaluate auxiliary operators if any - if aux_operators is not None: - aux_values = estimate_observables( - self.solver.estimator, self.solver.ansatz, aux_operators, result.optimal_point - ) - result.aux_operators_evaluated = aux_values - - logger.info("The final eigenvalue is: %s", str(result.eigenvalue)) - self.solver.ansatz.operators = self._excitation_pool - return result - - -class AdaptVQEResult(VQEResult): - """AdaptVQE Result.""" - - def __init__(self) -> None: - super().__init__() - self._num_iterations: int | None = None - self._final_max_gradient: float | None = None - self._termination_criterion: str = "" - self._eigenvalue_history: list[float] | None = None - - @property - def num_iterations(self) -> int: - """Returns the number of iterations.""" - return self._num_iterations - - @num_iterations.setter - def num_iterations(self, value: int) -> None: - """Sets the number of iterations.""" - self._num_iterations = value - - @property - def final_max_gradient(self) -> float: - """Returns the final maximum gradient.""" - return self._final_max_gradient - - @final_max_gradient.setter - def final_max_gradient(self, value: float) -> None: - """Sets the final maximum gradient.""" - self._final_max_gradient = value - - @property - def termination_criterion(self) -> str: - """Returns the termination criterion.""" - return self._termination_criterion - - @termination_criterion.setter - def termination_criterion(self, value: str) -> None: - """Sets the termination criterion.""" - self._termination_criterion = value - - @property - def eigenvalue_history(self) -> list[float]: - """Returns the history of computed eigenvalues. - - The history's length matches the number of iterations and includes the final computed value. - """ - return self._eigenvalue_history - - @eigenvalue_history.setter - def eigenvalue_history(self, eigenvalue_history: list[float]) -> None: - """Sets the history of computed eigenvalues.""" - self._eigenvalue_history = eigenvalue_history diff --git a/qiskit/algorithms/minimum_eigensolvers/diagonal_estimator.py b/qiskit/algorithms/minimum_eigensolvers/diagonal_estimator.py deleted file mode 100644 index 40354c884d98..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/diagonal_estimator.py +++ /dev/null @@ -1,199 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Expectation value for a diagonal observable using a sampler primitive.""" - -from __future__ import annotations - -from collections.abc import Callable, Sequence, Mapping -from typing import Any - -from dataclasses import dataclass - -import numpy as np -from qiskit.algorithms.algorithm_job import AlgorithmJob -from qiskit.circuit import QuantumCircuit -from qiskit.primitives import BaseSampler, BaseEstimator, EstimatorResult -from qiskit.primitives.utils import init_observable, _circuit_key -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import SparsePauliOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - - -@dataclass(frozen=True) -class _DiagonalEstimatorResult(EstimatorResult): - """A result from an expectation of a diagonal observable.""" - - # TODO make each measurement a dataclass rather than a dict - best_measurements: Sequence[Mapping[str, Any]] | None = None - - -class _DiagonalEstimator(BaseEstimator): - """An estimator for diagonal observables.""" - - def __init__( - self, - sampler: BaseSampler, - aggregation: float | Callable[[Sequence[tuple[float, float]]], float] | None = None, - callback: Callable[[Sequence[Mapping[str, Any]]], None] | None = None, - **options, - ) -> None: - r"""Evaluate the expectation of quantum state with respect to a diagonal operator. - - Args: - sampler: The sampler used to evaluate the circuits. - aggregation: The aggregation function to aggregate the measurement outcomes. If a float - this specified the CVaR :math:`\alpha` parameter. - callback: A callback which is given the best measurements of all circuits in each - evaluation. - run_options: Options for the sampler. - - """ - super().__init__(options=options) - self.sampler = sampler - if not callable(aggregation): - aggregation = _get_cvar_aggregation(aggregation) - - self.aggregation = aggregation - self.callback = callback - self._circuit_ids = {} - self._observable_ids = {} - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - **run_options, - ) -> AlgorithmJob: - circuit_indices = [] - for circuit in circuits: - key = _circuit_key(circuit) - index = self._circuit_ids.get(key) - if index is not None: - circuit_indices.append(index) - else: - circuit_indices.append(len(self._circuits)) - self._circuit_ids[key] = len(self._circuits) - self._circuits.append(circuit) - self._parameters.append(circuit.parameters) - observable_indices = [] - for observable in observables: - index = self._observable_ids.get(id(observable)) - if index is not None: - observable_indices.append(index) - else: - observable_indices.append(len(self._observables)) - self._observable_ids[id(observable)] = len(self._observables) - converted_observable = init_observable(observable) - _check_observable_is_diagonal(converted_observable) # check it's diagonal - self._observables.append(converted_observable) - job = AlgorithmJob( - self._call, circuit_indices, observable_indices, parameter_values, **run_options - ) - job.submit() - return job - - def _call( - self, - circuits: Sequence[int], - observables: Sequence[int], - parameter_values: Sequence[Sequence[float]], - **run_options, - ) -> _DiagonalEstimatorResult: - job = self.sampler.run( - [self._circuits[i] for i in circuits], - parameter_values, - **run_options, - ) - sampler_result = job.result() - samples = sampler_result.quasi_dists - - # a list of dictionaries containing: {state: (measurement probability, value)} - evaluations = [ - { - state: (probability, _evaluate_sparsepauli(state, self._observables[i])) - for state, probability in sampled.items() - } - for i, sampled in zip(observables, samples) - ] - - results = np.array([self.aggregation(evaluated.values()) for evaluated in evaluations]) - - # get the best measurements - best_measurements = [] - num_qubits = self._circuits[0].num_qubits - for evaluated in evaluations: - best_result = min(evaluated.items(), key=lambda x: x[1][1]) - best_measurements.append( - { - "state": best_result[0], - "bitstring": bin(best_result[0])[2:].zfill(num_qubits), - "value": best_result[1][1], - "probability": best_result[1][0], - } - ) - - if self.callback is not None: - self.callback(best_measurements) - - return _DiagonalEstimatorResult( - values=results, metadata=sampler_result.metadata, best_measurements=best_measurements - ) - - -def _get_cvar_aggregation(alpha): - """Get the aggregation function for CVaR with confidence level ``alpha``.""" - if alpha is None: - alpha = 1 - elif not 0 <= alpha <= 1: - raise ValueError(f"alpha must be in [0, 1] but was {alpha}") - - # if alpha is close to 1 we can avoid the sorting - if np.isclose(alpha, 1): - - def aggregate(measurements): - return sum(probability * value for probability, value in measurements) - - else: - - def aggregate(measurements): - # sort by values - sorted_measurements = sorted(measurements, key=lambda x: x[1]) - - accumulated_percent = 0 # once alpha is reached, stop - cvar = 0 - for probability, value in sorted_measurements: - cvar += value * min(probability, alpha - accumulated_percent) - accumulated_percent += probability - if accumulated_percent >= alpha: - break - - return cvar / alpha - - return aggregate - - -_PARITY = np.array([-1 if bin(i).count("1") % 2 else 1 for i in range(256)], dtype=np.complex128) - - -def _evaluate_sparsepauli(state: int, observable: SparsePauliOp) -> complex: - packed_uint8 = np.packbits(observable.paulis.z, axis=1, bitorder="little") - state_bytes = np.frombuffer(state.to_bytes(packed_uint8.shape[1], "little"), dtype=np.uint8) - reduced = np.bitwise_xor.reduce(packed_uint8 & state_bytes, axis=1) - return np.sum(observable.coeffs * _PARITY[reduced]) - - -def _check_observable_is_diagonal(observable: SparsePauliOp) -> None: - is_diagonal = not np.any(observable.paulis.x) - if not is_diagonal: - raise ValueError("The observable must be diagonal.") diff --git a/qiskit/algorithms/minimum_eigensolvers/minimum_eigensolver.py b/qiskit/algorithms/minimum_eigensolvers/minimum_eigensolver.py deleted file mode 100644 index 26087c053aef..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/minimum_eigensolver.py +++ /dev/null @@ -1,97 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""The minimum eigensolver interface and result.""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import Any - -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..algorithm_result import AlgorithmResult -from ..list_or_dict import ListOrDict - - -class MinimumEigensolver(ABC): - """The minimum eigensolver interface. - - Algorithms that can compute a minimum eigenvalue for an operator may implement this interface to - allow different algorithms to be used interchangeably. - """ - - @abstractmethod - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> "MinimumEigensolverResult": - """ - Computes the minimum eigenvalue. The ``operator`` and ``aux_operators`` are supplied here. - While an ``operator`` is required by algorithms, ``aux_operators`` are optional. - - Args: - operator: Qubit operator of the observable. - aux_operators: Optional list of auxiliary operators to be evaluated with the - parameters of the minimum eigenvalue main result and their expectation values - returned. For instance in chemistry these can be dipole operators and total particle - count operators, so we can get values for these at the ground state. - - Returns: - A minimum eigensolver result. - """ - return MinimumEigensolverResult() - - @classmethod - def supports_aux_operators(cls) -> bool: - """Whether computing the expectation value of auxiliary operators is supported. - - If the minimum eigensolver computes an eigenvalue of the main ``operator`` then it can - compute the expectation value of the ``aux_operators`` for that state. Otherwise they will - be ignored. - - Returns: - True if aux_operator expectations can be evaluated, False otherwise - """ - return False - - -class MinimumEigensolverResult(AlgorithmResult): - """Minimum eigensolver result.""" - - def __init__(self) -> None: - super().__init__() - self._eigenvalue: complex | None = None - self._aux_operators_evaluated: ListOrDict[tuple[complex, dict[str, Any]]] | None = None - - @property - def eigenvalue(self) -> complex | None: - """The computed minimum eigenvalue.""" - return self._eigenvalue - - @eigenvalue.setter - def eigenvalue(self, value: complex) -> None: - self._eigenvalue = value - - @property - def aux_operators_evaluated(self) -> ListOrDict[tuple[complex, dict[str, Any]]] | None: - """The aux operator expectation values. - - These values are in fact tuples formatted as (mean, (variance, shots)). - """ - return self._aux_operators_evaluated - - @aux_operators_evaluated.setter - def aux_operators_evaluated(self, value: ListOrDict[tuple[complex, dict[str, Any]]]) -> None: - self._aux_operators_evaluated = value diff --git a/qiskit/algorithms/minimum_eigensolvers/numpy_minimum_eigensolver.py b/qiskit/algorithms/minimum_eigensolvers/numpy_minimum_eigensolver.py deleted file mode 100644 index 93dc328b282c..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/numpy_minimum_eigensolver.py +++ /dev/null @@ -1,106 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""The NumPy minimum eigensolver algorithm and result.""" - -from __future__ import annotations - -from typing import Callable, List, Union, Optional -import logging -import numpy as np - -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import Statevector -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..eigensolvers.numpy_eigensolver import NumPyEigensolver -from .minimum_eigensolver import MinimumEigensolver, MinimumEigensolverResult -from ..list_or_dict import ListOrDict - -logger = logging.getLogger(__name__) - -# future type annotations not supported in type aliases in 3.8 -FilterType = Callable[[Union[List, np.ndarray], float, Optional[ListOrDict[float]]], bool] - - -class NumPyMinimumEigensolver(MinimumEigensolver): - """ - The NumPy minimum eigensolver algorithm. - """ - - def __init__( - self, - filter_criterion: FilterType | None = None, - ) -> None: - """ - Args: - filter_criterion: Callable that allows to filter eigenvalues/eigenstates. The minimum - eigensolver is only searching over feasible states and returns an eigenstate that - has the smallest eigenvalue among feasible states. The callable has the signature - ``filter(eigenstate, eigenvalue, aux_values)`` and must return a boolean to indicate - whether to consider this value or not. If there is no feasible element, the result - can even be empty. - """ - self._eigensolver = NumPyEigensolver(filter_criterion=filter_criterion) - - @property - def filter_criterion( - self, - ) -> FilterType | None: - """Returns the criterion for filtering eigenstates/eigenvalues.""" - return self._eigensolver.filter_criterion - - @filter_criterion.setter - def filter_criterion( - self, - filter_criterion: FilterType, - ) -> None: - self._eigensolver.filter_criterion = filter_criterion - - @classmethod - def supports_aux_operators(cls) -> bool: - return NumPyEigensolver.supports_aux_operators() - - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> NumPyMinimumEigensolverResult: - super().compute_minimum_eigenvalue(operator, aux_operators) - eigensolver_result = self._eigensolver.compute_eigenvalues(operator, aux_operators) - result = NumPyMinimumEigensolverResult() - if eigensolver_result.eigenvalues is not None and len(eigensolver_result.eigenvalues) > 0: - result.eigenvalue = eigensolver_result.eigenvalues[0] - result.eigenstate = eigensolver_result.eigenstates[0] - if eigensolver_result.aux_operators_evaluated: - result.aux_operators_evaluated = eigensolver_result.aux_operators_evaluated[0] - - logger.debug("NumPy minimum eigensolver result: %s", result) - - return result - - -class NumPyMinimumEigensolverResult(MinimumEigensolverResult): - """NumPy minimum eigensolver result.""" - - def __init__(self) -> None: - super().__init__() - self._eigenstate: Statevector | None = None - - @property - def eigenstate(self) -> Statevector | None: - """Returns the eigenstate corresponding to the computed minimum eigenvalue.""" - return self._eigenstate - - @eigenstate.setter - def eigenstate(self, value: Statevector) -> None: - self._eigenstate = value diff --git a/qiskit/algorithms/minimum_eigensolvers/qaoa.py b/qiskit/algorithms/minimum_eigensolvers/qaoa.py deleted file mode 100644 index 825d4fa64cc7..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/qaoa.py +++ /dev/null @@ -1,144 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""The quantum approximate optimization algorithm.""" - -from __future__ import annotations - -import warnings -from typing import Callable, Any -import numpy as np - -from qiskit.algorithms.optimizers import Minimizer, Optimizer -from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library.n_local.qaoa_ansatz import QAOAAnsatz -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseSampler -from qiskit.utils.validation import validate_min - -from .sampling_vqe import SamplingVQE - - -class QAOA(SamplingVQE): - r""" - The Quantum Approximate Optimization Algorithm (QAOA). - - QAOA is a well-known algorithm for finding approximate solutions to combinatorial-optimization - problems [1]. - - The QAOA implementation directly extends :class:`.SamplingVQE` and inherits its optimization - structure. However, unlike VQE, which can be configured with arbitrary ansatzes, QAOA uses its - own fine-tuned ansatz, which comprises :math:`p` parameterized global :math:`x` rotations and - :math:`p` different parameterizations of the problem hamiltonian. QAOA is thus principally - configured by the single integer parameter, ``reps``, which dictates the depth of the ansatz, - and thus affects the approximation quality. - - An optional array of :math:`2p` parameter values, as the :attr:`initial_point`, may be provided - as the starting :math:`\beta` and :math:`\gamma` parameters for the QAOA ansatz [1]. - - An operator or a parameterized quantum circuit may optionally also be provided as a custom - :attr:`mixer` Hamiltonian. This allows in the case of quantum annealing [2] and QAOA [3], to run - constrained optimization problems where the mixer constrains the evolution to a feasible - subspace of the full Hilbert space. - - The following attributes can be set via the initializer but can also be read and updated once - the QAOA object has been constructed. - - Attributes: - sampler (BaseSampler): The sampler primitive to sample the circuits. - optimizer (Optimizer | Minimizer): A classical optimizer to find the minimum energy. This - can either be a Qiskit :class:`.Optimizer` or a callable implementing the - :class:`.Minimizer` protocol. - reps (int): The integer parameter :math:`p`. Has a minimum valid value of 1. - initial_state: An optional initial state to prepend the QAOA circuit with. - mixer (QuantumCircuit | BaseOperator | PauliSumOp): The mixer Hamiltonian to evolve with or - a custom quantum circuit. Allows support of optimizations in constrained subspaces [2, - 3] as well as warm-starting the optimization [4]. - aggregation (float | Callable[[list[float]], float] | None): A float or callable to specify - how the objective function evaluated on the basis states should be aggregated. If a - float, this specifies the :math:`\alpha \in [0,1]` parameter for a CVaR expectation - value. - callback (Callable[[int, np.ndarray, float, dict[str, Any]], None] | None): A callback - that can access the intermediate data at each optimization step. These data are: the - evaluation count, the optimizer parameters for the ansatz, the evaluated value, the - the metadata dictionary, and the best measurement. - - References: - [1]: Farhi, E., Goldstone, J., Gutmann, S., "A Quantum Approximate Optimization Algorithm" - `arXiv:1411.4028 `__ - [2]: Hen, I., Spedalieri, F. M., "Quantum Annealing for Constrained Optimization" - `PhysRevApplied.5.034007 `__ - [3]: Hadfield, S. et al, "From the Quantum Approximate Optimization Algorithm to a Quantum - Alternating Operator Ansatz" `arXiv:1709.03489 `__ - [4]: Egger, D. J., Marecek, J., Woerner, S., "Warm-starting quantum optimization" - `arXiv: 2009.10095 `__ - """ - - def __init__( - self, - sampler: BaseSampler, - optimizer: Optimizer | Minimizer, - *, - reps: int = 1, - initial_state: QuantumCircuit | None = None, - mixer: QuantumCircuit | BaseOperator | PauliSumOp = None, - initial_point: np.ndarray | None = None, - aggregation: float | Callable[[list[float]], float] | None = None, - callback: Callable[[int, np.ndarray, float, dict[str, Any]], None] | None = None, - ) -> None: - r""" - Args: - sampler: The sampler primitive to sample the circuits. - optimizer: A classical optimizer to find the minimum energy. This can either be a - Qiskit :class:`.Optimizer` or a callable implementing the :class:`.Minimizer` - protocol. - reps: The integer parameter :math:`p`. Has a minimum valid value of 1. - initial_state: An optional initial state to prepend the QAOA circuit with. - mixer: The mixer Hamiltonian to evolve with or a custom quantum circuit. Allows support - of optimizations in constrained subspaces [2, 3] as well as warm-starting the - optimization [4]. - initial_point: An optional initial point (i.e. initial parameter values) for the - optimizer. The length of the initial point must match the number of :attr:`ansatz` - parameters. If ``None``, a random point will be generated within certain parameter - bounds. ``QAOA`` will look to the ansatz for these bounds. If the ansatz does not - specify bounds, bounds of :math:`-2\pi`, :math:`2\pi` will be used. - aggregation: A float or callable to specify how the objective function evaluated on the - basis states should be aggregated. If a float, this specifies the :math:`\alpha \in - [0,1]` parameter for a CVaR expectation value. - callback: A callback that can access the intermediate data at each optimization step. - These data are: the evaluation count, the optimizer parameters for the ansatz, the - evaluated value, the metadata dictionary. - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_min("reps", reps, 1) - - self.reps = reps - self.mixer = mixer - self.initial_state = initial_state - self._cost_operator = None - - super().__init__( - sampler=sampler, - ansatz=None, - optimizer=optimizer, - initial_point=initial_point, - aggregation=aggregation, - callback=callback, - ) - - def _check_operator_ansatz(self, operator: BaseOperator | PauliSumOp): - # Recreates a circuit based on operator parameter. - self.ansatz = QAOAAnsatz( - operator, self.reps, initial_state=self.initial_state, mixer_operator=self.mixer - ).decompose() # TODO remove decompose once #6674 is fixed diff --git a/qiskit/algorithms/minimum_eigensolvers/sampling_mes.py b/qiskit/algorithms/minimum_eigensolvers/sampling_mes.py deleted file mode 100644 index e193f53ce15c..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/sampling_mes.py +++ /dev/null @@ -1,138 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""The Sampling Minimum Eigensolver interface.""" - -from __future__ import annotations -from abc import ABC, abstractmethod -from collections.abc import Mapping -from typing import Any - -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.opflow import PauliSumOp -from qiskit.result import QuasiDistribution -from ..algorithm_result import AlgorithmResult -from ..list_or_dict import ListOrDict - - -class SamplingMinimumEigensolver(ABC): - """The Sampling Minimum Eigensolver Interface.""" - - @abstractmethod - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> "SamplingMinimumEigensolverResult": - """Compute the minimum eigenvalue of a diagonal operator. - - Args: - operator: Diagonal qubit operator. - aux_operators: Optional list of auxiliary operators to be evaluated with the - final state. - - Returns: - A :class:`~.SamplingMinimumEigensolverResult` containing the optimization result. - """ - pass - - @classmethod - def supports_aux_operators(cls) -> bool: - """Whether computing the expectation value of auxiliary operators is supported. - - If the minimum eigensolver computes an eigenstate of the main operator then it - can compute the expectation value of the aux_operators for that state. Otherwise - they will be ignored. - - Returns: - True if aux_operator expectations can be evaluated, False otherwise - """ - return False - - -class SamplingMinimumEigensolverResult(AlgorithmResult): - """Sampling Minimum Eigensolver Result. - - In contrast to the result of a :class:`~.MinimumEigenSolver`, this result also contains - the best measurement of the overall optimization and the samples of the final state. - """ - - def __init__(self) -> None: - super().__init__() - self._eigenvalue: complex | None = None - self._eigenstate: QuasiDistribution | None = None - self._aux_operator_values: ListOrDict[tuple[complex, dict[str, Any]]] | None = None - self._best_measurement: Mapping[str, Any] | None = None - - @property - def eigenvalue(self) -> complex | None: - """Return the approximation to the eigenvalue.""" - return self._eigenvalue - - @eigenvalue.setter - def eigenvalue(self, value: complex | None) -> None: - """Set the approximation to the eigenvalue.""" - self._eigenvalue = value - - @property - def eigenstate(self) -> QuasiDistribution | None: - """Return the quasi-distribution sampled from the final state. - - The ansatz is sampled when parameterized with the optimal parameters that where obtained - computing the minimum eigenvalue. The keys represent a measured classical value and the - value is a float for the quasi-probability of that result. - """ - return self._eigenstate - - @eigenstate.setter - def eigenstate(self, value: QuasiDistribution | None) -> None: - """Set the quasi-distribution sampled from the final state.""" - self._eigenstate = value - - @property - def aux_operators_evaluated(self) -> ListOrDict[tuple[complex, dict[str, Any]]] | None: - """Return aux operator expectation values and metadata. - - These are formatted as (mean, metadata). - """ - return self._aux_operator_values - - @aux_operators_evaluated.setter - def aux_operators_evaluated( - self, value: ListOrDict[tuple[complex, dict[str, Any]]] | None - ) -> None: - self._aux_operator_values = value - - @property - def best_measurement(self) -> Mapping[str, Any] | None: - """Return the best measurement over the entire optimization. - - Possesses keys: ``state``, ``bitstring``, ``value``, ``probability``. - """ - return self._best_measurement - - @best_measurement.setter - def best_measurement(self, value: Mapping[str, Any]) -> None: - """Set the best measurement over the entire optimization.""" - self._best_measurement = value - - def __str__(self) -> str: - """Return a string representation of the result.""" - disp = ( - "SamplingMinimumEigensolverResult:\n" - + f"\tEigenvalue: {self.eigenvalue}\n" - + f"\tBest measurement\n: {self.best_measurement}\n" - ) - if self.aux_operators_evaluated is not None: - disp += f"\n\tAuxiliary operator values: {self.aux_operators_evaluated}\n" - - return disp diff --git a/qiskit/algorithms/minimum_eigensolvers/sampling_vqe.py b/qiskit/algorithms/minimum_eigensolvers/sampling_vqe.py deleted file mode 100644 index 2fb60355b2a5..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/sampling_vqe.py +++ /dev/null @@ -1,382 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""The Variational Quantum Eigensolver algorithm, optimized for diagonal Hamiltonians.""" - -from __future__ import annotations - -from collections.abc import Callable, Sequence -import logging -from time import time -from typing import Any - -import numpy as np - -from qiskit.circuit import QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseSampler -from qiskit.result import QuasiDistribution -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..exceptions import AlgorithmError -from ..list_or_dict import ListOrDict -from ..optimizers import Minimizer, Optimizer, OptimizerResult -from ..variational_algorithm import VariationalAlgorithm, VariationalResult -from .diagonal_estimator import _DiagonalEstimator -from .sampling_mes import ( - SamplingMinimumEigensolver, - SamplingMinimumEigensolverResult, -) -from ..observables_evaluator import estimate_observables -from ..utils import validate_initial_point, validate_bounds - -# private function as we expect this to be updated in the next released -from ..utils.set_batching import _set_default_batchsize - - -logger = logging.getLogger(__name__) - - -class SamplingVQE(VariationalAlgorithm, SamplingMinimumEigensolver): - r"""The Variational Quantum Eigensolver algorithm, optimized for diagonal Hamiltonians. - - VQE is a hybrid quantum-classical algorithm that uses a variational technique to find the - minimum eigenvalue of a given diagonal Hamiltonian operator :math:`H_{\text{diag}}`. - - In contrast to the :class:`~qiskit.algorithms.minimum_eigensolvers.VQE` class, the - ``SamplingVQE`` algorithm is executed using a :attr:`sampler` primitive. - - An instance of ``SamplingVQE`` also requires an :attr:`ansatz`, a parameterized - :class:`.QuantumCircuit`, to prepare the trial state :math:`|\psi(\vec\theta)\rangle`. It also - needs a classical :attr:`optimizer` which varies the circuit parameters :math:`\vec\theta` to - minimize the objective function, which depends on the chosen :attr:`aggregation`. - - The optimizer can either be one of Qiskit's optimizers, such as - :class:`~qiskit.algorithms.optimizers.SPSA` or a callable with the following signature: - - .. code-block:: python - - from qiskit.algorithms.optimizers import OptimizerResult - - def my_minimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: - # Note that the callable *must* have these argument names! - # Args: - # fun (callable): the function to minimize - # x0 (np.ndarray): the initial point for the optimization - # jac (callable, optional): the gradient of the objective function - # bounds (list, optional): a list of tuples specifying the parameter bounds - - result = OptimizerResult() - result.x = # optimal parameters - result.fun = # optimal function value - return result - - The above signature also allows one to use any SciPy minimizer, for instance as - - .. code-block:: python - - from functools import partial - from scipy.optimize import minimize - - optimizer = partial(minimize, method="L-BFGS-B") - - The following attributes can be set via the initializer but can also be read and updated once - the ``SamplingVQE`` object has been constructed. - - Attributes: - sampler (BaseSampler): The sampler primitive to sample the circuits. - ansatz (QuantumCircuit): A parameterized quantum circuit to prepare the trial state. - optimizer (Optimizer | Minimizer): A classical optimizer to find the minimum energy. This - can either be a Qiskit :class:`.Optimizer` or a callable implementing the - :class:`.Minimizer` protocol. - aggregation (float | Callable[[list[tuple[float, complex]], float] | None): - A float or callable to specify how the objective function evaluated on the basis states - should be aggregated. If a float, this specifies the :math:`\alpha \in [0,1]` parameter - for a CVaR expectation value [1]. If a callable, it takes a list of basis state - measurements specified as ``[(probability, objective_value)]`` and return an objective - value as float. If None, all an ordinary expectation value is calculated. - callback (Callable[[int, np.ndarray, float, dict[str, Any]], None] | None): A callback that - can access the intermediate data at each optimization step. These data are: the - evaluation count, the optimizer parameters for the ansatz, the evaluated value, and the - metadata dictionary. - - References: - [1]: Barkoutsos, P. K., Nannicini, G., Robert, A., Tavernelli, I., and Woerner, S., - "Improving Variational Quantum Optimization using CVaR" - `arXiv:1907.04769 `_ - """ - - def __init__( - self, - sampler: BaseSampler, - ansatz: QuantumCircuit, - optimizer: Optimizer | Minimizer, - *, - initial_point: Sequence[float] | None = None, - aggregation: float | Callable[[list[float]], float] | None = None, - callback: Callable[[int, np.ndarray, float, dict[str, Any]], None] | None = None, - ) -> None: - r""" - Args: - sampler: The sampler primitive to sample the circuits. - ansatz: A parameterized quantum circuit to prepare the trial state. - optimizer: A classical optimizer to find the minimum energy. This can either be a Qiskit - :class:`.Optimizer` or a callable implementing the :class:`.Minimizer` protocol. - initial_point: An optional initial point (i.e. initial parameter values) for the - optimizer. The length of the initial point must match the number of :attr:`ansatz` - parameters. If ``None``, a random point will be generated within certain parameter - bounds. ``SamplingVQE`` will look to the ansatz for these bounds. If the ansatz does - not specify bounds, bounds of :math:`-2\pi`, :math:`2\pi` will be used. - aggregation: A float or callable to specify how the objective function evaluated on the - basis states should be aggregated. - callback: A callback that can access the intermediate data at each optimization step. - These data are: the evaluation count, the optimizer parameters for the ansatz, the - estimated value, and the metadata dictionary. - """ - super().__init__() - - self.sampler = sampler - self.ansatz = ansatz - self.optimizer = optimizer - self.aggregation = aggregation - self.callback = callback - - # this has to go via getters and setters due to the VariationalAlgorithm interface - self._initial_point = initial_point - - @property - def initial_point(self) -> Sequence[float] | None: - """Return the initial point.""" - return self._initial_point - - @initial_point.setter - def initial_point(self, value: Sequence[float] | None) -> None: - """Set the initial point.""" - self._initial_point = value - - def _check_operator_ansatz(self, operator: BaseOperator | PauliSumOp): - """Check that the number of qubits of operator and ansatz match and that the ansatz is - parameterized. - """ - if operator.num_qubits != self.ansatz.num_qubits: - try: - logger.info( - "Trying to resize ansatz to match operator on %s qubits.", operator.num_qubits - ) - self.ansatz.num_qubits = operator.num_qubits - except AttributeError as error: - raise AlgorithmError( - "The number of qubits of the ansatz does not match the " - "operator, and the ansatz does not allow setting the " - "number of qubits using `num_qubits`." - ) from error - - if self.ansatz.num_parameters == 0: - raise AlgorithmError("The ansatz must be parameterized, but has no free parameters.") - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> SamplingMinimumEigensolverResult: - # check that the number of qubits of operator and ansatz match, and resize if possible - self._check_operator_ansatz(operator) - - if len(self.ansatz.clbits) > 0: - self.ansatz.remove_final_measurements() - self.ansatz.measure_all() - - initial_point = validate_initial_point(self.initial_point, self.ansatz) - - bounds = validate_bounds(self.ansatz) - - evaluate_energy, best_measurement = self._get_evaluate_energy( - operator, self.ansatz, return_best_measurement=True - ) - - start_time = time() - - if callable(self.optimizer): - optimizer_result = self.optimizer(fun=evaluate_energy, x0=initial_point, bounds=bounds) - else: - # we always want to submit as many estimations per job as possible for minimal - # overhead on the hardware - was_updated = _set_default_batchsize(self.optimizer) - - optimizer_result = self.optimizer.minimize( - fun=evaluate_energy, x0=initial_point, bounds=bounds - ) - - # reset to original value - if was_updated: - self.optimizer.set_max_evals_grouped(None) - - optimizer_time = time() - start_time - - logger.info( - "Optimization complete in %s seconds.\nFound opt_params %s.", - optimizer_time, - optimizer_result.x, - ) - - final_state = self.sampler.run([self.ansatz], [optimizer_result.x]).result().quasi_dists[0] - - if aux_operators is not None: - aux_operators_evaluated = estimate_observables( - _DiagonalEstimator(sampler=self.sampler), - self.ansatz, - aux_operators, - optimizer_result.x, - ) - else: - aux_operators_evaluated = None - - return self._build_sampling_vqe_result( - self.ansatz.copy(), - optimizer_result, - aux_operators_evaluated, - best_measurement, - final_state, - optimizer_time, - ) - - def _get_evaluate_energy( - self, - operator: BaseOperator | PauliSumOp, - ansatz: QuantumCircuit, - return_best_measurement: bool = False, - ) -> Callable[[np.ndarray], np.ndarray | float] | tuple[ - Callable[[np.ndarray], np.ndarray | float], dict[str, Any] - ]: - """Returns a function handle to evaluate the energy at given parameters. - - This is the objective function to be passed to the optimizer that is used for evaluation. - - Args: - operator: The operator whose energy to evaluate. - ansatz: The ansatz preparing the quantum state. - return_best_measurement: If True, a handle to a dictionary containing the best - measurement evaluated with the cost function. - - Returns: - A tuple of a callable evaluating the energy and (optionally) a dictionary containing the - best measurement of the energy evaluation. - - Raises: - AlgorithmError: If the circuit is not parameterized (i.e. has 0 free parameters). - - """ - num_parameters = ansatz.num_parameters - if num_parameters == 0: - raise AlgorithmError("The ansatz must be parameterized, but has 0 free parameters.") - - # avoid creating an instance variable to remain stateless regarding results - eval_count = 0 - - best_measurement = {"best": None} - - def store_best_measurement(best): - for best_i in best: - if best_measurement["best"] is None or _compare_measurements( - best_i, best_measurement["best"] - ): - best_measurement["best"] = best_i - - estimator = _DiagonalEstimator( - sampler=self.sampler, callback=store_best_measurement, aggregation=self.aggregation - ) - - def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: - nonlocal eval_count - # handle broadcasting: ensure parameters is of shape [array, array, ...] - parameters = np.reshape(parameters, (-1, num_parameters)).tolist() - batch_size = len(parameters) - - estimator_result = estimator.run( - batch_size * [ansatz], batch_size * [operator], parameters - ).result() - values = estimator_result.values - - if self.callback is not None: - metadata = estimator_result.metadata - for params, value, meta in zip(parameters, values, metadata): - eval_count += 1 - self.callback(eval_count, params, value, meta) - - result = values if len(values) > 1 else values[0] - return np.real(result) - - if return_best_measurement: - return evaluate_energy, best_measurement - - return evaluate_energy - - def _build_sampling_vqe_result( - self, - ansatz: QuantumCircuit, - optimizer_result: OptimizerResult, - aux_operators_evaluated: ListOrDict[tuple[complex, tuple[complex, int]]], - best_measurement: dict[str, Any], - final_state: QuasiDistribution, - optimizer_time: float, - ) -> SamplingVQEResult: - result = SamplingVQEResult() - result.eigenvalue = optimizer_result.fun - result.cost_function_evals = optimizer_result.nfev - result.optimal_point = optimizer_result.x - result.optimal_parameters = dict(zip(self.ansatz.parameters, optimizer_result.x)) - result.optimal_value = optimizer_result.fun - result.optimizer_time = optimizer_time - result.aux_operators_evaluated = aux_operators_evaluated - result.optimizer_result = optimizer_result - result.best_measurement = best_measurement["best"] - result.eigenstate = final_state - result.optimal_circuit = ansatz - return result - - -class SamplingVQEResult(VariationalResult, SamplingMinimumEigensolverResult): - """VQE Result.""" - - def __init__(self) -> None: - super().__init__() - self._cost_function_evals: int | None = None - - @property - def cost_function_evals(self) -> int | None: - """Returns number of cost optimizer evaluations""" - return self._cost_function_evals - - @cost_function_evals.setter - def cost_function_evals(self, value: int) -> None: - """Sets number of cost function evaluations""" - self._cost_function_evals = value - - -def _compare_measurements(candidate, current_best): - """Compare two best measurements. Returns True if the candidate is better than current value. - - This compares the following two criteria, in this precedence: - - 1. The smaller objective value is better - 2. The higher probability for the objective value is better - - """ - if candidate["value"] < current_best["value"]: - return True - elif candidate["value"] == current_best["value"]: - return candidate["probability"] > current_best["probability"] - return False diff --git a/qiskit/algorithms/minimum_eigensolvers/vqe.py b/qiskit/algorithms/minimum_eigensolvers/vqe.py deleted file mode 100644 index f26d3687971c..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/vqe.py +++ /dev/null @@ -1,356 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""The variational quantum eigensolver algorithm.""" - -from __future__ import annotations - -import logging -from time import time -from collections.abc import Callable, Sequence -from typing import Any - -import numpy as np - -from qiskit.algorithms.gradients import BaseEstimatorGradient -from qiskit.circuit import QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..exceptions import AlgorithmError -from ..list_or_dict import ListOrDict -from ..optimizers import Optimizer, Minimizer, OptimizerResult -from ..variational_algorithm import VariationalAlgorithm, VariationalResult -from .minimum_eigensolver import MinimumEigensolver, MinimumEigensolverResult -from ..observables_evaluator import estimate_observables -from ..utils import validate_initial_point, validate_bounds - -# private function as we expect this to be updated in the next released -from ..utils.set_batching import _set_default_batchsize - -logger = logging.getLogger(__name__) - - -class VQE(VariationalAlgorithm, MinimumEigensolver): - r"""The variational quantum eigensolver (VQE) algorithm. - - VQE is a hybrid quantum-classical algorithm that uses a variational technique to find the - minimum eigenvalue of a given Hamiltonian operator :math:`H`. - - The ``VQE`` algorithm is executed using an :attr:`estimator` primitive, which computes - expectation values of operators (observables). - - An instance of ``VQE`` also requires an :attr:`ansatz`, a parameterized - :class:`.QuantumCircuit`, to prepare the trial state :math:`|\psi(\vec\theta)\rangle`. It also - needs a classical :attr:`optimizer` which varies the circuit parameters :math:`\vec\theta` such - that the expectation value of the operator on the corresponding state approaches a minimum, - - .. math:: - - \min_{\vec\theta} \langle\psi(\vec\theta)|H|\psi(\vec\theta)\rangle. - - The :attr:`estimator` is used to compute this expectation value for every optimization step. - - The optimizer can either be one of Qiskit's optimizers, such as - :class:`~qiskit.algorithms.optimizers.SPSA` or a callable with the following signature: - - .. code-block:: python - - from qiskit.algorithms.optimizers import OptimizerResult - - def my_minimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: - # Note that the callable *must* have these argument names! - # Args: - # fun (callable): the function to minimize - # x0 (np.ndarray): the initial point for the optimization - # jac (callable, optional): the gradient of the objective function - # bounds (list, optional): a list of tuples specifying the parameter bounds - - result = OptimizerResult() - result.x = # optimal parameters - result.fun = # optimal function value - return result - - The above signature also allows one to use any SciPy minimizer, for instance as - - .. code-block:: python - - from functools import partial - from scipy.optimize import minimize - - optimizer = partial(minimize, method="L-BFGS-B") - - The following attributes can be set via the initializer but can also be read and updated once - the VQE object has been constructed. - - Attributes: - estimator (BaseEstimator): The estimator primitive to compute the expectation value of the - Hamiltonian operator. - ansatz (QuantumCircuit): A parameterized quantum circuit to prepare the trial state. - optimizer (Optimizer | Minimizer): A classical optimizer to find the minimum energy. This - can either be a Qiskit :class:`.Optimizer` or a callable implementing the - :class:`.Minimizer` protocol. - gradient (BaseEstimatorGradient | None): An optional estimator gradient to be used with the - optimizer. - callback (Callable[[int, np.ndarray, float, dict[str, Any]], None] | None): A callback that - can access the intermediate data at each optimization step. These data are: the - evaluation count, the optimizer parameters for the ansatz, the evaluated mean, and the - metadata dictionary. - - References: - [1]: Peruzzo, A., et al, "A variational eigenvalue solver on a quantum processor" - `arXiv:1304.3061 `__ - """ - - def __init__( - self, - estimator: BaseEstimator, - ansatz: QuantumCircuit, - optimizer: Optimizer | Minimizer, - *, - gradient: BaseEstimatorGradient | None = None, - initial_point: Sequence[float] | None = None, - callback: Callable[[int, np.ndarray, float, dict[str, Any]], None] | None = None, - ) -> None: - r""" - Args: - estimator: The estimator primitive to compute the expectation value of the - Hamiltonian operator. - ansatz: A parameterized quantum circuit to prepare the trial state. - optimizer: A classical optimizer to find the minimum energy. This can either be a - Qiskit :class:`.Optimizer` or a callable implementing the :class:`.Minimizer` - protocol. - gradient: An optional estimator gradient to be used with the optimizer. - initial_point: An optional initial point (i.e. initial parameter values) for the - optimizer. The length of the initial point must match the number of :attr:`ansatz` - parameters. If ``None``, a random point will be generated within certain parameter - bounds. ``VQE`` will look to the ansatz for these bounds. If the ansatz does not - specify bounds, bounds of :math:`-2\pi`, :math:`2\pi` will be used. - callback: A callback that can access the intermediate data at each optimization step. - These data are: the evaluation count, the optimizer parameters for the ansatz, the - estimated value, and the metadata dictionary. - """ - super().__init__() - - self.estimator = estimator - self.ansatz = ansatz - self.optimizer = optimizer - self.gradient = gradient - # this has to go via getters and setters due to the VariationalAlgorithm interface - self.initial_point = initial_point - self.callback = callback - - @property - def initial_point(self) -> Sequence[float] | None: - return self._initial_point - - @initial_point.setter - def initial_point(self, value: Sequence[float] | None) -> None: - self._initial_point = value - - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> VQEResult: - self._check_operator_ansatz(operator) - - initial_point = validate_initial_point(self.initial_point, self.ansatz) - - bounds = validate_bounds(self.ansatz) - - start_time = time() - - evaluate_energy = self._get_evaluate_energy(self.ansatz, operator) - - if self.gradient is not None: - evaluate_gradient = self._get_evaluate_gradient(self.ansatz, operator) - else: - evaluate_gradient = None - - # perform optimization - if callable(self.optimizer): - optimizer_result = self.optimizer( - fun=evaluate_energy, x0=initial_point, jac=evaluate_gradient, bounds=bounds - ) - else: - # we always want to submit as many estimations per job as possible for minimal - # overhead on the hardware - was_updated = _set_default_batchsize(self.optimizer) - - optimizer_result = self.optimizer.minimize( - fun=evaluate_energy, x0=initial_point, jac=evaluate_gradient, bounds=bounds - ) - - # reset to original value - if was_updated: - self.optimizer.set_max_evals_grouped(None) - - optimizer_time = time() - start_time - - logger.info( - "Optimization complete in %s seconds.\nFound optimal point %s", - optimizer_time, - optimizer_result.x, - ) - - if aux_operators is not None: - aux_operators_evaluated = estimate_observables( - self.estimator, self.ansatz, aux_operators, optimizer_result.x - ) - else: - aux_operators_evaluated = None - - return self._build_vqe_result( - self.ansatz, optimizer_result, aux_operators_evaluated, optimizer_time - ) - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def _get_evaluate_energy( - self, - ansatz: QuantumCircuit, - operator: BaseOperator | PauliSumOp, - ) -> Callable[[np.ndarray], np.ndarray | float]: - """Returns a function handle to evaluate the energy at given parameters for the ansatz. - This is the objective function to be passed to the optimizer that is used for evaluation. - - Args: - ansatz: The ansatz preparing the quantum state. - operator: The operator whose energy to evaluate. - - Returns: - A callable that computes and returns the energy of the hamiltonian of each parameter. - - Raises: - AlgorithmError: If the primitive job to evaluate the energy fails. - """ - num_parameters = ansatz.num_parameters - - # avoid creating an instance variable to remain stateless regarding results - eval_count = 0 - - def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: - nonlocal eval_count - - # handle broadcasting: ensure parameters is of shape [array, array, ...] - parameters = np.reshape(parameters, (-1, num_parameters)).tolist() - batch_size = len(parameters) - - try: - job = self.estimator.run(batch_size * [ansatz], batch_size * [operator], parameters) - estimator_result = job.result() - except Exception as exc: - raise AlgorithmError("The primitive job to evaluate the energy failed!") from exc - - values = estimator_result.values - - if self.callback is not None: - metadata = estimator_result.metadata - for params, value, meta in zip(parameters, values, metadata): - eval_count += 1 - self.callback(eval_count, params, value, meta) - - energy = values[0] if len(values) == 1 else values - - return energy - - return evaluate_energy - - def _get_evaluate_gradient( - self, - ansatz: QuantumCircuit, - operator: BaseOperator | PauliSumOp, - ) -> Callable[[np.ndarray], np.ndarray]: - """Get a function handle to evaluate the gradient at given parameters for the ansatz. - - Args: - ansatz: The ansatz preparing the quantum state. - operator: The operator whose energy to evaluate. - - Returns: - A function handle to evaluate the gradient at given parameters for the ansatz. - - Raises: - AlgorithmError: If the primitive job to evaluate the gradient fails. - """ - - def evaluate_gradient(parameters: np.ndarray) -> np.ndarray: - # broadcasting not required for the estimator gradients - try: - job = self.gradient.run([ansatz], [operator], [parameters]) - gradients = job.result().gradients - except Exception as exc: - raise AlgorithmError("The primitive job to evaluate the gradient failed!") from exc - - return gradients[0] - - return evaluate_gradient - - def _check_operator_ansatz(self, operator: BaseOperator | PauliSumOp): - """Check that the number of qubits of operator and ansatz match and that the ansatz is - parameterized. - """ - if operator.num_qubits != self.ansatz.num_qubits: - try: - logger.info( - "Trying to resize ansatz to match operator on %s qubits.", operator.num_qubits - ) - self.ansatz.num_qubits = operator.num_qubits - except AttributeError as error: - raise AlgorithmError( - "The number of qubits of the ansatz does not match the " - "operator, and the ansatz does not allow setting the " - "number of qubits using `num_qubits`." - ) from error - - if self.ansatz.num_parameters == 0: - raise AlgorithmError("The ansatz must be parameterized, but has no free parameters.") - - def _build_vqe_result( - self, - ansatz: QuantumCircuit, - optimizer_result: OptimizerResult, - aux_operators_evaluated: ListOrDict[tuple[complex, tuple[complex, int]]], - optimizer_time: float, - ) -> VQEResult: - result = VQEResult() - result.optimal_circuit = ansatz.copy() - result.eigenvalue = optimizer_result.fun - result.cost_function_evals = optimizer_result.nfev - result.optimal_point = optimizer_result.x - result.optimal_parameters = dict(zip(self.ansatz.parameters, optimizer_result.x)) - result.optimal_value = optimizer_result.fun - result.optimizer_time = optimizer_time - result.aux_operators_evaluated = aux_operators_evaluated - result.optimizer_result = optimizer_result - return result - - -class VQEResult(VariationalResult, MinimumEigensolverResult): - """Variational quantum eigensolver result.""" - - def __init__(self) -> None: - super().__init__() - self._cost_function_evals: int | None = None - - @property - def cost_function_evals(self) -> int | None: - """The number of cost optimizer evaluations.""" - return self._cost_function_evals - - @cost_function_evals.setter - def cost_function_evals(self, value: int) -> None: - self._cost_function_evals = value diff --git a/qiskit/algorithms/observables_evaluator.py b/qiskit/algorithms/observables_evaluator.py deleted file mode 100644 index 6d40239e229e..000000000000 --- a/qiskit/algorithms/observables_evaluator.py +++ /dev/null @@ -1,126 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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. - -"""Evaluator of observables for algorithms.""" - -from __future__ import annotations -from collections.abc import Sequence -from typing import Any - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import SparsePauliOp -from .exceptions import AlgorithmError -from .list_or_dict import ListOrDict -from ..primitives import BaseEstimator -from ..quantum_info.operators.base_operator import BaseOperator - - -def estimate_observables( - estimator: BaseEstimator, - quantum_state: QuantumCircuit, - observables: ListOrDict[BaseOperator | PauliSumOp], - parameter_values: Sequence[float] | None = None, - threshold: float = 1e-12, -) -> ListOrDict[tuple[complex, dict[str, Any]]]: - """ - Accepts a sequence of operators and calculates their expectation values - means - and metadata. They are calculated with respect to a quantum state provided. A user - can optionally provide a threshold value which filters mean values falling below the threshold. - - Args: - estimator: An estimator primitive used for calculations. - quantum_state: A (parameterized) quantum circuit preparing a quantum state that expectation - values are computed against. - observables: A list or a dictionary of operators whose expectation values are to be - calculated. - parameter_values: Optional list of parameters values to evaluate the quantum circuit on. - threshold: A threshold value that defines which mean values should be neglected (helpful for - ignoring numerical instabilities close to 0). - - Returns: - A list or a dictionary of tuples (mean, metadata). - - Raises: - AlgorithmError: If a primitive job is not successful. - """ - - if isinstance(observables, dict): - observables_list = list(observables.values()) - else: - observables_list = observables - - if len(observables_list) > 0: - observables_list = _handle_zero_ops(observables_list) - quantum_state = [quantum_state] * len(observables) - if parameter_values is not None: - parameter_values = [parameter_values] * len(observables) - try: - estimator_job = estimator.run(quantum_state, observables_list, parameter_values) - expectation_values = estimator_job.result().values - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - - metadata = estimator_job.result().metadata - # Discard values below threshold - observables_means = expectation_values * (np.abs(expectation_values) > threshold) - # zip means and metadata into tuples - observables_results = list(zip(observables_means, metadata)) - else: - observables_results = [] - - return _prepare_result(observables_results, observables) - - -def _handle_zero_ops( - observables_list: list[BaseOperator | PauliSumOp], -) -> list[BaseOperator | PauliSumOp]: - """Replaces all occurrence of operators equal to 0 in the list with an equivalent ``PauliSumOp`` - operator.""" - if observables_list: - zero_op = SparsePauliOp.from_list([("I" * observables_list[0].num_qubits, 0)]) - for ind, observable in enumerate(observables_list): - if observable == 0: - observables_list[ind] = zero_op - return observables_list - - -def _prepare_result( - observables_results: list[tuple[complex, dict]], - observables: ListOrDict[BaseOperator | PauliSumOp], -) -> ListOrDict[tuple[complex, dict[str, Any]]]: - """ - Prepares a list of tuples of eigenvalues and metadata tuples from - ``observables_results`` and ``observables``. - - Args: - observables_results: A list of tuples (mean, metadata). - observables: A list or a dictionary of operators whose expectation values are to be - calculated. - - Returns: - A list or a dictionary of tuples (mean, metadata). - """ - - if isinstance(observables, list): - # by construction, all None values will be overwritten - observables_eigenvalues: ListOrDict[tuple[complex, complex]] = [None] * len(observables) - key_value_iterator = enumerate(observables_results) - else: - observables_eigenvalues = {} - key_value_iterator = zip(observables.keys(), observables_results) - - for key, value in key_value_iterator: - observables_eigenvalues[key] = value - return observables_eigenvalues diff --git a/qiskit/algorithms/optimizers/__init__.py b/qiskit/algorithms/optimizers/__init__.py deleted file mode 100644 index 11bf73d1fcaf..000000000000 --- a/qiskit/algorithms/optimizers/__init__.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -""" -Optimizers (:mod:`qiskit.algorithms.optimizers`) -===================================================== -It contains a variety of classical optimizers for use by quantum variational algorithms, -such as :class:`~qiskit.algorithms.VQE`. -Logically, these optimizers can be divided into two categories: - -`Local Optimizers`_ - Given an optimization problem, a **local optimizer** is a function - that attempts to find an optimal value within the neighboring set of a candidate solution. - -`Global Optimizers`_ - Given an optimization problem, a **global optimizer** is a function - that attempts to find an optimal value among all possible solutions. - -.. currentmodule:: qiskit.algorithms.optimizers - -Optimizer Base Class -==================== - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - OptimizerResult - OptimizerSupportLevel - Optimizer - Minimizer - -Steppable Optimizer Base Class -============================== - -.. autosummary:: - :toctree: ../stubs/ - - optimizer_utils - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - SteppableOptimizer - AskData - TellData - OptimizerState - - - -Local Optimizers -================ - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - ADAM - AQGD - CG - COBYLA - L_BFGS_B - GSLS - GradientDescent - GradientDescentState - NELDER_MEAD - NFT - P_BFGS - POWELL - SLSQP - SPSA - QNSPSA - TNC - SciPyOptimizer - UMDA - -Qiskit also provides the following optimizers, which are built-out using the optimizers from -the `scikit-quant` package. The `scikit-quant` package is not installed by default but must be -explicitly installed, if desired, by the user - the optimizers therein are provided under various -licenses so it has been made an optional install for the end user to choose whether to do so or -not. To install the `scikit-quant` dependent package you can use -`pip install scikit-quant`. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - BOBYQA - IMFIL - SNOBFIT - -Global Optimizers -================= -The global optimizers here all use NLopt for their core function and can only be -used if their dependent NLopt package is manually installed. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - CRS - DIRECT_L - DIRECT_L_RAND - ESCH - ISRES - -""" - -from .adam_amsgrad import ADAM -from .aqgd import AQGD -from .bobyqa import BOBYQA -from .cg import CG -from .cobyla import COBYLA -from .gsls import GSLS -from .gradient_descent import GradientDescent, GradientDescentState -from .imfil import IMFIL -from .l_bfgs_b import L_BFGS_B -from .nelder_mead import NELDER_MEAD -from .nft import NFT -from .nlopts.crs import CRS -from .nlopts.direct_l import DIRECT_L -from .nlopts.direct_l_rand import DIRECT_L_RAND -from .nlopts.esch import ESCH -from .nlopts.isres import ISRES -from .steppable_optimizer import SteppableOptimizer, AskData, TellData, OptimizerState -from .optimizer import Minimizer, Optimizer, OptimizerResult, OptimizerSupportLevel -from .p_bfgs import P_BFGS -from .powell import POWELL -from .qnspsa import QNSPSA -from .scipy_optimizer import SciPyOptimizer -from .slsqp import SLSQP -from .snobfit import SNOBFIT -from .spsa import SPSA -from .tnc import TNC -from .umda import UMDA - -__all__ = [ - "Optimizer", - "OptimizerSupportLevel", - "SteppableOptimizer", - "AskData", - "TellData", - "OptimizerState", - "OptimizerResult", - "Minimizer", - "ADAM", - "AQGD", - "CG", - "COBYLA", - "GSLS", - "GradientDescent", - "GradientDescentState", - "L_BFGS_B", - "NELDER_MEAD", - "NFT", - "P_BFGS", - "POWELL", - "SciPyOptimizer", - "SLSQP", - "SPSA", - "QNSPSA", - "TNC", - "CRS", - "DIRECT_L", - "DIRECT_L_RAND", - "ESCH", - "ISRES", - "SNOBFIT", - "BOBYQA", - "IMFIL", - "UMDA", -] diff --git a/qiskit/algorithms/optimizers/adam_amsgrad.py b/qiskit/algorithms/optimizers/adam_amsgrad.py deleted file mode 100644 index 422aa17a5f01..000000000000 --- a/qiskit/algorithms/optimizers/adam_amsgrad.py +++ /dev/null @@ -1,270 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2021. -# -# 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. - -"""The Adam and AMSGRAD optimizers.""" -from __future__ import annotations - -from collections.abc import Callable -from typing import Any -import os - -import csv -import numpy as np -from qiskit.utils.deprecation import deprecate_arg -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - -# pylint: disable=invalid-name - - -class ADAM(Optimizer): - """Adam and AMSGRAD optimizers. - - Adam [1] is a gradient-based optimization algorithm that is relies on adaptive estimates of - lower-order moments. The algorithm requires little memory and is invariant to diagonal - rescaling of the gradients. Furthermore, it is able to cope with non-stationary objective - functions and noisy and/or sparse gradients. - - AMSGRAD [2] (a variant of Adam) uses a 'long-term memory' of past gradients and, thereby, - improves convergence properties. - - References: - - [1]: Kingma, Diederik & Ba, Jimmy (2014), Adam: A Method for Stochastic Optimization. - `arXiv:1412.6980 `_ - - [2]: Sashank J. Reddi and Satyen Kale and Sanjiv Kumar (2018), - On the Convergence of Adam and Beyond. - `arXiv:1904.09237 `_ - - .. note:: - - This component has some function that is normally random. If you want to reproduce behavior - then you should set the random number generator seed in the algorithm_globals - (``qiskit.utils.algorithm_globals.random_seed = seed``). - - """ - - _OPTIONS = [ - "maxiter", - "tol", - "lr", - "beta_1", - "beta_2", - "noise_factor", - "eps", - "amsgrad", - "snapshot_dir", - ] - - def __init__( - self, - maxiter: int = 10000, - tol: float = 1e-6, - lr: float = 1e-3, - beta_1: float = 0.9, - beta_2: float = 0.99, - noise_factor: float = 1e-8, - eps: float = 1e-10, - amsgrad: bool = False, - snapshot_dir: str | None = None, - ) -> None: - """ - Args: - maxiter: Maximum number of iterations - tol: Tolerance for termination - lr: Value >= 0, Learning rate. - beta_1: Value in range 0 to 1, Generally close to 1. - beta_2: Value in range 0 to 1, Generally close to 1. - noise_factor: Value >= 0, Noise factor - eps : Value >=0, Epsilon to be used for finite differences if no analytic - gradient method is given. - amsgrad: True to use AMSGRAD, False if not - snapshot_dir: If not None save the optimizer's parameter - after every step to the given directory - """ - super().__init__() - for k, v in list(locals().items()): - if k in self._OPTIONS: - self._options[k] = v - self._maxiter = maxiter - self._snapshot_dir = snapshot_dir - self._tol = tol - self._lr = lr - self._beta_1 = beta_1 - self._beta_2 = beta_2 - self._noise_factor = noise_factor - self._eps = eps - self._amsgrad = amsgrad - - # runtime variables - self._t = 0 # time steps - self._m = np.zeros(1) - self._v = np.zeros(1) - if self._amsgrad: - self._v_eff = np.zeros(1) - - if self._snapshot_dir: - - with open(os.path.join(self._snapshot_dir, "adam_params.csv"), mode="w") as csv_file: - if self._amsgrad: - fieldnames = ["v", "v_eff", "m", "t"] - else: - fieldnames = ["v", "m", "t"] - writer = csv.DictWriter(csv_file, fieldnames=fieldnames) - writer.writeheader() - - @property - def settings(self) -> dict[str, Any]: - return { - "maxiter": self._maxiter, - "tol": self._tol, - "lr": self._lr, - "beta_1": self._beta_1, - "beta_2": self._beta_2, - "noise_factor": self._noise_factor, - "eps": self._eps, - "amsgrad": self._amsgrad, - "snapshot_dir": self._snapshot_dir, - } - - def get_support_level(self): - """Return support level dictionary""" - return { - "gradient": OptimizerSupportLevel.supported, - "bounds": OptimizerSupportLevel.ignored, - "initial_point": OptimizerSupportLevel.supported, - } - - def save_params(self, snapshot_dir: str) -> None: - """Save the current iteration parameters to a file called ``adam_params.csv``. - - Note: - - The current parameters are appended to the file, if it exists already. - The file is not overwritten. - - Args: - snapshot_dir: The directory to store the file in. - """ - if self._amsgrad: - with open(os.path.join(snapshot_dir, "adam_params.csv"), mode="a") as csv_file: - fieldnames = ["v", "v_eff", "m", "t"] - writer = csv.DictWriter(csv_file, fieldnames=fieldnames) - writer.writerow({"v": self._v, "v_eff": self._v_eff, "m": self._m, "t": self._t}) - else: - with open(os.path.join(snapshot_dir, "adam_params.csv"), mode="a") as csv_file: - fieldnames = ["v", "m", "t"] - writer = csv.DictWriter(csv_file, fieldnames=fieldnames) - writer.writerow({"v": self._v, "m": self._m, "t": self._t}) - - def load_params(self, load_dir: str) -> None: - """Load iteration parameters for a file called ``adam_params.csv``. - - Args: - load_dir: The directory containing ``adam_params.csv``. - """ - with open(os.path.join(load_dir, "adam_params.csv")) as csv_file: - if self._amsgrad: - fieldnames = ["v", "v_eff", "m", "t"] - else: - fieldnames = ["v", "m", "t"] - reader = csv.DictReader(csv_file, fieldnames=fieldnames) - for line in reader: - v = line["v"] - if self._amsgrad: - v_eff = line["v_eff"] - m = line["m"] - t = line["t"] - - v = v[1:-1] - self._v = np.fromstring(v, dtype=float, sep=" ") - if self._amsgrad: - v_eff = v_eff[1:-1] - self._v_eff = np.fromstring(v_eff, dtype=float, sep=" ") - m = m[1:-1] - self._m = np.fromstring(m, dtype=float, sep=" ") - t = t[1:-1] - self._t = np.fromstring(t, dtype=int, sep=" ") - - @deprecate_arg("objective_function", new_alias="fun", since="0.19.0") - @deprecate_arg("initial_point", new_alias="fun", since="0.19.0") - @deprecate_arg("gradient_function", new_alias="jac", since="0.19.0") - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - # pylint:disable=unused-argument - objective_function: Callable[[np.ndarray], float] | None = None, - initial_point: np.ndarray | None = None, - gradient_function: Callable[[np.ndarray], float] | None = None, - # ) -> Tuple[np.ndarray, float, int]: - ) -> OptimizerResult: # TODO find proper way to deprecate return type - """Minimize the scalar function. - - Args: - fun: The scalar function to minimize. - x0: The initial point for the minimization. - jac: The gradient of the scalar function ``fun``. - bounds: Bounds for the variables of ``fun``. This argument might be ignored if the - optimizer does not support bounds. - objective_function: DEPRECATED. A function handle to the objective function. - initial_point: DEPRECATED. The initial iteration point. - gradient_function: DEPRECATED. A function handle to the gradient of the objective - function. - - Returns: - The result of the optimization, containing e.g. the result as attribute ``x``. - """ - if jac is None: - jac = Optimizer.wrap_function(Optimizer.gradient_num_diff, (fun, self._eps)) - - derivative = jac(x0) - self._t = 0 - self._m = np.zeros(np.shape(derivative)) - self._v = np.zeros(np.shape(derivative)) - if self._amsgrad: - self._v_eff = np.zeros(np.shape(derivative)) - - params = params_new = x0 - while self._t < self._maxiter: - if self._t > 0: - derivative = jac(params) - self._t += 1 - self._m = self._beta_1 * self._m + (1 - self._beta_1) * derivative - self._v = self._beta_2 * self._v + (1 - self._beta_2) * derivative * derivative - lr_eff = self._lr * np.sqrt(1 - self._beta_2**self._t) / (1 - self._beta_1**self._t) - if not self._amsgrad: - params_new = params - lr_eff * self._m.flatten() / ( - np.sqrt(self._v.flatten()) + self._noise_factor - ) - else: - self._v_eff = np.maximum(self._v_eff, self._v) - params_new = params - lr_eff * self._m.flatten() / ( - np.sqrt(self._v_eff.flatten()) + self._noise_factor - ) - - if self._snapshot_dir: - self.save_params(self._snapshot_dir) - - # check termination - if np.linalg.norm(params - params_new) < self._tol: - break - - params = params_new - - result = OptimizerResult() - result.x = params_new - result.fun = fun(params_new) - result.nfev = self._t - return result diff --git a/qiskit/algorithms/optimizers/aqgd.py b/qiskit/algorithms/optimizers/aqgd.py deleted file mode 100644 index ad8ad42ae9f1..000000000000 --- a/qiskit/algorithms/optimizers/aqgd.py +++ /dev/null @@ -1,367 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2021. -# -# 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. - -"""Analytical Quantum Gradient Descent (AQGD) optimizer.""" - -from __future__ import annotations -import logging -from collections.abc import Callable -from typing import Any -import warnings - -import numpy as np -from qiskit.utils.validation import validate_range_exclusive_max -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT -from ..exceptions import AlgorithmError - -logger = logging.getLogger(__name__) - - -class AQGD(Optimizer): - """Analytic Quantum Gradient Descent (AQGD) with Epochs optimizer. - Performs gradient descent optimization with a momentum term, analytic gradients, - and customized step length schedule for parameterized quantum gates, i.e. - Pauli Rotations. See, for example: - - * K. Mitarai, M. Negoro, M. Kitagawa, and K. Fujii. (2018). - Quantum circuit learning. Phys. Rev. A 98, 032309. - https://arxiv.org/abs/1803.00745 - - * Maria Schuld, Ville Bergholm, Christian Gogolin, Josh Izaac, Nathan Killoran. (2019). - Evaluating analytic gradients on quantum hardware. Phys. Rev. A 99, 032331. - https://arxiv.org/abs/1811.11184 - - for further details on analytic gradients of parameterized quantum gates. - - Gradients are computed "analytically" using the quantum circuit when evaluating - the objective function. - - """ - - _OPTIONS = ["maxiter", "eta", "tol", "disp", "momentum", "param_tol", "averaging"] - - def __init__( - self, - maxiter: int | list[int] = 1000, - eta: float | list[float] = 1.0, - tol: float = 1e-6, # this is tol - momentum: float | list[float] = 0.25, - param_tol: float = 1e-6, - averaging: int = 10, - ) -> None: - """ - Performs Analytical Quantum Gradient Descent (AQGD) with Epochs. - - Args: - maxiter: Maximum number of iterations (full gradient steps) - eta: The coefficient of the gradient update. Increasing this value - results in larger step sizes: param = previous_param - eta * deriv - tol: Tolerance for change in windowed average of objective values. - Convergence occurs when either objective tolerance is met OR parameter - tolerance is met. - momentum: Bias towards the previous gradient momentum in current - update. Must be within the bounds: [0,1) - param_tol: Tolerance for change in norm of parameters. - averaging: Length of window over which to average objective values for objective - convergence criterion - - Raises: - AlgorithmError: If the length of ``maxiter``, `momentum``, and ``eta`` is not the same. - """ - super().__init__() - if isinstance(maxiter, int): - maxiter = [maxiter] - if isinstance(eta, (int, float)): - eta = [eta] - if isinstance(momentum, (int, float)): - momentum = [momentum] - if len(maxiter) != len(eta) or len(maxiter) != len(momentum): - raise AlgorithmError( - "AQGD input parameter length mismatch. Parameters `maxiter`, " - "`eta`, and `momentum` must have the same length." - ) - for m in momentum: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_range_exclusive_max("momentum", m, 0, 1) - - self._eta = eta - self._maxiter = maxiter - self._momenta_coeff = momentum - self._param_tol = param_tol - self._tol = tol - self._averaging = averaging - - # state - self._avg_objval: float | None = None - self._prev_param: np.ndarray | None = None - self._eval_count = 0 # function evaluations - self._prev_loss: list[float] = [] - self._prev_grad: list[list[float]] = [] - - def get_support_level(self) -> dict[str, OptimizerSupportLevel]: - """Support level dictionary - - Returns: - Dict[str, int]: gradient, bounds and initial point - support information that is ignored/required. - """ - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.ignored, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self) -> dict[str, Any]: - return { - "maxiter": self._maxiter, - "eta": self._eta, - "momentum": self._momenta_coeff, - "param_tol": self._param_tol, - "tol": self._tol, - "averaging": self._averaging, - } - - def _compute_objective_fn_and_gradient( - self, params: np.ndarray | list[float], obj: Callable - ) -> tuple[float, np.ndarray]: - """ - Obtains the objective function value for params and the analytical quantum derivatives of - the objective function with respect to each parameter. Requires - 2*(number parameters) + 1 objective evaluations - - Args: - params: Current value of the parameters to evaluate the objective function - obj: Objective function of interest - - Returns: - Tuple containing the objective value and array of gradients for the given parameter set. - """ - num_params = len(params) - param_sets_to_eval = params + np.concatenate( - ( - np.zeros((1, num_params)), # copy of the parameters as is - np.eye(num_params) * np.pi / 2, # copy of the parameters with the positive shift - -np.eye(num_params) * np.pi / 2, - ), # copy of the parameters with the negative shift - axis=0, - ) - # Evaluate, - # reshaping to flatten, as expected by objective function - values = np.array(obj(param_sets_to_eval.reshape(-1))) - - # Update number of objective function evaluations - self._eval_count += 2 * num_params + 1 - - # return the objective function value - obj_value = values[0] - - # return the gradient values - gradient = 0.5 * (values[1 : num_params + 1] - values[1 + num_params :]) - return obj_value, gradient - - def _update( - self, - params: np.ndarray, - gradient: np.ndarray, - mprev: np.ndarray, - step_size: float, - momentum_coeff: float, - ) -> tuple[np.ndarray, np.ndarray]: - """ - Updates full parameter array based on a step that is a convex - combination of the gradient and previous momentum - - Args: - params: Current value of the parameters to evaluate the objective function at - gradient: Gradient of objective wrt parameters - mprev: Momentum vector for each parameter - step_size: The scaling of step to take - momentum_coeff: Bias towards previous momentum vector when updating current - momentum/step vector - - Returns: - Tuple of the updated parameter and momentum vectors respectively. - """ - # Momentum update: - # Convex combination of previous momentum and current gradient estimate - mnew = (1 - momentum_coeff) * gradient + momentum_coeff * mprev - params -= step_size * mnew - return params, mnew - - def _converged_objective(self, objval: float, tol: float, window_size: int) -> bool: - """ - Tests convergence based on the change in a moving windowed average of past objective values - - Args: - objval: Current value of the objective function - tol: tolerance below which (average) objective function change must be - window_size: size of averaging window - - Returns: - Bool indicating whether or not the optimization has converged. - """ - # If we haven't reached the required window length, - # append the current value, but we haven't converged - if len(self._prev_loss) < window_size: - self._prev_loss.append(objval) - return False - - # Update last value in list with current value - self._prev_loss.append(objval) - # (length now = n+1) - - # Calculate previous windowed average - # and current windowed average of objective values - prev_avg = np.mean(self._prev_loss[:window_size]) - curr_avg = np.mean(self._prev_loss[1 : window_size + 1]) - self._avg_objval = curr_avg - - # Update window of objective values - # (Remove earliest value) - self._prev_loss.pop(0) - - if np.absolute(prev_avg - curr_avg) < tol: - # converged - logger.info("Previous obj avg: %f\nCurr obj avg: %f", prev_avg, curr_avg) - return True - return False - - def _converged_parameter(self, parameter: np.ndarray, tol: float) -> bool: - """ - Tests convergence based on change in parameter - - Args: - parameter: current parameter values - tol: tolerance for change in norm of parameters - - Returns: - Bool indicating whether or not the optimization has converged - """ - if self._prev_param is None: - self._prev_param = np.copy(parameter) - return False - - order = np.inf - p_change = np.linalg.norm(self._prev_param - parameter, ord=order) - if p_change < tol: - # converged - logger.info("Change in parameters (%f norm): %f", order, p_change) - return True - return False - - def _converged_alt(self, gradient: list[float], tol: float, window_size: int) -> bool: - """ - Tests convergence from norm of windowed average of gradients - - Args: - gradient: current gradient - tol: tolerance for average gradient norm - window_size: size of averaging window - - Returns: - Bool indicating whether or not the optimization has converged - """ - # If we haven't reached the required window length, - # append the current value, but we haven't converged - if len(self._prev_grad) < window_size - 1: - self._prev_grad.append(gradient) - return False - - # Update last value in list with current value - self._prev_grad.append(gradient) - # (length now = n) - - # Calculate previous windowed average - # and current windowed average of objective values - avg_grad = np.mean(self._prev_grad, axis=0) - - # Update window of values - # (Remove earliest value) - self._prev_grad.pop(0) - - if np.linalg.norm(avg_grad, ord=np.inf) < tol: - # converged - logger.info("Avg. grad. norm: %f", np.linalg.norm(avg_grad, ord=np.inf)) - return True - return False - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - params = np.asarray(x0) - momentum = np.zeros(shape=(params.size,)) - # empty out history of previous objectives/gradients/parameters - # (in case this object is re-used) - self._prev_loss = [] - self._prev_grad = [] - self._prev_param = None - self._eval_count = 0 # function evaluations - - iter_count = 0 - logger.info("Initial Params: %s", params) - - epoch = 0 - converged = False - for (eta, mom_coeff) in zip(self._eta, self._momenta_coeff): - logger.info("Epoch: %4d | Stepsize: %6.4f | Momentum: %6.4f", epoch, eta, mom_coeff) - - sum_max_iters = sum(self._maxiter[0 : epoch + 1]) - while iter_count < sum_max_iters: - # update the iteration count - iter_count += 1 - - # Check for parameter convergence before potentially costly function evaluation - converged = self._converged_parameter(params, self._param_tol) - if converged: - break - - # Calculate objective function and estimate of analytical gradient - if jac is None: - objval, gradient = self._compute_objective_fn_and_gradient(params, fun) - else: - objval = fun(params) - gradient = jac(params) - - logger.info( - " Iter: %4d | Obj: %11.6f | Grad Norm: %f", - iter_count, - objval, - np.linalg.norm(gradient, ord=np.inf), - ) - - # Check for objective convergence - converged = self._converged_objective(objval, self._tol, self._averaging) - if converged: - break - - # Update parameters and momentum - params, momentum = self._update(params, gradient, momentum, eta, mom_coeff) - # end inner iteration - # if converged, end iterating over epochs - if converged: - break - epoch += 1 - # end epoch iteration - - result = OptimizerResult() - result.x = params - result.fun = objval - result.nfev = self._eval_count - result.nit = iter_count - - return result diff --git a/qiskit/algorithms/optimizers/bobyqa.py b/qiskit/algorithms/optimizers/bobyqa.py deleted file mode 100644 index 39250aef917a..000000000000 --- a/qiskit/algorithms/optimizers/bobyqa.py +++ /dev/null @@ -1,84 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""Bound Optimization BY Quadratic Approximation (BOBYQA) optimizer.""" - -from __future__ import annotations - -from collections.abc import Callable -from typing import Any - -import numpy as np -from qiskit.utils import optionals as _optionals -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - - -@_optionals.HAS_SKQUANT.require_in_instance -class BOBYQA(Optimizer): - """Bound Optimization BY Quadratic Approximation algorithm. - - BOBYQA finds local solutions to nonlinear, non-convex minimization problems with optional - bound constraints, without requirement of derivatives of the objective function. - - Uses skquant.opt installed with pip install scikit-quant. - For further detail, please refer to - https://github.com/scikit-quant/scikit-quant and https://qat4chem.lbl.gov/software. - """ - - def __init__( - self, - maxiter: int = 1000, - ) -> None: - """ - Args: - maxiter: Maximum number of function evaluations. - - Raises: - MissingOptionalLibraryError: scikit-quant not installed - """ - super().__init__() - self._maxiter = maxiter - - def get_support_level(self): - """Returns support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.required, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self) -> dict[str, Any]: - return {"maxiter": self._maxiter} - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - from skquant import opt as skq - - res, history = skq.minimize( - func=fun, - x0=np.asarray(x0), - bounds=np.array(bounds), - budget=self._maxiter, - method="bobyqa", - ) - - optimizer_result = OptimizerResult() - optimizer_result.x = res.optpar - optimizer_result.fun = res.optval - optimizer_result.nfev = len(history) - return optimizer_result diff --git a/qiskit/algorithms/optimizers/cg.py b/qiskit/algorithms/optimizers/cg.py deleted file mode 100644 index 670b4ac33868..000000000000 --- a/qiskit/algorithms/optimizers/cg.py +++ /dev/null @@ -1,70 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Conjugate Gradient optimizer.""" - -from __future__ import annotations - -from .scipy_optimizer import SciPyOptimizer - - -class CG(SciPyOptimizer): - """Conjugate Gradient optimizer. - - CG is an algorithm for the numerical solution of systems of linear equations whose matrices are - symmetric and positive-definite. It is an *iterative algorithm* in that it uses an initial - guess to generate a sequence of improving approximate solutions for a problem, - in which each approximation is derived from the previous ones. It is often used to solve - unconstrained optimization problems, such as energy minimization. - - Uses scipy.optimize.minimize CG. - For further detail, please refer to - https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "disp", "gtol", "eps"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int = 20, - disp: bool = False, - gtol: float = 1e-5, - tol: float | None = None, - eps: float = 1.4901161193847656e-08, - options: dict | None = None, - max_evals_grouped: int = 1, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum number of iterations to perform. - disp: Set to True to print convergence messages. - gtol: Gradient norm must be less than gtol before successful termination. - tol: Tolerance for termination. - eps: If jac is approximated, use this value for the step size. - options: A dictionary of solver options. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__( - method="CG", - options=options, - tol=tol, - max_evals_grouped=max_evals_grouped, - **kwargs, - ) diff --git a/qiskit/algorithms/optimizers/cobyla.py b/qiskit/algorithms/optimizers/cobyla.py deleted file mode 100644 index 72a0938379e7..000000000000 --- a/qiskit/algorithms/optimizers/cobyla.py +++ /dev/null @@ -1,59 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Constrained Optimization By Linear Approximation optimizer.""" - -from __future__ import annotations - -from .scipy_optimizer import SciPyOptimizer - - -class COBYLA(SciPyOptimizer): - """ - Constrained Optimization By Linear Approximation optimizer. - - COBYLA is a numerical optimization method for constrained problems - where the derivative of the objective function is not known. - - Uses scipy.optimize.minimize COBYLA. - For further detail, please refer to - https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "disp", "rhobeg"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int = 1000, - disp: bool = False, - rhobeg: float = 1.0, - tol: float | None = None, - options: dict | None = None, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum number of function evaluations. - disp: Set to True to print convergence messages. - rhobeg: Reasonable initial changes to the variables. - tol: Final accuracy in the optimization (not precisely guaranteed). - This is a lower bound on the size of the trust region. - options: A dictionary of solver options. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__(method="COBYLA", options=options, tol=tol, **kwargs) diff --git a/qiskit/algorithms/optimizers/gradient_descent.py b/qiskit/algorithms/optimizers/gradient_descent.py deleted file mode 100644 index 03911c21c012..000000000000 --- a/qiskit/algorithms/optimizers/gradient_descent.py +++ /dev/null @@ -1,401 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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 standard gradient descent optimizer.""" -from __future__ import annotations - -from collections.abc import Generator -from dataclasses import dataclass, field -from typing import Any, Callable, SupportsFloat -import numpy as np -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT -from .steppable_optimizer import AskData, TellData, OptimizerState, SteppableOptimizer -from .optimizer_utils import LearningRate - -CALLBACK = Callable[[int, np.ndarray, float, SupportsFloat], None] - - -@dataclass -class GradientDescentState(OptimizerState): - """State of :class:`~.GradientDescent`. - - Dataclass with all the information of an optimizer plus the learning_rate and the stepsize. - """ - - stepsize: float | None - """Norm of the gradient on the last step.""" - - learning_rate: LearningRate = field(compare=False) - """Learning rate at the current step of the optimization process. - - It behaves like a generator, (use ``next(learning_rate)`` to get the learning rate for the - next step) but it can also return the current learning rate with ``learning_rate.current``. - - """ - - -class GradientDescent(SteppableOptimizer): - r"""The gradient descent minimization routine. - - For a function :math:`f` and an initial point :math:`\vec\theta_0`, the standard (or "vanilla") - gradient descent method is an iterative scheme to find the minimum :math:`\vec\theta^*` of - :math:`f` by updating the parameters in the direction of the negative gradient of :math:`f` - - .. math:: - - \vec\theta_{n+1} = \vec\theta_{n} - \eta_n \vec\nabla f(\vec\theta_{n}), - - for a small learning rate :math:`\eta_n > 0`. - - You can either provide the analytic gradient :math:`\vec\nabla f` as ``jac`` - in the :meth:`~.minimize` method, or, if you do not provide it, use a finite difference - approximation of the gradient. To adapt the size of the perturbation in the finite difference - gradients, set the ``perturbation`` property in the initializer. - - This optimizer supports a callback function. If provided in the initializer, the optimizer - will call the callback in each iteration with the following information in this order: - current number of function values, current parameters, current function value, norm of current - gradient. - - Examples: - - A minimum example that will use finite difference gradients with a default perturbation - of 0.01 and a default learning rate of 0.01. - - .. code-block:: python - - from qiskit.algorithms.optimizers import GradientDescent - - def f(x): - return (np.linalg.norm(x) - 1) ** 2 - - initial_point = np.array([1, 0.5, -0.2]) - - optimizer = GradientDescent(maxiter=100) - - result = optimizer.minimize(fun=fun, x0=initial_point) - - print(f"Found minimum {result.x} at a value" - "of {result.fun} using {result.nfev} evaluations.") - - An example where the learning rate is an iterator and we supply the analytic gradient. - Note how much faster this convergences (i.e. less ``nfev``) compared to the previous - example. - - .. code-block:: python - - from qiskit.algorithms.optimizers import GradientDescent - - def learning_rate(): - power = 0.6 - constant_coeff = 0.1 - def powerlaw(): - n = 0 - while True: - yield constant_coeff * (n ** power) - n += 1 - - return powerlaw() - - def f(x): - return (np.linalg.norm(x) - 1) ** 2 - - def grad_f(x): - return 2 * (np.linalg.norm(x) - 1) * x / np.linalg.norm(x) - - initial_point = np.array([1, 0.5, -0.2]) - - optimizer = GradientDescent(maxiter=100, learning_rate=learning_rate) - result = optimizer.minimize(fun=fun, jac=grad_f, x0=initial_point) - - print(f"Found minimum {result.x} at a value" - "of {result.fun} using {result.nfev} evaluations.") - - - An other example where the evaluation of the function has a chance of failing. The user, with - specific knowledge about his function can catch this errors and handle them before passing the - result to the optimizer. - - .. code-block:: python - - import random - import numpy as np - from qiskit.algorithms.optimizers import GradientDescent - - def objective(x): - if random.choice([True, False]): - return None - else: - return (np.linalg.norm(x) - 1) ** 2 - - def grad(x): - if random.choice([True, False]): - return None - else: - return 2 * (np.linalg.norm(x) - 1) * x / np.linalg.norm(x) - - - initial_point = np.random.normal(0, 1, size=(100,)) - - optimizer = GradientDescent(maxiter=20) - optimizer.start(x0=initial_point, fun=objective, jac=grad) - - while optimizer.continue_condition(): - ask_data = optimizer.ask() - evaluated_gradient = None - - while evaluated_gradient is None: - evaluated_gradient = grad(ask_data.x_center) - optimizer.state.njev += 1 - - optmizer.state.nit += 1 - - tell_data = TellData(eval_jac=evaluated_gradient) - optimizer.tell(ask_data=ask_data, tell_data=tell_data) - - result = optimizer.create_result() - - Users that aren't dealing with complicated functions and who are more familiar with step by step - optimization algorithms can use the :meth:`~.step` method which wraps the :meth:`~.ask` - and :meth:`~.tell` methods. In the same spirit the method :meth:`~.minimize` will optimize the - function and return the result. - - To see other libraries that use this interface one can visit: - https://optuna.readthedocs.io/en/stable/tutorial/20_recipes/009_ask_and_tell.html - - """ - - def __init__( - self, - maxiter: int = 100, - learning_rate: float - | list[float] - | np.ndarray - | Callable[[], Generator[float, None, None]] = 0.01, - tol: float = 1e-7, - callback: CALLBACK | None = None, - perturbation: float | None = None, - ) -> None: - """ - Args: - maxiter: The maximum number of iterations. - learning_rate: A constant, list, array or factory of generators yielding learning rates - for the parameter updates. See the docstring for an example. - tol: If the norm of the parameter update is smaller than this threshold, the - optimizer has converged. - perturbation: If no gradient is passed to :meth:`~.minimize` the gradient is - approximated with a forward finite difference scheme with ``perturbation`` - perturbation in both directions (defaults to 1e-2 if required). - Ignored when we have an explicit function for the gradient. - Raises: - ValueError: If ``learning_rate`` is an array and its lenght is less than ``maxiter``. - """ - super().__init__(maxiter=maxiter) - self.callback = callback - self._state: GradientDescentState | None = None - self._perturbation = perturbation - self._tol = tol - # if learning rate is an array, check it is sufficiently long. - if isinstance(learning_rate, (list, np.ndarray)): - if len(learning_rate) < maxiter: - raise ValueError( - f"Length of learning_rate ({len(learning_rate)}) " - f"is smaller than maxiter ({maxiter})." - ) - self.learning_rate = learning_rate - - @property - def state(self) -> GradientDescentState: - """Return the current state of the optimizer.""" - return self._state - - @state.setter - def state(self, state: GradientDescentState) -> None: - """Set the current state of the optimizer.""" - self._state = state - - @property - def tol(self) -> float: - """Returns the tolerance of the optimizer. - - Any step with smaller stepsize than this value will stop the optimization.""" - return self._tol - - @tol.setter - def tol(self, tol: float) -> None: - """Set the tolerance.""" - self._tol = tol - - @property - def perturbation(self) -> float | None: - """Returns the perturbation. - - This is the perturbation used in the finite difference gradient approximation. - """ - return self._perturbation - - @perturbation.setter - def perturbation(self, perturbation: float | None) -> None: - """Set the perturbation.""" - self._perturbation = perturbation - - def _callback_wrapper(self) -> None: - """ - Wraps the callback function to accomodate GradientDescent. - - Will call :attr:`~.callback` and pass the following arguments: - current number of function values, current parameters, current function value, - norm of current gradient. - """ - if self.callback is not None: - self.callback( - self.state.nfev, - self.state.x, - self.state.fun(self.state.x), - self.state.stepsize, - ) - - @property - def settings(self) -> dict[str, Any]: - # if learning rate or perturbation are custom iterators expand them - if callable(self.learning_rate): - iterator = self.learning_rate() - learning_rate: float | np.ndarray = np.array( - [next(iterator) for _ in range(self.maxiter)] - ) - else: - learning_rate = self.learning_rate - - return { - "maxiter": self.maxiter, - "tol": self.tol, - "learning_rate": learning_rate, - "perturbation": self.perturbation, - "callback": self.callback, - } - - def ask(self) -> AskData: - """Returns an object with the data needed to evaluate the gradient. - - If this object contains a gradient function the gradient can be evaluated directly. Otherwise - approximate it with a finite difference scheme. - """ - return AskData( - x_jac=self.state.x, - ) - - def tell(self, ask_data: AskData, tell_data: TellData) -> None: - """ - Updates :attr:`.~GradientDescentState.x` by an ammount proportional to the learning - rate and value of the gradient at that point. - - Args: - ask_data: The data used to evaluate the function. - tell_data: The data from the function evaluation. - - Raises: - ValueError: If the gradient passed doesn't have the right dimension. - """ - if np.shape(self.state.x) != np.shape(tell_data.eval_jac): - raise ValueError("The gradient does not have the correct dimension") - self.state.x = self.state.x - next(self.state.learning_rate) * tell_data.eval_jac - self.state.stepsize = np.linalg.norm(tell_data.eval_jac) - self.state.nit += 1 - - def evaluate(self, ask_data: AskData) -> TellData: - """Evaluates the gradient. - - It does so either by evaluating an analytic gradient or by approximating it with a - finite difference scheme. It will either add ``1`` to the number of gradient evaluations or add - ``N+1`` to the number of function evaluations (Where N is the dimension of the gradient). - - Args: - ask_data: It contains the point where the gradient is to be evaluated and the gradient - function or, in its absence, the objective function to perform a finite difference - approximation. - - Returns: - The data containing the gradient evaluation. - """ - if self.state.jac is None: - eps = 0.01 if (self.perturbation is None) else self.perturbation - grad = Optimizer.gradient_num_diff( - x_center=ask_data.x_jac, - f=self.state.fun, - epsilon=eps, - max_evals_grouped=self._max_evals_grouped, - ) - self.state.nfev += 1 + len(ask_data.x_jac) - else: - grad = self.state.jac(ask_data.x_jac) - self.state.njev += 1 - - return TellData(eval_jac=grad) - - def create_result(self) -> OptimizerResult: - """Creates a result of the optimization process. - - This result contains the best point, the best function value, the number of function/gradient - evaluations and the number of iterations. - - Returns: - The result of the optimization process. - """ - result = OptimizerResult() - result.x = self.state.x - result.fun = self.state.fun(self.state.x) - result.nfev = self.state.nfev - result.njev = self.state.njev - result.nit = self.state.nit - return result - - def start( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> None: - - self.state = GradientDescentState( - fun=fun, - jac=jac, - x=np.asarray(x0), - nit=0, - nfev=0, - njev=0, - learning_rate=LearningRate(learning_rate=self.learning_rate), - stepsize=None, - ) - - def continue_condition(self) -> bool: - """ - Condition that indicates the optimization process should come to an end. - - When the stepsize is smaller than the tolerance, the optimization process is considered - finished. - - Returns: - ``True`` if the optimization process should continue, ``False`` otherwise. - """ - if self.state.stepsize is None: - return True - else: - return (self.state.stepsize > self.tol) and super().continue_condition() - - def get_support_level(self): - """Get the support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.supported, - "bounds": OptimizerSupportLevel.ignored, - "initial_point": OptimizerSupportLevel.required, - } diff --git a/qiskit/algorithms/optimizers/gsls.py b/qiskit/algorithms/optimizers/gsls.py deleted file mode 100644 index 6ae423fb52c8..000000000000 --- a/qiskit/algorithms/optimizers/gsls.py +++ /dev/null @@ -1,378 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# 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. - -"""Line search with Gaussian-smoothed samples on a sphere.""" - -from __future__ import annotations - -import warnings - -from collections.abc import Callable -from typing import Any, SupportsFloat -import numpy as np - -from qiskit.utils import algorithm_globals -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - - -class GSLS(Optimizer): - """Gaussian-smoothed Line Search. - - An implementation of the line search algorithm described in - https://arxiv.org/pdf/1905.01332.pdf, using gradient approximation - based on Gaussian-smoothed samples on a sphere. - - .. note:: - - This component has some function that is normally random. If you want to reproduce behavior - then you should set the random number generator seed in the algorithm_globals - (``qiskit.utils.algorithm_globals.random_seed = seed``). - """ - - _OPTIONS = [ - "maxiter", - "max_eval", - "disp", - "sampling_radius", - "sample_size_factor", - "initial_step_size", - "min_step_size", - "step_size_multiplier", - "armijo_parameter", - "min_gradient_norm", - "max_failed_rejection_sampling", - ] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int = 10000, - max_eval: int = 10000, - disp: bool = False, - sampling_radius: float = 1.0e-6, - sample_size_factor: int = 1, - initial_step_size: float = 1.0e-2, - min_step_size: float = 1.0e-10, - step_size_multiplier: float = 0.4, - armijo_parameter: float = 1.0e-1, - min_gradient_norm: float = 1e-8, - max_failed_rejection_sampling: int = 50, - ) -> None: - """ - Args: - maxiter: Maximum number of iterations. - max_eval: Maximum number of evaluations. - disp: Set to True to display convergence messages. - sampling_radius: Sampling radius to determine gradient estimate. - sample_size_factor: The size of the sample set at each iteration is this number - multiplied by the dimension of the problem, rounded to the nearest integer. - initial_step_size: Initial step size for the descent algorithm. - min_step_size: Minimum step size for the descent algorithm. - step_size_multiplier: Step size reduction after unsuccessful steps, in the - interval (0, 1). - armijo_parameter: Armijo parameter for sufficient decrease criterion, in the - interval (0, 1). - min_gradient_norm: If the gradient norm is below this threshold, the algorithm stops. - max_failed_rejection_sampling: Maximum number of attempts to sample points within - bounds. - """ - super().__init__() - for k, v in list(locals().items()): - if k in self._OPTIONS: - self._options[k] = v - - def get_support_level(self) -> dict[str, int]: - """Return support level dictionary. - - Returns: - A dictionary containing the support levels for different options. - """ - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.supported, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self) -> dict[str, Any]: - return {key: self._options.get(key, None) for key in self._OPTIONS} - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - if not isinstance(x0, np.ndarray): - x0 = np.asarray(x0) - - if bounds is None: - var_lb = np.array([-np.inf] * x0.size) - var_ub = np.array([np.inf] * x0.size) - else: - var_lb = np.array([l for (l, _) in bounds]) - var_ub = np.array([u for (_, u) in bounds]) - - x, fun, nfev, _ = self.ls_optimize(x0.size, fun, x0, var_lb, var_ub) - - result = OptimizerResult() - result.x = x - result.fun = fun - result.nfev = nfev - - return result - - def ls_optimize( - self, - n: int, - obj_fun: Callable[[POINT], float], - initial_point: np.ndarray, - var_lb: np.ndarray, - var_ub: np.ndarray, - ) -> tuple[np.ndarray, float, int, float]: - """Run the line search optimization. - - Args: - n: Dimension of the problem. - obj_fun: Objective function. - initial_point: Initial point. - var_lb: Vector of lower bounds on the decision variables. Vector elements can be -np.inf - if the corresponding variable is unbounded from below. - var_ub: Vector of upper bounds on the decision variables. Vector elements can be np.inf - if the corresponding variable is unbounded from below. - - Returns: - Final iterate as a vector, corresponding objective function value, - number of evaluations, and norm of the gradient estimate. - - Raises: - ValueError: If the number of dimensions mismatches the size of the initial point or - the length of the lower or upper bound. - """ - if len(initial_point) != n: - raise ValueError("Size of the initial point mismatches the number of dimensions.") - if len(var_lb) != n: - raise ValueError("Length of the lower bound mismatches the number of dimensions.") - if len(var_ub) != n: - raise ValueError("Length of the upper bound mismatches the number of dimensions.") - - # Initialize counters and data - iter_count = 0 - n_evals = 0 - prev_iter_successful = True - prev_directions, prev_sample_set_x, prev_sample_set_y = None, None, None - consecutive_fail_iter = 0 - alpha = self._options["initial_step_size"] - grad_norm: SupportsFloat = np.inf - sample_set_size = int(round(self._options["sample_size_factor"] * n)) - - # Initial point - x = initial_point - x_value = obj_fun(x) - n_evals += 1 - while iter_count < self._options["maxiter"] and n_evals < self._options["max_eval"]: - - # Determine set of sample points - directions, sample_set_x = self.sample_set(n, x, var_lb, var_ub, sample_set_size) - - if n_evals + len(sample_set_x) + 1 >= self._options["max_eval"]: - # The evaluation budget is too small to allow for - # another full iteration; we therefore exit now - break - - sample_set_y = np.array([obj_fun(point) for point in sample_set_x]) - n_evals += len(sample_set_x) - - # Expand sample set if we could not improve - if not prev_iter_successful: - directions = np.vstack((prev_directions, directions)) - sample_set_x = np.vstack((prev_sample_set_x, sample_set_x)) - sample_set_y = np.hstack((prev_sample_set_y, sample_set_y)) - - # Find gradient approximation and candidate point - grad = self.gradient_approximation( - n, x, x_value, directions, sample_set_x, sample_set_y - ) - grad_norm = np.linalg.norm(grad) - new_x = np.clip(x - alpha * grad, var_lb, var_ub) - new_x_value = obj_fun(new_x) - n_evals += 1 - - # Print information - if self._options["disp"]: - print(f"Iter {iter_count:d}") - print(f"Point {x} obj {x_value}") - print(f"Gradient {grad}") - print(f"Grad norm {grad_norm} new_x_value {new_x_value} step_size {alpha}") - print(f"Direction {directions}") - - # Test Armijo condition for sufficient decrease - if new_x_value <= x_value - self._options["armijo_parameter"] * alpha * grad_norm: - # Accept point - x, x_value = new_x, new_x_value - alpha /= 2 * self._options["step_size_multiplier"] - prev_iter_successful = True - consecutive_fail_iter = 0 - - # Reset sample set - prev_directions = None - prev_sample_set_x = None - prev_sample_set_y = None - else: - # Do not accept point - alpha *= self._options["step_size_multiplier"] - prev_iter_successful = False - consecutive_fail_iter += 1 - - # Store sample set to enlarge it - prev_directions = directions - prev_sample_set_x, prev_sample_set_y = sample_set_x, sample_set_y - - iter_count += 1 - - # Check termination criterion - if ( - grad_norm <= self._options["min_gradient_norm"] - or alpha <= self._options["min_step_size"] - ): - break - - return x, x_value, n_evals, grad_norm - - def sample_points( - self, n: int, x: np.ndarray, num_points: int - ) -> tuple[np.ndarray, np.ndarray]: - """Sample ``num_points`` points around ``x`` on the ``n``-sphere of specified radius. - - The radius of the sphere is ``self._options['sampling_radius']``. - - Args: - n: Dimension of the problem. - x: Point around which the sample set is constructed. - num_points: Number of points in the sample set. - - Returns: - A tuple containing the sampling points and the directions. - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - normal_samples = algorithm_globals.random.normal(size=(num_points, n)) - row_norms = np.linalg.norm(normal_samples, axis=1, keepdims=True) - directions = normal_samples / row_norms - points = x + self._options["sampling_radius"] * directions - - return points, directions - - def sample_set( - self, n: int, x: np.ndarray, var_lb: np.ndarray, var_ub: np.ndarray, num_points: int - ) -> tuple[np.ndarray, np.ndarray]: - """Construct sample set of given size. - - Args: - n: Dimension of the problem. - x: Point around which the sample set is constructed. - var_lb: Vector of lower bounds on the decision variables. Vector elements can be -np.inf - if the corresponding variable is unbounded from below. - var_ub: Vector of lower bounds on the decision variables. Vector elements can be np.inf - if the corresponding variable is unbounded from above. - num_points: Number of points in the sample set. - - Returns: - Matrices of (unit-norm) sample directions and sample points, one per row. - Both matrices are 2D arrays of floats. - - Raises: - RuntimeError: If not enough samples could be generated within the bounds. - """ - # Generate points uniformly on the sphere - points, directions = self.sample_points(n, x, num_points) - - # Check bounds - if (points >= var_lb).all() and (points <= var_ub).all(): - # If all points are within bounds, return them - return directions, (x + self._options["sampling_radius"] * directions) - else: - # Otherwise we perform rejection sampling until we have - # enough points that satisfy the bounds - indices = np.where((points >= var_lb).all(axis=1) & (points <= var_ub).all(axis=1))[0] - accepted = directions[indices] - num_trials = 0 - - while ( - len(accepted) < num_points - and num_trials < self._options["max_failed_rejection_sampling"] - ): - # Generate points uniformly on the sphere - points, directions = self.sample_points(n, x, num_points) - indices = np.where((points >= var_lb).all(axis=1) & (points <= var_ub).all(axis=1))[ - 0 - ] - accepted = np.vstack((accepted, directions[indices])) - num_trials += 1 - - # When we are at a corner point, the expected fraction of acceptable points may be - # exponential small in the dimension of the problem. Thus, if we keep failing and - # do not have enough points by now, we switch to a different method that guarantees - # finding enough points, but they may not be uniformly distributed. - if len(accepted) < num_points: - points, directions = self.sample_points(n, x, num_points) - to_be_flipped = (points < var_lb) | (points > var_ub) - directions *= np.where(to_be_flipped, -1, 1) - points = x + self._options["sampling_radius"] * directions - indices = np.where((points >= var_lb).all(axis=1) & (points <= var_ub).all(axis=1))[ - 0 - ] - accepted = np.vstack((accepted, directions[indices])) - - # If we still do not have enough sampling points, we have failed. - if len(accepted) < num_points: - raise RuntimeError( - "Could not generate enough samples within bounds; try smaller radius." - ) - - return ( - accepted[:num_points], - x + self._options["sampling_radius"] * accepted[:num_points], - ) - - def gradient_approximation( - self, - n: int, - x: np.ndarray, - x_value: float, - directions: np.ndarray, - sample_set_x: np.ndarray, - sample_set_y: np.ndarray, - ) -> np.ndarray: - """Construct gradient approximation from given sample. - - Args: - n: Dimension of the problem. - x: Point around which the sample set was constructed. - x_value: Objective function value at x. - directions: Directions of the sample points wrt the central point x, as a 2D array. - sample_set_x: x-coordinates of the sample set, one point per row, as a 2D array. - sample_set_y: Objective function values of the points in sample_set_x, as a 1D array. - - Returns: - Gradient approximation at x, as a 1D array. - """ - ffd = sample_set_y - x_value - gradient = ( - float(n) - / len(sample_set_y) - * np.sum( - ffd.reshape(len(sample_set_y), 1) / self._options["sampling_radius"] * directions, 0 - ) - ) - return gradient diff --git a/qiskit/algorithms/optimizers/imfil.py b/qiskit/algorithms/optimizers/imfil.py deleted file mode 100644 index 2fca4da2c139..000000000000 --- a/qiskit/algorithms/optimizers/imfil.py +++ /dev/null @@ -1,86 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""IMplicit FILtering (IMFIL) optimizer.""" -from __future__ import annotations - -from collections.abc import Callable -from typing import Any - -from qiskit.utils import optionals as _optionals -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - - -@_optionals.HAS_SKQUANT.require_in_instance -class IMFIL(Optimizer): - """IMplicit FILtering algorithm. - - Implicit filtering is a way to solve bound-constrained optimization problems for - which derivatives are not available. In comparison to methods that use interpolation to - reconstruct the function and its higher derivatives, implicit filtering builds upon - coordinate search followed by interpolation to get an approximate gradient. - - Uses skquant.opt installed with pip install scikit-quant. - For further detail, please refer to - https://github.com/scikit-quant/scikit-quant and https://qat4chem.lbl.gov/software. - """ - - def __init__( - self, - maxiter: int = 1000, - ) -> None: - """ - Args: - maxiter: Maximum number of function evaluations. - - Raises: - MissingOptionalLibraryError: scikit-quant not installed - """ - super().__init__() - self._maxiter = maxiter - - def get_support_level(self): - """Returns support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.required, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self) -> dict[str, Any]: - return { - "maxiter": self._maxiter, - } - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - from skquant import opt as skq - - res, history = skq.minimize( - func=fun, - x0=x0, - bounds=bounds, - budget=self._maxiter, - method="imfil", - ) - - optimizer_result = OptimizerResult() - optimizer_result.x = res.optpar - optimizer_result.fun = res.optval - optimizer_result.nfev = len(history) - return optimizer_result diff --git a/qiskit/algorithms/optimizers/l_bfgs_b.py b/qiskit/algorithms/optimizers/l_bfgs_b.py deleted file mode 100644 index 3c2d7a619ef3..000000000000 --- a/qiskit/algorithms/optimizers/l_bfgs_b.py +++ /dev/null @@ -1,88 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Limited-memory BFGS Bound optimizer.""" - -from __future__ import annotations -from typing import SupportsFloat - -import numpy as np - -from .scipy_optimizer import SciPyOptimizer - - -class L_BFGS_B(SciPyOptimizer): # pylint: disable=invalid-name - """ - Limited-memory BFGS Bound optimizer. - - The target goal of Limited-memory Broyden-Fletcher-Goldfarb-Shanno Bound (L-BFGS-B) - is to minimize the value of a differentiable scalar function :math:`f`. - This optimizer is a quasi-Newton method, meaning that, in contrast to Newtons's method, - it does not require :math:`f`'s Hessian (the matrix of :math:`f`'s second derivatives) - when attempting to compute :math:`f`'s minimum value. - - Like BFGS, L-BFGS is an iterative method for solving unconstrained, non-linear optimization - problems, but approximates BFGS using a limited amount of computer memory. - L-BFGS starts with an initial estimate of the optimal value, and proceeds iteratively - to refine that estimate with a sequence of better estimates. - - The derivatives of :math:`f` are used to identify the direction of steepest descent, - and also to form an estimate of the Hessian matrix (second derivative) of :math:`f`. - L-BFGS-B extends L-BFGS to handle simple, per-variable bound constraints. - - Uses ``scipy.optimize.fmin_l_bfgs_b``. - For further detail, please refer to - https://docs.scipy.org/doc/scipy/reference/optimize.minimize-lbfgsb.html - """ - - _OPTIONS = ["maxfun", "maxiter", "ftol", "iprint", "eps"] - - # pylint: disable=unused-argument - def __init__( - self, - maxfun: int = 15000, - maxiter: int = 15000, - ftol: SupportsFloat = 10 * np.finfo(float).eps, - iprint: int = -1, - eps: float = 1e-08, - options: dict | None = None, - max_evals_grouped: int = 1, - **kwargs, - ): - r""" - Args: - maxfun: Maximum number of function evaluations. - maxiter: Maximum number of iterations. - ftol: The iteration stops when - :math:`(f^k - f^{k+1}) / \max\{|f^k|, |f^{k+1}|,1\} \leq \text{ftol}`. - iprint: Controls the frequency of output. ``iprint < 0`` means no output; - ``iprint = 0`` print only one line at the last iteration; ``0 < iprint < 99`` - print also :math:`f` and :math:`|\text{proj} g|` every iprint iterations; - ``iprint = 99`` print details of every iteration except n-vectors; ``iprint = 100`` - print also the changes of active set and final :math:`x`; ``iprint > 100`` print - details of every iteration including :math:`x` and :math:`g`. - eps: If jac is approximated, use this value for the step size. - options: A dictionary of solver options. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - kwargs: additional kwargs for ``scipy.optimize.minimize``. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__( - method="L-BFGS-B", - options=options, - max_evals_grouped=max_evals_grouped, - **kwargs, - ) diff --git a/qiskit/algorithms/optimizers/nelder_mead.py b/qiskit/algorithms/optimizers/nelder_mead.py deleted file mode 100644 index ff9d8708763f..000000000000 --- a/qiskit/algorithms/optimizers/nelder_mead.py +++ /dev/null @@ -1,73 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Nelder-Mead optimizer.""" -from __future__ import annotations - - -from .scipy_optimizer import SciPyOptimizer - - -class NELDER_MEAD(SciPyOptimizer): # pylint: disable=invalid-name - """ - Nelder-Mead optimizer. - - The Nelder-Mead algorithm performs unconstrained optimization; it ignores bounds - or constraints. It is used to find the minimum or maximum of an objective function - in a multidimensional space. It is based on the Simplex algorithm. Nelder-Mead - is robust in many applications, especially when the first and second derivatives of the - objective function are not known. - - However, if the numerical computation of the derivatives can be trusted to be accurate, - other algorithms using the first and/or second derivatives information might be preferred to - Nelder-Mead for their better performance in the general case, especially in consideration of - the fact that the Nelder–Mead technique is a heuristic search method that can converge to - non-stationary points. - - Uses scipy.optimize.minimize Nelder-Mead. - For further detail, please refer to - See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "maxfev", "disp", "xatol", "adaptive"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int | None = None, - maxfev: int = 1000, - disp: bool = False, - xatol: float = 0.0001, - tol: float | None = None, - adaptive: bool = False, - options: dict | None = None, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum allowed number of iterations. If both maxiter and maxfev are set, - minimization will stop at the first reached. - maxfev: Maximum allowed number of function evaluations. If both maxiter and - maxfev are set, minimization will stop at the first reached. - disp: Set to True to print convergence messages. - xatol: Absolute error in xopt between iterations that is acceptable for convergence. - tol: Tolerance for termination. - adaptive: Adapt algorithm parameters to dimensionality of problem. - options: A dictionary of solver options. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__(method="Nelder-Mead", options=options, tol=tol, **kwargs) diff --git a/qiskit/algorithms/optimizers/nft.py b/qiskit/algorithms/optimizers/nft.py deleted file mode 100644 index 2a7503137daf..000000000000 --- a/qiskit/algorithms/optimizers/nft.py +++ /dev/null @@ -1,170 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""Nakanishi-Fujii-Todo algorithm.""" -from __future__ import annotations - - -import numpy as np -from scipy.optimize import OptimizeResult - -from .scipy_optimizer import SciPyOptimizer - - -class NFT(SciPyOptimizer): - """ - Nakanishi-Fujii-Todo algorithm. - - See https://arxiv.org/abs/1903.12166 - """ - - _OPTIONS = ["maxiter", "maxfev", "disp", "reset_interval"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int | None = None, - maxfev: int = 1024, - disp: bool = False, - reset_interval: int = 32, - options: dict | None = None, - **kwargs, - ) -> None: - """ - Built out using scipy framework, for details, please refer to - https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html. - - Args: - maxiter: Maximum number of iterations to perform. - maxfev: Maximum number of function evaluations to perform. - disp: disp - reset_interval: The minimum estimates directly once - in ``reset_interval`` times. - options: A dictionary of solver options. - kwargs: additional kwargs for scipy.optimize.minimize. - - Notes: - In this optimization method, the optimization function have to satisfy - three conditions written in [1]_. - - References: - .. [1] K. M. Nakanishi, K. Fujii, and S. Todo. 2019. - Sequential minimal optimization for quantum-classical hybrid algorithms. - arXiv preprint arXiv:1903.12166. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__(method=nakanishi_fujii_todo, options=options, **kwargs) - - -# pylint: disable=invalid-name -def nakanishi_fujii_todo( - fun, x0, args=(), maxiter=None, maxfev=1024, reset_interval=32, eps=1e-32, callback=None, **_ -): - """ - Find the global minimum of a function using the nakanishi_fujii_todo - algorithm [1]. - Args: - fun (callable): ``f(x, *args)`` - Function to be optimized. ``args`` can be passed as an optional item - in the dict ``minimizer_kwargs``. - This function must satisfy the three condition written in Ref. [1]. - x0 (ndarray): shape (n,) - Initial guess. Array of real elements of size (n,), - where 'n' is the number of independent variables. - args (tuple, optional): - Extra arguments passed to the objective function. - maxiter (int): - Maximum number of iterations to perform. - Default: None. - maxfev (int): - Maximum number of function evaluations to perform. - Default: 1024. - reset_interval (int): - The minimum estimates directly once in ``reset_interval`` times. - Default: 32. - eps (float): eps - **_ : additional options - callback (callable, optional): - Called after each iteration. - Returns: - OptimizeResult: - The optimization result represented as a ``OptimizeResult`` object. - Important attributes are: ``x`` the solution array. See - `OptimizeResult` for a description of other attributes. - Notes: - In this optimization method, the optimization function have to satisfy - three conditions written in [2]_. - - References: - .. [2] K. M. Nakanishi, K. Fujii, and S. Todo. 2019. - Sequential minimal optimization for quantum-classical hybrid algorithms. - arXiv preprint arXiv:1903.12166. - """ - - x0 = np.asarray(x0) - recycle_z0 = None - niter = 0 - funcalls = 0 - - while True: - - idx = niter % x0.size - - if reset_interval > 0: - if niter % reset_interval == 0: - recycle_z0 = None - - if recycle_z0 is None: - z0 = fun(np.copy(x0), *args) - funcalls += 1 - else: - z0 = recycle_z0 - - p = np.copy(x0) - p[idx] = x0[idx] + np.pi / 2 - z1 = fun(p, *args) - funcalls += 1 - - p = np.copy(x0) - p[idx] = x0[idx] - np.pi / 2 - z3 = fun(p, *args) - funcalls += 1 - - z2 = z1 + z3 - z0 - c = (z1 + z3) / 2 - a = np.sqrt((z0 - z2) ** 2 + (z1 - z3) ** 2) / 2 - b = np.arctan((z1 - z3) / ((z0 - z2) + eps * (z0 == z2))) + x0[idx] - b += 0.5 * np.pi + 0.5 * np.pi * np.sign((z0 - z2) + eps * (z0 == z2)) - - x0[idx] = b - recycle_z0 = c - a - - niter += 1 - - if callback is not None: - callback(np.copy(x0)) - - if maxfev is not None: - if funcalls >= maxfev: - break - - if maxiter is not None: - if niter >= maxiter: - break - - return OptimizeResult( - fun=fun(np.copy(x0), *args), x=x0, nit=niter, nfev=funcalls, success=(niter > 1) - ) diff --git a/qiskit/algorithms/optimizers/nlopts/__init__.py b/qiskit/algorithms/optimizers/nlopts/__init__.py deleted file mode 100644 index e3165bc0b482..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""NLopt based global optimizers""" diff --git a/qiskit/algorithms/optimizers/nlopts/crs.py b/qiskit/algorithms/optimizers/nlopts/crs.py deleted file mode 100644 index 77eb67b298b6..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/crs.py +++ /dev/null @@ -1,35 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Controlled Random Search (CRS) with local mutation optimizer.""" - -from .nloptimizer import NLoptOptimizer, NLoptOptimizerType - - -class CRS(NLoptOptimizer): - """ - Controlled Random Search (CRS) with local mutation optimizer. - - Controlled Random Search (CRS) with local mutation is part of the family of the CRS optimizers. - The CRS optimizers start with a random population of points, and randomly evolve these points - by heuristic rules. In the case of CRS with local mutation, the evolution is a randomized - version of the :class:`NELDER_MEAD` local optimizer. - - - NLopt global optimizer, derivative-free. - For further detail, please refer to - https://nlopt.readthedocs.io/en/latest/NLopt_Algorithms/#controlled-random-search-crs-with-local-mutation - """ - - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """Return NLopt optimizer type""" - return NLoptOptimizerType.GN_CRS2_LM diff --git a/qiskit/algorithms/optimizers/nlopts/direct_l.py b/qiskit/algorithms/optimizers/nlopts/direct_l.py deleted file mode 100644 index e7ed9be3e25f..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/direct_l.py +++ /dev/null @@ -1,34 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""DIviding RECTangles Locally-biased optimizer.""" - -from .nloptimizer import NLoptOptimizer, NLoptOptimizerType - - -class DIRECT_L(NLoptOptimizer): # pylint: disable=invalid-name - """ - DIviding RECTangles Locally-biased optimizer. - - DIviding RECTangles (DIRECT) is a deterministic-search algorithms based on systematic division - of the search domain into increasingly smaller hyper-rectangles. - The DIRECT-L version is a "locally biased" variant of DIRECT that makes the algorithm more - biased towards local search, so that it is more efficient for functions with few local minima. - - NLopt global optimizer, derivative-free. - For further detail, please refer to - http://nlopt.readthedocs.io/en/latest/NLopt_Algorithms/#direct-and-direct-l - """ - - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """Return NLopt optimizer type""" - return NLoptOptimizerType.GN_DIRECT_L diff --git a/qiskit/algorithms/optimizers/nlopts/direct_l_rand.py b/qiskit/algorithms/optimizers/nlopts/direct_l_rand.py deleted file mode 100644 index 15172ef00880..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/direct_l_rand.py +++ /dev/null @@ -1,32 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""DIviding RECTangles Locally-biased Randomized optimizer.""" - -from .nloptimizer import NLoptOptimizer, NLoptOptimizerType - - -class DIRECT_L_RAND(NLoptOptimizer): # pylint: disable=invalid-name - """ - DIviding RECTangles Locally-biased Randomized optimizer. - - DIRECT-L RAND is the "locally biased" variant with some randomization in near-tie decisions. - See also :class:`DIRECT_L` - - NLopt global optimizer, derivative-free. - For further detail, please refer to - http://nlopt.readthedocs.io/en/latest/NLopt_Algorithms/#direct-and-direct-l - """ - - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """Return NLopt optimizer type""" - return NLoptOptimizerType.GN_DIRECT_L_RAND diff --git a/qiskit/algorithms/optimizers/nlopts/esch.py b/qiskit/algorithms/optimizers/nlopts/esch.py deleted file mode 100644 index 7e754f9447fe..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/esch.py +++ /dev/null @@ -1,33 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""ESCH evolutionary optimizer.""" - -from .nloptimizer import NLoptOptimizer, NLoptOptimizerType - - -class ESCH(NLoptOptimizer): - """ - ESCH evolutionary optimizer. - - ESCH is an evolutionary algorithm for global optimization that supports bound constraints only. - Specifically, it does not support nonlinear constraints. - - NLopt global optimizer, derivative-free. - For further detail, please refer to - - http://nlopt.readthedocs.io/en/latest/NLopt_Algorithms/#esch-evolutionary-algorithm - """ - - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """Return NLopt optimizer type""" - return NLoptOptimizerType.GN_ESCH diff --git a/qiskit/algorithms/optimizers/nlopts/isres.py b/qiskit/algorithms/optimizers/nlopts/isres.py deleted file mode 100644 index 1c37a9401e3a..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/isres.py +++ /dev/null @@ -1,39 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Improved Stochastic Ranking Evolution Strategy optimizer.""" - -from .nloptimizer import NLoptOptimizer, NLoptOptimizerType - - -class ISRES(NLoptOptimizer): - """ - Improved Stochastic Ranking Evolution Strategy optimizer. - - Improved Stochastic Ranking Evolution Strategy (ISRES) is an algorithm for - non-linearly constrained global optimization. It has heuristics to escape local optima, - even though convergence to a global optima is not guaranteed. The evolution strategy is based - on a combination of a mutation rule and differential variation. The fitness ranking is simply - via the objective function for problems without nonlinear constraints. When nonlinear - constraints are included, the `stochastic ranking proposed by Runarsson and Yao - `__ - is employed. This method supports arbitrary nonlinear inequality and equality constraints, in - addition to the bound constraints. - - NLopt global optimizer, derivative-free. - For further detail, please refer to - http://nlopt.readthedocs.io/en/latest/NLopt_Algorithms/#isres-improved-stochastic-ranking-evolution-strategy - """ - - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """Return NLopt optimizer type""" - return NLoptOptimizerType.GN_ISRES diff --git a/qiskit/algorithms/optimizers/nlopts/nloptimizer.py b/qiskit/algorithms/optimizers/nlopts/nloptimizer.py deleted file mode 100644 index 65f56b930482..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/nloptimizer.py +++ /dev/null @@ -1,131 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Minimize using objective function""" -from __future__ import annotations - -from collections.abc import Callable -from enum import Enum -from abc import abstractmethod -import logging -import numpy as np - -from qiskit.utils import optionals as _optionals -from ..optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - -logger = logging.getLogger(__name__) - - -class NLoptOptimizerType(Enum): - """NLopt Valid Optimizer""" - - GN_CRS2_LM = 1 - GN_DIRECT_L_RAND = 2 - GN_DIRECT_L = 3 - GN_ESCH = 4 - GN_ISRES = 5 - - -@_optionals.HAS_NLOPT.require_in_instance -class NLoptOptimizer(Optimizer): - """ - NLopt global optimizer base class - """ - - _OPTIONS = ["max_evals"] - - def __init__(self, max_evals: int = 1000) -> None: # pylint: disable=unused-argument - """ - Args: - max_evals: Maximum allowed number of function evaluations. - - Raises: - MissingOptionalLibraryError: NLopt library not installed. - """ - import nlopt - - super().__init__() - for k, v in list(locals().items()): - if k in self._OPTIONS: - self._options[k] = v - - self._optimizer_names = { - NLoptOptimizerType.GN_CRS2_LM: nlopt.GN_CRS2_LM, - NLoptOptimizerType.GN_DIRECT_L_RAND: nlopt.GN_DIRECT_L_RAND, - NLoptOptimizerType.GN_DIRECT_L: nlopt.GN_DIRECT_L, - NLoptOptimizerType.GN_ESCH: nlopt.GN_ESCH, - NLoptOptimizerType.GN_ISRES: nlopt.GN_ISRES, - } - - @abstractmethod - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """return NLopt optimizer enum type""" - raise NotImplementedError - - def get_support_level(self): - """return support level dictionary""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.supported, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self): - return {"max_evals": self._options.get("max_evals", 1000)} - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - import nlopt - - x0 = np.asarray(x0) - - if bounds is None: - bounds = [(None, None)] * x0.size - - threshold = 3 * np.pi - low = [(l if l is not None else -threshold) for (l, u) in bounds] - high = [(u if u is not None else threshold) for (l, u) in bounds] - - name = self._optimizer_names[self.get_nlopt_optimizer()] - opt = nlopt.opt(name, len(low)) - logger.debug(opt.get_algorithm_name()) - - opt.set_lower_bounds(low) - opt.set_upper_bounds(high) - - eval_count = 0 - - def wrap_objfunc_global(x, _grad): - nonlocal eval_count - eval_count += 1 - return fun(x) - - opt.set_min_objective(wrap_objfunc_global) - opt.set_maxeval(self._options.get("max_evals", 1000)) - - xopt = opt.optimize(x0) - minf = opt.last_optimum_value() - - logger.debug("Global minimize found %s eval count %s", minf, eval_count) - - result = OptimizerResult() - result.x = xopt - result.fun = minf - result.nfev = eval_count - - return result diff --git a/qiskit/algorithms/optimizers/optimizer.py b/qiskit/algorithms/optimizers/optimizer.py deleted file mode 100644 index e253167b5d9d..000000000000 --- a/qiskit/algorithms/optimizers/optimizer.py +++ /dev/null @@ -1,389 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Optimizer interface""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Callable -from enum import IntEnum -import logging -from typing import Any, Union, Protocol - -import numpy as np -import scipy - -from qiskit.algorithms.algorithm_result import AlgorithmResult - -logger = logging.getLogger(__name__) - -POINT = Union[float, np.ndarray] - - -class OptimizerResult(AlgorithmResult): - """The result of an optimization routine.""" - - def __init__(self) -> None: - super().__init__() - self._x: POINT | None = None - self._fun: float | None = None - self._jac: POINT | None = None - self._nfev: int | None = None - self._njev: int | None = None - self._nit: int | None = None - - @property - def x(self) -> POINT | None: - """The final point of the minimization.""" - return self._x - - @x.setter - def x(self, x: POINT | None) -> None: - """Set the final point of the minimization.""" - self._x = x - - @property - def fun(self) -> float | None: - """The final value of the minimization.""" - return self._fun - - @fun.setter - def fun(self, fun: float | None) -> None: - """Set the final value of the minimization.""" - self._fun = fun - - @property - def jac(self) -> POINT | None: - """The final gradient of the minimization.""" - return self._jac - - @jac.setter - def jac(self, jac: POINT | None) -> None: - """Set the final gradient of the minimization.""" - self._jac = jac - - @property - def nfev(self) -> int | None: - """The total number of function evaluations.""" - return self._nfev - - @nfev.setter - def nfev(self, nfev: int | None) -> None: - """Set the total number of function evaluations.""" - self._nfev = nfev - - @property - def njev(self) -> int | None: - """The total number of gradient evaluations.""" - return self._njev - - @njev.setter - def njev(self, njev: int | None) -> None: - """Set the total number of gradient evaluations.""" - self._njev = njev - - @property - def nit(self) -> int | None: - """The total number of iterations.""" - return self._nit - - @nit.setter - def nit(self, nit: int | None) -> None: - """Set the total number of iterations.""" - self._nit = nit - - -class Minimizer(Protocol): - """Callable Protocol for minimizer. - - This interface is based on `SciPy's optimize module - `__. - - This protocol defines a callable taking the following parameters: - - fun - The objective function to minimize (for example the energy in the case of the VQE). - x0 - The initial point for the optimization. - jac - The gradient of the objective function. - bounds - Parameters bounds for the optimization. Note that these might not be supported - by all optimizers. - - and which returns a minimization result object (either SciPy's or Qiskit's). - """ - - # pylint: disable=invalid-name - def __call__( - self, - fun: Callable[[np.ndarray], float], - x0: np.ndarray, - jac: Callable[[np.ndarray], np.ndarray] | None, - bounds: list[tuple[float, float]] | None, - ) -> scipy.optimize.OptimizeResult | OptimizerResult: - """Minimize the objective function. - - This interface is based on `SciPy's optimize module `__. - - Args: - fun: The objective function to minimize (for example the energy in the case of the VQE). - x0: The initial point for the optimization. - jac: The gradient of the objective function. - bounds: Parameters bounds for the optimization. Note that these might not be supported - by all optimizers. - - Returns: - The minimization result object (either SciPy's or Qiskit's). - """ - ... - - -class OptimizerSupportLevel(IntEnum): - """Support Level enum for features such as bounds, gradient and initial point""" - - # pylint: disable=invalid-name - not_supported = 0 # Does not support the corresponding parameter in optimize() - ignored = 1 # Feature can be passed as non None but will be ignored - supported = 2 # Feature is supported - required = 3 # Feature is required and must be given, None is invalid - - -class Optimizer(ABC): - """Base class for optimization algorithm.""" - - @abstractmethod - def __init__(self): - """ - Initialize the optimization algorithm, setting the support - level for _gradient_support_level, _bound_support_level, - _initial_point_support_level, and empty options. - """ - self._gradient_support_level = self.get_support_level()["gradient"] - self._bounds_support_level = self.get_support_level()["bounds"] - self._initial_point_support_level = self.get_support_level()["initial_point"] - self._options = {} - self._max_evals_grouped = None - - @abstractmethod - def get_support_level(self): - """Return support level dictionary""" - raise NotImplementedError - - def set_options(self, **kwargs): - """ - Sets or updates values in the options dictionary. - - The options dictionary may be used internally by a given optimizer to - pass additional optional values for the underlying optimizer/optimization - function used. The options dictionary may be initially populated with - a set of key/values when the given optimizer is constructed. - - Args: - kwargs (dict): options, given as name=value. - """ - for name, value in kwargs.items(): - self._options[name] = value - logger.debug("options: %s", self._options) - - # pylint: disable=invalid-name - @staticmethod - def gradient_num_diff(x_center, f, epsilon, max_evals_grouped=None): - """ - We compute the gradient with the numeric differentiation in the parallel way, - around the point x_center. - - Args: - x_center (ndarray): point around which we compute the gradient - f (func): the function of which the gradient is to be computed. - epsilon (float): the epsilon used in the numeric differentiation. - max_evals_grouped (int): max evals grouped, defaults to 1 (i.e. no batching). - Returns: - grad: the gradient computed - - """ - if max_evals_grouped is None: # no batching by default - max_evals_grouped = 1 - - forig = f(*((x_center,))) - grad = [] - ei = np.zeros((len(x_center),), float) - todos = [] - for k in range(len(x_center)): - ei[k] = 1.0 - d = epsilon * ei - todos.append(x_center + d) - ei[k] = 0.0 - - counter = 0 - chunk = [] - chunks = [] - length = len(todos) - # split all points to chunks, where each chunk has batch_size points - for i in range(length): - x = todos[i] - chunk.append(x) - counter += 1 - # the last one does not have to reach batch_size - if counter == max_evals_grouped or i == length - 1: - chunks.append(chunk) - chunk = [] - counter = 0 - - for chunk in chunks: # eval the chunks in order - parallel_parameters = np.concatenate(chunk) - todos_results = f(parallel_parameters) # eval the points in a chunk (order preserved) - if isinstance(todos_results, float): - grad.append((todos_results - forig) / epsilon) - else: - for todor in todos_results: - grad.append((todor - forig) / epsilon) - - return np.array(grad) - - @staticmethod - def wrap_function(function, args): - """ - Wrap the function to implicitly inject the args at the call of the function. - - Args: - function (func): the target function - args (tuple): the args to be injected - Returns: - function_wrapper: wrapper - """ - - def function_wrapper(*wrapper_args): - return function(*(wrapper_args + args)) - - return function_wrapper - - @property - def setting(self): - """Return setting""" - ret = f"Optimizer: {self.__class__.__name__}\n" - params = "" - for key, value in self.__dict__.items(): - if key[0] == "_": - params += f"-- {key[1:]}: {value}\n" - ret += f"{params}" - return ret - - @property - def settings(self) -> dict[str, Any]: - """The optimizer settings in a dictionary format. - - The settings can for instance be used for JSON-serialization (if all settings are - serializable, which e.g. doesn't hold per default for callables), such that the - optimizer object can be reconstructed as - - .. code-block:: - - settings = optimizer.settings - # JSON serialize and send to another server - optimizer = OptimizerClass(**settings) - - """ - raise NotImplementedError("The settings method is not implemented per default.") - - @abstractmethod - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - """Minimize the scalar function. - - Args: - fun: The scalar function to minimize. - x0: The initial point for the minimization. - jac: The gradient of the scalar function ``fun``. - bounds: Bounds for the variables of ``fun``. This argument might be ignored if the - optimizer does not support bounds. - - Returns: - The result of the optimization, containing e.g. the result as attribute ``x``. - """ - raise NotImplementedError() - - @property - def gradient_support_level(self): - """Returns gradient support level""" - return self._gradient_support_level - - @property - def is_gradient_ignored(self): - """Returns is gradient ignored""" - return self._gradient_support_level == OptimizerSupportLevel.ignored - - @property - def is_gradient_supported(self): - """Returns is gradient supported""" - return self._gradient_support_level != OptimizerSupportLevel.not_supported - - @property - def is_gradient_required(self): - """Returns is gradient required""" - return self._gradient_support_level == OptimizerSupportLevel.required - - @property - def bounds_support_level(self): - """Returns bounds support level""" - return self._bounds_support_level - - @property - def is_bounds_ignored(self): - """Returns is bounds ignored""" - return self._bounds_support_level == OptimizerSupportLevel.ignored - - @property - def is_bounds_supported(self): - """Returns is bounds supported""" - return self._bounds_support_level != OptimizerSupportLevel.not_supported - - @property - def is_bounds_required(self): - """Returns is bounds required""" - return self._bounds_support_level == OptimizerSupportLevel.required - - @property - def initial_point_support_level(self): - """Returns initial point support level""" - return self._initial_point_support_level - - @property - def is_initial_point_ignored(self): - """Returns is initial point ignored""" - return self._initial_point_support_level == OptimizerSupportLevel.ignored - - @property - def is_initial_point_supported(self): - """Returns is initial point supported""" - return self._initial_point_support_level != OptimizerSupportLevel.not_supported - - @property - def is_initial_point_required(self): - """Returns is initial point required""" - return self._initial_point_support_level == OptimizerSupportLevel.required - - def print_options(self): - """Print algorithm-specific options.""" - for name in sorted(self._options): - logger.debug("%s = %s", name, str(self._options[name])) - - def set_max_evals_grouped(self, limit): - """Set max evals grouped""" - self._max_evals_grouped = limit diff --git a/qiskit/algorithms/optimizers/optimizer_utils/__init__.py b/qiskit/algorithms/optimizers/optimizer_utils/__init__.py deleted file mode 100644 index 33c5bc90b087..000000000000 --- a/qiskit/algorithms/optimizers/optimizer_utils/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -"""Utils for optimizers - -Optimizer Utils (:mod:`qiskit.algorithms.optimizers.optimizer_utils`) -===================================================================== - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - LearningRate - -""" - -from .learning_rate import LearningRate - -__all__ = ["LearningRate"] diff --git a/qiskit/algorithms/optimizers/optimizer_utils/learning_rate.py b/qiskit/algorithms/optimizers/optimizer_utils/learning_rate.py deleted file mode 100644 index 7bfea636ce2c..000000000000 --- a/qiskit/algorithms/optimizers/optimizer_utils/learning_rate.py +++ /dev/null @@ -1,88 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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 class to represent the Learning Rate.""" -from __future__ import annotations - -from collections.abc import Generator, Callable -from itertools import tee -import numpy as np - - -class LearningRate(Generator): - """Represents a Learning Rate. - Will be an attribute of :class:`~.GradientDescentState`. Note that :class:`~.GradientDescent` also - has a learning rate. That learning rate can be a float, a list, an array, a function returning - a generator and will be used to create a generator to be used during the - optimization process. - This class wraps ``Generator`` so that we can also access the last yielded value. - """ - - def __init__( - self, - learning_rate: float - | list[float] - | np.ndarray - | Callable[[], Generator[float, None, None]], - ): - """ - Args: - learning_rate: Used to create a generator to iterate on. - """ - if isinstance(learning_rate, (float, int)): - self._gen = constant(learning_rate) - elif isinstance(learning_rate, Generator): - learning_rate, self._gen = tee(learning_rate) - elif isinstance(learning_rate, (list, np.ndarray)): - self._gen = (eta for eta in learning_rate) - else: - self._gen = learning_rate() - - self._current: float | None = None - - def send(self, value): - """Send a value into the generator. - Return next yielded value or raise StopIteration. - """ - self._current = next(self._gen) - return self.current - - def throw(self, typ, val=None, tb=None): - """Raise an exception in the generator. - Return next yielded value or raise StopIteration. - """ - if val is None: - if tb is None: - raise typ - val = typ() - if tb is not None: - val = val.with_traceback(tb) - raise val - - @property - def current(self): - """Returns the current value of the learning rate.""" - return self._current - - -def constant(learning_rate: float = 0.01) -> Generator[float, None, None]: - """Returns a python generator that always yields the same value. - - Args: - learning_rate: The value to yield. - - Yields: - The learning rate for the next iteration. - """ - - while True: - yield learning_rate diff --git a/qiskit/algorithms/optimizers/p_bfgs.py b/qiskit/algorithms/optimizers/p_bfgs.py deleted file mode 100644 index f166160d98de..000000000000 --- a/qiskit/algorithms/optimizers/p_bfgs.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# 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. - -"""Parallelized Limited-memory BFGS optimizer""" -from __future__ import annotations - -import logging -import multiprocessing -import platform -import warnings -from collections.abc import Callable -from typing import SupportsFloat - -import numpy as np - -from qiskit.utils import algorithm_globals -from qiskit.utils.validation import validate_min - -from .optimizer import OptimizerResult, POINT -from .scipy_optimizer import SciPyOptimizer - -logger = logging.getLogger(__name__) - - -class P_BFGS(SciPyOptimizer): # pylint: disable=invalid-name - """ - Parallelized Limited-memory BFGS optimizer. - - P-BFGS is a parallelized version of :class:`L_BFGS_B` with which it shares the same parameters. - P-BFGS can be useful when the target hardware is a quantum simulator running on a classical - machine. This allows the multiple processes to use simulation to potentially reach a minimum - faster. The parallelization may also help the optimizer avoid getting stuck at local optima. - - Uses scipy.optimize.fmin_l_bfgs_b. - For further detail, please refer to - https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_l_bfgs_b.html - """ - - _OPTIONS = ["maxfun", "ftol", "iprint"] - - # pylint: disable=unused-argument - def __init__( - self, - maxfun: int = 1000, - ftol: SupportsFloat = 10 * np.finfo(float).eps, - iprint: int = -1, - max_processes: int | None = None, - options: dict | None = None, - max_evals_grouped: int = 1, - **kwargs, - ) -> None: - r""" - Args: - maxfun: Maximum number of function evaluations. - ftol: The iteration stops when (f\^k - f\^{k+1})/max{\|f\^k\|,\|f\^{k+1}\|,1} <= ftol. - iprint: Controls the frequency of output. iprint < 0 means no output; - iprint = 0 print only one line at the last iteration; 0 < iprint < 99 - print also f and \|proj g\| every iprint iterations; iprint = 99 print - details of every iteration except n-vectors; iprint = 100 print also the - changes of active set and final x; iprint > 100 print details of - every iteration including x and g. - max_processes: maximum number of processes allowed, has a min. value of 1 if not None. - options: A dictionary of solver options. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if max_processes: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_min("max_processes", max_processes, 1) - - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__( - method="L-BFGS-B", - options=options, - max_evals_grouped=max_evals_grouped, - **kwargs, - ) - self._max_processes = max_processes - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - x0 = np.asarray(x0) - - num_procs = multiprocessing.cpu_count() - 1 - num_procs = ( - num_procs if self._max_processes is None else min(num_procs, self._max_processes) - ) - num_procs = num_procs if num_procs >= 0 else 0 - - if platform.system() == "Darwin": - # Changed in version 3.8: On macOS, the spawn start method is now the - # default. The fork start method should be considered unsafe as it can - # lead to crashes. - # However P_BFGS doesn't support spawn, so we revert to single process. - num_procs = 0 - logger.warning( - "For MacOS, python >= 3.8, using only current process. " - "Multiple core use not supported." - ) - elif platform.system() == "Windows": - num_procs = 0 - logger.warning( - "For Windows, using only current process. Multiple core use not supported." - ) - - queue: multiprocessing.queues.Queue[tuple[POINT, float, int]] = multiprocessing.Queue() - - # TODO: are automatic bounds a good idea? What if the circuit parameters are not - # just from plain Pauli rotations but have a coefficient? - - # bounds for additional initial points in case bounds has any None values - threshold = 2 * np.pi - if bounds is None: - bounds = [(-threshold, threshold)] * x0.size - low = [(l if l is not None else -threshold) for (l, u) in bounds] - high = [(u if u is not None else threshold) for (l, u) in bounds] - - def optimize_runner(_queue, _i_pt): # Multi-process sampling - _sol, _opt, _nfev = self._optimize(fun, _i_pt, jac, bounds) - _queue.put((_sol, _opt, _nfev)) - - # Start off as many other processes running the optimize (can be 0) - processes = [] - for _ in range(num_procs): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - i_pt = algorithm_globals.random.uniform(low, high) # Another random point in bounds - proc = multiprocessing.Process(target=optimize_runner, args=(queue, i_pt)) - processes.append(proc) - proc.start() - - # While the one optimize in this process below runs the other processes will - # be running too. This one runs - # with the supplied initial point. The process ones have their own random one - sol, opt, nfev = self._optimize(fun, x0, jac, bounds) - - for proc in processes: - # For each other process we wait now for it to finish and see if it has - # a better result than above - proc.join() - p_sol, p_opt, p_nfev = queue.get() - if p_opt < opt: - sol, opt = p_sol, p_opt - nfev += p_nfev - - result = OptimizerResult() - result.x = sol - result.fun = opt - result.nfev = nfev - - return result - - def _optimize( - self, - objective_function, - initial_point, - gradient_function=None, - variable_bounds=None, - ) -> tuple[POINT, float, int]: - result = super().minimize( - objective_function, initial_point, gradient_function, variable_bounds - ) - return result.x, result.fun, result.nfev diff --git a/qiskit/algorithms/optimizers/powell.py b/qiskit/algorithms/optimizers/powell.py deleted file mode 100644 index de8cbb1b9d18..000000000000 --- a/qiskit/algorithms/optimizers/powell.py +++ /dev/null @@ -1,64 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Powell optimizer.""" -from __future__ import annotations - -from .scipy_optimizer import SciPyOptimizer - - -class POWELL(SciPyOptimizer): - """ - Powell optimizer. - - The Powell algorithm performs unconstrained optimization; it ignores bounds or - constraints. Powell is a *conjugate direction method*: it performs sequential one-dimensional - minimization along each directional vector, which is updated at - each iteration of the main minimization loop. The function being minimized need not be - differentiable, and no derivatives are taken. - - Uses scipy.optimize.minimize Powell. - For further detail, please refer to - See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "maxfev", "disp", "xtol"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int | None = None, - maxfev: int = 1000, - disp: bool = False, - xtol: float = 0.0001, - tol: float | None = None, - options: dict | None = None, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum allowed number of iterations. If both maxiter and maxfev - are set, minimization will stop at the first reached. - maxfev: Maximum allowed number of function evaluations. If both maxiter and - maxfev are set, minimization will stop at the first reached. - disp: Set to True to print convergence messages. - xtol: Relative error in solution xopt acceptable for convergence. - tol: Tolerance for termination. - options: A dictionary of solver options. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__("Powell", options=options, tol=tol, **kwargs) diff --git a/qiskit/algorithms/optimizers/qnspsa.py b/qiskit/algorithms/optimizers/qnspsa.py deleted file mode 100644 index be5907afbf17..000000000000 --- a/qiskit/algorithms/optimizers/qnspsa.py +++ /dev/null @@ -1,421 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. - -"""The QN-SPSA optimizer.""" - -from __future__ import annotations - -from collections.abc import Iterator -from typing import Any, Callable - -import numpy as np -from qiskit.providers import Backend -from qiskit.circuit import ParameterVector, QuantumCircuit -from qiskit.opflow import StateFn, CircuitSampler, ExpectationBase -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg - -from qiskit.primitives import BaseSampler, Sampler -from qiskit.algorithms.state_fidelities import ComputeUncompute - -from .spsa import SPSA, CALLBACK, TERMINATIONCHECKER, _batch_evaluate - -# the function to compute the fidelity -FIDELITY = Callable[[np.ndarray, np.ndarray], float] - - -class QNSPSA(SPSA): - r"""The Quantum Natural SPSA (QN-SPSA) optimizer. - - The QN-SPSA optimizer [1] is a stochastic optimizer that belongs to the family of gradient - descent methods. This optimizer is based on SPSA but attempts to improve the convergence by - sampling the **natural gradient** instead of the vanilla, first-order gradient. It achieves - this by approximating Hessian of the ``fidelity`` of the ansatz circuit. - - Compared to natural gradients, which require :math:`\mathcal{O}(d^2)` expectation value - evaluations for a circuit with :math:`d` parameters, QN-SPSA only requires - :math:`\mathcal{O}(1)` and can therefore significantly speed up the natural gradient calculation - by sacrificing some accuracy. Compared to SPSA, QN-SPSA requires 4 additional function - evaluations of the fidelity. - - The stochastic approximation of the natural gradient can be systematically improved by - increasing the number of ``resamplings``. This leads to a Monte Carlo-style convergence to - the exact, analytic value. - - .. note:: - - This component has some function that is normally random. If you want to reproduce behavior - then you should set the random number generator seed in the algorithm_globals - (``qiskit.utils.algorithm_globals.random_seed = seed``). - - Examples: - - This short example runs QN-SPSA for the ground state calculation of the ``Z ^ Z`` - observable where the ansatz is a ``PauliTwoDesign`` circuit. - - .. code-block:: python - - import numpy as np - from qiskit.algorithms.optimizers import QNSPSA - from qiskit.circuit.library import PauliTwoDesign - from qiskit.primitives import Estimator, Sampler - from qiskit.quantum_info import Pauli - - # problem setup - ansatz = PauliTwoDesign(2, reps=1, seed=2) - observable = Pauli("ZZ") - initial_point = np.random.random(ansatz.num_parameters) - - # loss function - estimator = Estimator() - - def loss(x): - result = estimator.run([ansatz], [observable], [x]).result() - return np.real(result.values[0]) - - # fidelity for estimation of the geometric tensor - sampler = Sampler() - fidelity = QNSPSA.get_fidelity(ansatz, sampler) - - # run QN-SPSA - qnspsa = QNSPSA(fidelity, maxiter=300) - result = qnspsa.optimize(ansatz.num_parameters, loss, initial_point=initial_point) - - This is a legacy version solving the same problem but using Qiskit Opflow instead - of the Qiskit Primitives. Note however, that this usage is deprecated. - - .. code-block:: python - - import numpy as np - from qiskit.algorithms.optimizers import QNSPSA - from qiskit.circuit.library import PauliTwoDesign - from qiskit.opflow import Z, StateFn - - ansatz = PauliTwoDesign(2, reps=1, seed=2) - observable = Z ^ Z - initial_point = np.random.random(ansatz.num_parameters) - - def loss(x): - bound = ansatz.assign_parameters(x) - return np.real((StateFn(observable, is_measurement=True) @ StateFn(bound)).eval()) - - fidelity = QNSPSA.get_fidelity(ansatz) - qnspsa = QNSPSA(fidelity, maxiter=300) - result = qnspsa.optimize(ansatz.num_parameters, loss, initial_point=initial_point) - - - References: - - [1] J. Gacon et al, "Simultaneous Perturbation Stochastic Approximation of the Quantum - Fisher Information", `arXiv:2103.09232 `_ - - """ - - def __init__( - self, - fidelity: FIDELITY, - maxiter: int = 100, - blocking: bool = True, - allowed_increase: float | None = None, - learning_rate: float | Callable[[], Iterator] | None = None, - perturbation: float | Callable[[], Iterator] | None = None, - resamplings: int | dict[int, int] = 1, - perturbation_dims: int | None = None, - regularization: float | None = None, - hessian_delay: int = 0, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - initial_hessian: np.ndarray | None = None, - callback: CALLBACK | None = None, - termination_checker: TERMINATIONCHECKER | None = None, - ) -> None: - r""" - Args: - fidelity: A function to compute the fidelity of the ansatz state with itself for - two different sets of parameters. - maxiter: The maximum number of iterations. Note that this is not the maximal number - of function evaluations. - blocking: If True, only accepts updates that improve the loss (up to some allowed - increase, see next argument). - allowed_increase: If ``blocking`` is ``True``, this argument determines by how much - the loss can increase with the proposed parameters and still be accepted. - If ``None``, the allowed increases is calibrated automatically to be twice the - approximated standard deviation of the loss function. - learning_rate: The update step is the learning rate is multiplied with the gradient. - If the learning rate is a float, it remains constant over the course of the - optimization. It can also be a callable returning an iterator which yields the - learning rates for each optimization step. - If ``learning_rate`` is set ``perturbation`` must also be provided. - perturbation: Specifies the magnitude of the perturbation for the finite difference - approximation of the gradients. Can be either a float or a generator yielding - the perturbation magnitudes per step. - If ``perturbation`` is set ``learning_rate`` must also be provided. - resamplings: The number of times the gradient (and Hessian) is sampled using a random - direction to construct a gradient estimate. Per default the gradient is estimated - using only one random direction. If an integer, all iterations use the same number - of resamplings. If a dictionary, this is interpreted as - ``{iteration: number of resamplings per iteration}``. - perturbation_dims: The number of perturbed dimensions. Per default, all dimensions - are perturbed, but a smaller, fixed number can be perturbed. If set, the perturbed - dimensions are chosen uniformly at random. - regularization: To ensure the preconditioner is symmetric and positive definite, the - identity times a small coefficient is added to it. This generator yields that - coefficient. - hessian_delay: Start multiplying the gradient with the inverse Hessian only after a - certain number of iterations. The Hessian is still evaluated and therefore this - argument can be useful to first get a stable average over the last iterations before - using it as preconditioner. - lse_solver: The method to solve for the inverse of the Hessian. Per default an - exact LSE solver is used, but can e.g. be overwritten by a minimization routine. - initial_hessian: The initial guess for the Hessian. By default the identity matrix - is used. - callback: A callback function passed information in each iteration step. The - information is, in this order: the parameters, the function value, the number - of function evaluations, the stepsize, whether the step was accepted. - termination_checker: A callback function executed at the end of each iteration step. The - arguments are, in this order: the parameters, the function value, the number - of function evaluations, the stepsize, whether the step was accepted. If the callback - returns True, the optimization is terminated. - To prevent additional evaluations of the objective method, if the objective has not yet - been evaluated, the objective is estimated by taking the mean of the objective - evaluations used in the estimate of the gradient. - - - """ - super().__init__( - maxiter, - blocking, - allowed_increase, - # trust region *must* be false for natural gradients to work - trust_region=False, - learning_rate=learning_rate, - perturbation=perturbation, - resamplings=resamplings, - callback=callback, - second_order=True, - hessian_delay=hessian_delay, - lse_solver=lse_solver, - regularization=regularization, - perturbation_dims=perturbation_dims, - initial_hessian=initial_hessian, - termination_checker=termination_checker, - ) - - self.fidelity = fidelity - - def _point_sample(self, loss, x, eps, delta1, delta2): - loss_points = [x + eps * delta1, x - eps * delta1] - fidelity_points = [ - (x, x + eps * delta1), - (x, x - eps * delta1), - (x, x + eps * (delta1 + delta2)), - (x, x + eps * (-delta1 + delta2)), - ] - self._nfev += 6 - - loss_values = _batch_evaluate(loss, loss_points, self._max_evals_grouped) - fidelity_values = _batch_evaluate( - self.fidelity, fidelity_points, self._max_evals_grouped, unpack_points=True - ) - - # compute the gradient approximation and additionally return the loss function evaluations - gradient_estimate = (loss_values[0] - loss_values[1]) / (2 * eps) * delta1 - - # compute the preconditioner point estimate - fidelity_values = np.asarray(fidelity_values, dtype=float) - diff = fidelity_values[2] - fidelity_values[0] - diff = diff - (fidelity_values[3] - fidelity_values[1]) - diff = diff / (2 * eps**2) - - rank_one = np.outer(delta1, delta2) - # -0.5 factor comes from the fact that we need -0.5 * fidelity - hessian_estimate = -0.5 * diff * (rank_one + rank_one.T) / 2 - - return np.mean(loss_values), gradient_estimate, hessian_estimate - - @property - def settings(self) -> dict[str, Any]: - """The optimizer settings in a dictionary format.""" - # re-use serialization from SPSA - settings = super().settings - settings.update({"fidelity": self.fidelity}) - - # remove SPSA-specific arguments not in QNSPSA - settings.pop("trust_region") - settings.pop("second_order") - - return settings - - @staticmethod - @deprecate_arg( - "backend", - since="0.24.0", - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - # We allow passing a sampler as the second argument because that will become a positional - # argument for `sampler` after removing `backend` and `expectation`. - predicate=lambda backend: not isinstance(backend, BaseSampler), - ) - @deprecate_arg( - "expectation", - since="0.24.0", - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def get_fidelity( - circuit: QuantumCircuit, - backend: Backend | QuantumInstance | None = None, - expectation: ExpectationBase | None = None, - *, - sampler: BaseSampler | None = None, - ) -> Callable[[np.ndarray, np.ndarray], float]: - r"""Get a function to compute the fidelity of ``circuit`` with itself. - - .. note:: - - Using this function with a backend and expectation converter is pending deprecation, - instead pass a Qiskit Primitive sampler, such as :class:`~.Sampler`. - The sampler can be passed as keyword argument or, positionally, as second argument. - - Let ``circuit`` be a parameterized quantum circuit performing the operation - :math:`U(\theta)` given a set of parameters :math:`\theta`. Then this method returns - a function to evaluate - - .. math:: - - F(\theta, \phi) = \big|\langle 0 | U^\dagger(\theta) U(\phi) |0\rangle \big|^2. - - The output of this function can be used as input for the ``fidelity`` to the - :class:`~.QNSPSA` optimizer. - - Args: - circuit: The circuit preparing the parameterized ansatz. - backend: Deprecated. A backend of quantum instance to evaluate the circuits. - If None, plain matrix multiplication will be used. - expectation: Deprecated. An expectation converter to specify how the expected - value is computed. If a shot-based readout is used this should be set to - ``PauliExpectation``. - sampler: A sampler primitive to sample from a quantum state. - - Returns: - A handle to the function :math:`F`. - - """ - # allow passing sampler by position - if isinstance(backend, BaseSampler): - sampler = backend - backend = None - - if expectation is None and backend is None and sampler is None: - sampler = Sampler() - - if expectation is not None or backend is not None: - return QNSPSA._legacy_get_fidelity(circuit, backend, expectation) - - fid = ComputeUncompute(sampler) - - num_parameters = circuit.num_parameters - - def fidelity(values_x, values_y): - values_x = np.reshape(values_x, (-1, num_parameters)).tolist() - batch_size_x = len(values_x) - - values_y = np.reshape(values_y, (-1, num_parameters)).tolist() - batch_size_y = len(values_y) - - result = fid.run( - batch_size_x * [circuit], batch_size_y * [circuit], values_x, values_y - ).result() - return np.asarray(result.fidelities) - - return fidelity - - @staticmethod - def _legacy_get_fidelity( - circuit: QuantumCircuit, - backend: Backend | QuantumInstance | None = None, - expectation: ExpectationBase | None = None, - ) -> Callable[[np.ndarray, np.ndarray], float]: - r"""Deprecated. Get a function to compute the fidelity of ``circuit`` with itself. - - .. note:: - - This method is deprecated. Instead use the :class:`~.ComputeUncompute` - class which implements the fidelity calculation in the same fashion as this method. - - Let ``circuit`` be a parameterized quantum circuit performing the operation - :math:`U(\theta)` given a set of parameters :math:`\theta`. Then this method returns - a function to evaluate - - .. math:: - - F(\theta, \phi) = \big|\langle 0 | U^\dagger(\theta) U(\phi) |0\rangle \big|^2. - - The output of this function can be used as input for the ``fidelity`` to the - :class:~`qiskit.algorithms.optimizers.QNSPSA` optimizer. - - Args: - circuit: The circuit preparing the parameterized ansatz. - backend: A backend of quantum instance to evaluate the circuits. If None, plain - matrix multiplication will be used. - expectation: An expectation converter to specify how the expected value is computed. - If a shot-based readout is used this should be set to ``PauliExpectation``. - - Returns: - A handle to the function :math:`F`. - - """ - params_x = ParameterVector("x", circuit.num_parameters) - params_y = ParameterVector("y", circuit.num_parameters) - - expression = ~StateFn(circuit.assign_parameters(params_x)) @ StateFn( - circuit.assign_parameters(params_y) - ) - - if expectation is not None: - expression = expectation.convert(expression) - - if backend is None: - - def fidelity(values_x, values_y): - value_dict = dict( - zip(params_x[:] + params_y[:], values_x.tolist() + values_y.tolist()) - ) - return np.abs(expression.assign_parameters(value_dict).eval()) ** 2 - - else: - sampler = CircuitSampler(backend) - - def fidelity(values_x, values_y=None): - # no batches - if isinstance(values_x, np.ndarray) and isinstance(values_y, np.ndarray): - value_dict = dict( - zip(params_x[:] + params_y[:], values_x.tolist() + values_y.tolist()) - ) - # legacy batching -- remove once QNSPSA.get_fidelity is only supported with sampler - elif values_y is None: - value_dict = {p: [] for p in params_x[:] + params_y[:]} - for values_xy in values_x: - for value_x, param_x in zip(values_xy[0, :], params_x): - value_dict[param_x].append(value_x) - - for value_y, param_y in zip(values_xy[1, :], params_y): - value_dict[param_y].append(value_y) - else: - value_dict = {p: [] for p in params_x[:] + params_y[:]} - for values_i_x, values_i_y in zip(values_x, values_y): - for value_x, param_x in zip(values_i_x, params_x): - value_dict[param_x].append(value_x) - - for value_y, param_y in zip(values_i_y, params_y): - value_dict[param_y].append(value_y) - - return np.abs(sampler.convert(expression, params=value_dict).eval()) ** 2 - - return fidelity diff --git a/qiskit/algorithms/optimizers/scipy_optimizer.py b/qiskit/algorithms/optimizers/scipy_optimizer.py deleted file mode 100644 index 3a210126b2bd..000000000000 --- a/qiskit/algorithms/optimizers/scipy_optimizer.py +++ /dev/null @@ -1,183 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Wrapper class of scipy.optimize.minimize.""" -from __future__ import annotations - -import warnings -from collections.abc import Callable -from typing import Any - -import numpy as np -from scipy.optimize import minimize - -from qiskit.utils.validation import validate_min - -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - - -class SciPyOptimizer(Optimizer): - """A general Qiskit Optimizer wrapping scipy.optimize.minimize. - - For further detail, please refer to - https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _bounds_support_methods = {"l-bfgs-b", "tnc", "slsqp", "powell", "trust-constr"} - _gradient_support_methods = { - "cg", - "bfgs", - "newton-cg", - "l-bfgs-b", - "tnc", - "slsqp", - "dogleg", - "trust-ncg", - "trust-krylov", - "trust-exact", - "trust-constr", - } - - def __init__( - self, - method: str | Callable, - options: dict[str, Any] | None = None, - max_evals_grouped: int = 1, - **kwargs, - ): - """ - Args: - method: Type of solver. - options: A dictionary of solver options. - kwargs: additional kwargs for scipy.optimize.minimize. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - """ - self._method = method.lower() if isinstance(method, str) else method - # Set support level - if self._method in self._bounds_support_methods: - self._bounds_support_level = OptimizerSupportLevel.supported - else: - self._bounds_support_level = OptimizerSupportLevel.ignored - if self._method in self._gradient_support_methods: - self._gradient_support_level = OptimizerSupportLevel.supported - else: - self._gradient_support_level = OptimizerSupportLevel.ignored - self._initial_point_support_level = OptimizerSupportLevel.required - - self._options = options if options is not None else {} - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_min("max_evals_grouped", max_evals_grouped, 1) - - self._max_evals_grouped = max_evals_grouped - self._kwargs = kwargs - - def get_support_level(self): - """Return support level dictionary""" - return { - "gradient": self._gradient_support_level, - "bounds": self._bounds_support_level, - "initial_point": self._initial_point_support_level, - } - - @property - def settings(self) -> dict[str, Any]: - options = self._options.copy() - if hasattr(self, "_OPTIONS"): - # all _OPTIONS should be keys in self._options, but add a failsafe here - attributes = [ - option - for option in self._OPTIONS # pylint: disable=no-member - if option in options.keys() - ] - - settings = {attr: options.pop(attr) for attr in attributes} - else: - settings = {} - - settings["max_evals_grouped"] = self._max_evals_grouped - settings["options"] = options - settings.update(self._kwargs) - - # the subclasses don't need the "method" key as the class type specifies the method - if self.__class__ == SciPyOptimizer: - settings["method"] = self._method - - return settings - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - # Remove ignored parameters to supress the warning of scipy.optimize.minimize - if self.is_bounds_ignored: - bounds = None - if self.is_gradient_ignored: - jac = None - - if self.is_gradient_supported and jac is None and self._max_evals_grouped > 1: - if "eps" in self._options: - epsilon = self._options["eps"] - else: - epsilon = ( - 1e-8 if self._method in {"l-bfgs-b", "tnc"} else np.sqrt(np.finfo(float).eps) - ) - jac = Optimizer.wrap_function( - Optimizer.gradient_num_diff, (fun, epsilon, self._max_evals_grouped) - ) - - # Workaround for L_BFGS_B because it does not accept np.ndarray. - # See https://github.com/Qiskit/qiskit-terra/pull/6373. - if jac is not None and self._method == "l-bfgs-b": - jac = self._wrap_gradient(jac) - - # Starting in scipy 1.9.0 maxiter is deprecated and maxfun (added in 1.5.0) - # should be used instead - swapped_deprecated_args = False - if self._method == "tnc" and "maxiter" in self._options: - swapped_deprecated_args = True - self._options["maxfun"] = self._options.pop("maxiter") - - raw_result = minimize( - fun=fun, - x0=x0, - method=self._method, - jac=jac, - bounds=bounds, - options=self._options, - **self._kwargs, - ) - if swapped_deprecated_args: - self._options["maxiter"] = self._options.pop("maxfun") - - result = OptimizerResult() - result.x = raw_result.x - result.fun = raw_result.fun - result.nfev = raw_result.nfev - result.njev = raw_result.get("njev", None) - result.nit = raw_result.get("nit", None) - - return result - - @staticmethod - def _wrap_gradient(gradient_function): - def wrapped_gradient(x): - gradient = gradient_function(x) - if isinstance(gradient, np.ndarray): - return gradient.tolist() - return gradient - - return wrapped_gradient diff --git a/qiskit/algorithms/optimizers/slsqp.py b/qiskit/algorithms/optimizers/slsqp.py deleted file mode 100644 index d02eb790afa9..000000000000 --- a/qiskit/algorithms/optimizers/slsqp.py +++ /dev/null @@ -1,73 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Sequential Least SQuares Programming optimizer""" -from __future__ import annotations - - -from .scipy_optimizer import SciPyOptimizer - - -class SLSQP(SciPyOptimizer): - """ - Sequential Least SQuares Programming optimizer. - - SLSQP minimizes a function of several variables with any combination of bounds, equality - and inequality constraints. The method wraps the SLSQP Optimization subroutine originally - implemented by Dieter Kraft. - - SLSQP is ideal for mathematical problems for which the objective function and the constraints - are twice continuously differentiable. Note that the wrapper handles infinite values in bounds - by converting them into large floating values. - - Uses scipy.optimize.minimize SLSQP. - For further detail, please refer to - See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "disp", "ftol", "eps"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int = 100, - disp: bool = False, - ftol: float = 1e-06, - tol: float | None = None, - eps: float = 1.4901161193847656e-08, - options: dict | None = None, - max_evals_grouped: int = 1, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum number of iterations. - disp: Set to True to print convergence messages. - ftol: Precision goal for the value of f in the stopping criterion. - tol: Tolerance for termination. - eps: Step size used for numerical approximation of the Jacobian. - options: A dictionary of solver options. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__( - "SLSQP", - options=options, - tol=tol, - max_evals_grouped=max_evals_grouped, - **kwargs, - ) diff --git a/qiskit/algorithms/optimizers/snobfit.py b/qiskit/algorithms/optimizers/snobfit.py deleted file mode 100644 index 8d6a3bde1d07..000000000000 --- a/qiskit/algorithms/optimizers/snobfit.py +++ /dev/null @@ -1,130 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""Stable Noisy Optimization by Branch and FIT algorithm (SNOBFIT) optimizer.""" -from __future__ import annotations - -from collections.abc import Callable -from typing import Any - -import numpy as np -from qiskit.exceptions import QiskitError -from qiskit.utils import optionals as _optionals -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - - -@_optionals.HAS_SKQUANT.require_in_instance -@_optionals.HAS_SQSNOBFIT.require_in_instance -class SNOBFIT(Optimizer): - """Stable Noisy Optimization by Branch and FIT algorithm. - - SnobFit is used for the optimization of derivative-free, noisy objective functions providing - robust and fast solutions of problems with continuous variables varying within bound. - - Uses skquant.opt installed with pip install scikit-quant. - For further detail, please refer to - https://github.com/scikit-quant/scikit-quant and https://qat4chem.lbl.gov/software. - """ - - def __init__( - self, - maxiter: int = 1000, - maxfail: int = 10, - maxmp: int = None, - verbose: bool = False, - ) -> None: - """ - Args: - maxiter: Maximum number of function evaluations. - maxmp: Maximum number of model points requested for the local fit. - Default = 2 * number of parameters + 6 set to this value when None. - maxfail: Maximum number of failures to improve the solution. Stops the algorithm - after maxfail is reached. - verbose: Provide verbose (debugging) output. - - Raises: - MissingOptionalLibraryError: scikit-quant or SQSnobFit not installed - QiskitError: If NumPy 1.24.0 or above is installed. - See https://github.com/scikit-quant/scikit-quant/issues/24 for more details. - """ - # check version - version = tuple(map(int, np.__version__.split("."))) - if version >= (1, 24, 0): - raise QiskitError( - "SnobFit is incompatible with NumPy 1.24.0 or above, please " - "install a previous version. See also scikit-quant/scikit-quant#24." - ) - - super().__init__() - self._maxiter = maxiter - self._maxfail = maxfail - self._maxmp = maxmp - self._verbose = verbose - - def get_support_level(self): - """Returns support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.required, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self) -> dict[str, Any]: - return { - "maxiter": self._maxiter, - "maxfail": self._maxfail, - "maxmp": self._maxmp, - "verbose": self._verbose, - } - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - import skquant.opt as skq - from SQSnobFit import optset - - if bounds is None or any(None in bound_tuple for bound_tuple in bounds): - raise ValueError("Optimizer SNOBFIT requires bounds for all parameters.") - - snobfit_settings = { - "maxmp": self._maxmp, - "maxfail": self._maxfail, - "verbose": self._verbose, - } - options = optset(optin=snobfit_settings) - # counters the error when initial point is outside the acceptable bounds - x0 = np.asarray(x0) - for idx, theta in enumerate(x0): - if abs(theta) > bounds[idx][0]: - x0[idx] = x0[idx] % bounds[idx][0] - elif abs(theta) > bounds[idx][1]: - x0[idx] = x0[idx] % bounds[idx][1] - - res, history = skq.minimize( - fun, - x0, - bounds=bounds, - budget=self._maxiter, - method="snobfit", - options=options, - ) - - optimizer_result = OptimizerResult() - optimizer_result.x = res.optpar - optimizer_result.fun = res.optval - optimizer_result.nfev = len(history) - return optimizer_result diff --git a/qiskit/algorithms/optimizers/spsa.py b/qiskit/algorithms/optimizers/spsa.py deleted file mode 100644 index 2902f518af16..000000000000 --- a/qiskit/algorithms/optimizers/spsa.py +++ /dev/null @@ -1,810 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# 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. - -"""Simultaneous Perturbation Stochastic Approximation (SPSA) optimizer. - -This implementation allows both, standard first-order as well as second-order SPSA. -""" -from __future__ import annotations - -from collections import deque -from collections.abc import Iterator -from typing import Callable, Any, SupportsFloat -import logging -import warnings -from time import time - -import scipy -import numpy as np - -from qiskit.utils import algorithm_globals -from qiskit.utils.deprecation import deprecate_func - -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - -# number of function evaluations, parameters, loss, stepsize, accepted -CALLBACK = Callable[[int, np.ndarray, float, SupportsFloat, bool], None] -TERMINATIONCHECKER = Callable[[int, np.ndarray, float, SupportsFloat, bool], bool] - -logger = logging.getLogger(__name__) - - -class SPSA(Optimizer): - """Simultaneous Perturbation Stochastic Approximation (SPSA) optimizer. - - SPSA [1] is an gradient descent method for optimizing systems with multiple unknown parameters. - As an optimization method, it is appropriately suited to large-scale population models, - adaptive modeling, and simulation optimization. - - .. seealso:: - - Many examples are presented at the `SPSA Web site `__. - - The main feature of SPSA is the stochastic gradient approximation, which requires only two - measurements of the objective function, regardless of the dimension of the optimization - problem. - - Additionally to standard, first-order SPSA, where only gradient information is used, this - implementation also allows second-order SPSA (2-SPSA) [2]. In 2-SPSA we also estimate the - Hessian of the loss with a stochastic approximation and multiply the gradient with the - inverse Hessian to take local curvature into account and improve convergence. - Notably this Hessian estimate requires only a constant number of function evaluations - unlike an exact evaluation of the Hessian, which scales quadratically in the number of - function evaluations. - - .. note:: - - SPSA can be used in the presence of noise, and it is therefore indicated in situations - involving measurement uncertainty on a quantum computation when finding a minimum. - If you are executing a variational algorithm using an OpenQASM - simulator or a real device, SPSA would be the most recommended choice among the optimizers - provided here. - - The optimization process can includes a calibration phase if neither the ``learning_rate`` nor - ``perturbation`` is provided, which requires additional functional evaluations. - (Note that either both or none must be set.) For further details on the automatic calibration, - please refer to the supplementary information section IV. of [3]. - - .. note:: - - This component has some function that is normally random. If you want to reproduce behavior - then you should set the random number generator seed in the algorithm_globals - (``qiskit.utils.algorithm_globals.random_seed = seed``). - - - Examples: - - This short example runs SPSA for the ground state calculation of the ``Z ^ Z`` - observable where the ansatz is a ``PauliTwoDesign`` circuit. - - .. code-block:: python - - import numpy as np - from qiskit.algorithms.optimizers import SPSA - from qiskit.circuit.library import PauliTwoDesign - from qiskit.opflow import Z, StateFn - - ansatz = PauliTwoDesign(2, reps=1, seed=2) - observable = Z ^ Z - initial_point = np.random.random(ansatz.num_parameters) - - def loss(x): - bound = ansatz.assign_parameters(x) - return np.real((StateFn(observable, is_measurement=True) @ StateFn(bound)).eval()) - - spsa = SPSA(maxiter=300) - result = spsa.optimize(ansatz.num_parameters, loss, initial_point=initial_point) - - To use the Hessian information, i.e. 2-SPSA, you can add `second_order=True` to the - initializer of the `SPSA` class, the rest of the code remains the same. - - .. code-block:: python - - two_spsa = SPSA(maxiter=300, second_order=True) - result = two_spsa.optimize(ansatz.num_parameters, loss, initial_point=initial_point) - - The `termination_checker` can be used to implement a custom termination criterion. - - .. code-block:: python - - import numpy as np - from qiskit.algorithms.optimizers import SPSA - - def objective(x): - return np.linalg.norm(x) + .04*np.random.rand(1) - - class TerminationChecker: - - def __init__(self, N : int): - self.N = N - self.values = [] - - def __call__(self, nfev, parameters, value, stepsize, accepted) -> bool: - self.values.append(value) - - if len(self.values) > self.N: - last_values = self.values[-self.N:] - pp = np.polyfit(range(self.N), last_values, 1) - slope = pp[0] / self.N - - if slope > 0: - return True - return False - - spsa = SPSA(maxiter=200, termination_checker=TerminationChecker(10)) - parameters, value, niter = spsa.optimize(2, objective, initial_point=[0.5, 0.5]) - print(f'SPSA completed after {niter} iterations') - - - References: - - [1]: J. C. Spall (1998). An Overview of the Simultaneous Perturbation Method for Efficient - Optimization, Johns Hopkins APL Technical Digest, 19(4), 482–492. - `Online at jhuapl.edu. `_ - - [2]: J. C. Spall (1997). Accelerated second-order stochastic optimization using only - function measurements, Proceedings of the 36th IEEE Conference on Decision and Control, - 1417-1424 vol.2. `Online at IEEE.org. `_ - - [3]: A. Kandala et al. (2017). Hardware-efficient Variational Quantum Eigensolver for - Small Molecules and Quantum Magnets. Nature 549, pages242–246(2017). - `arXiv:1704.05018v2 `_ - - """ - - def __init__( - self, - maxiter: int = 100, - blocking: bool = False, - allowed_increase: float | None = None, - trust_region: bool = False, - learning_rate: float | np.ndarray | Callable[[], Iterator] | None = None, - perturbation: float | np.ndarray | Callable[[], Iterator] | None = None, - last_avg: int = 1, - resamplings: int | dict[int, int] = 1, - perturbation_dims: int | None = None, - second_order: bool = False, - regularization: float | None = None, - hessian_delay: int = 0, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - initial_hessian: np.ndarray | None = None, - callback: CALLBACK | None = None, - termination_checker: TERMINATIONCHECKER | None = None, - ) -> None: - r""" - Args: - maxiter: The maximum number of iterations. Note that this is not the maximal number - of function evaluations. - blocking: If True, only accepts updates that improve the loss (up to some allowed - increase, see next argument). - allowed_increase: If ``blocking`` is ``True``, this argument determines by how much - the loss can increase with the proposed parameters and still be accepted. - If ``None``, the allowed increases is calibrated automatically to be twice the - approximated standard deviation of the loss function. - trust_region: If ``True``, restricts the norm of the update step to be :math:`\leq 1`. - learning_rate: The update step is the learning rate is multiplied with the gradient. - If the learning rate is a float, it remains constant over the course of the - optimization. If a NumPy array, the :math:`i`-th element is the learning rate for - the :math:`i`-th iteration. It can also be a callable returning an iterator which - yields the learning rates for each optimization step. - If ``learning_rate`` is set ``perturbation`` must also be provided. - perturbation: Specifies the magnitude of the perturbation for the finite difference - approximation of the gradients. See ``learning_rate`` for the supported types. - If ``perturbation`` is set ``learning_rate`` must also be provided. - last_avg: Return the average of the ``last_avg`` parameters instead of just the - last parameter values. - resamplings: The number of times the gradient (and Hessian) is sampled using a random - direction to construct a gradient estimate. Per default the gradient is estimated - using only one random direction. If an integer, all iterations use the same number - of resamplings. If a dictionary, this is interpreted as - ``{iteration: number of resamplings per iteration}``. - perturbation_dims: The number of perturbed dimensions. Per default, all dimensions - are perturbed, but a smaller, fixed number can be perturbed. If set, the perturbed - dimensions are chosen uniformly at random. - second_order: If True, use 2-SPSA instead of SPSA. In 2-SPSA, the Hessian is estimated - additionally to the gradient, and the gradient is preconditioned with the inverse - of the Hessian to improve convergence. - regularization: To ensure the preconditioner is symmetric and positive definite, the - identity times a small coefficient is added to it. This generator yields that - coefficient. - hessian_delay: Start multiplying the gradient with the inverse Hessian only after a - certain number of iterations. The Hessian is still evaluated and therefore this - argument can be useful to first get a stable average over the last iterations before - using it as preconditioner. - lse_solver: The method to solve for the inverse of the Hessian. Per default an - exact LSE solver is used, but can e.g. be overwritten by a minimization routine. - initial_hessian: The initial guess for the Hessian. By default the identity matrix - is used. - callback: A callback function passed information in each iteration step. The - information is, in this order: the number of function evaluations, the parameters, - the function value, the stepsize, whether the step was accepted. - termination_checker: A callback function executed at the end of each iteration step. The - arguments are, in this order: the parameters, the function value, the number - of function evaluations, the stepsize, whether the step was accepted. If the callback - returns True, the optimization is terminated. - To prevent additional evaluations of the objective method, if the objective has not yet - been evaluated, the objective is estimated by taking the mean of the objective - evaluations used in the estimate of the gradient. - - - Raises: - ValueError: If ``learning_rate`` or ``perturbation`` is an array with less elements - than the number of iterations. - - - """ - super().__init__() - - # general optimizer arguments - self.maxiter = maxiter - self.trust_region = trust_region - self.callback = callback - self.termination_checker = termination_checker - - # if learning rate and perturbation are arrays, check they are sufficiently long - for attr, name in zip([learning_rate, perturbation], ["learning_rate", "perturbation"]): - if isinstance(attr, (list, np.ndarray)): - if len(attr) < maxiter: - raise ValueError(f"Length of {name} is smaller than maxiter ({maxiter}).") - - self.learning_rate = learning_rate - self.perturbation = perturbation - - # SPSA specific arguments - self.blocking = blocking - self.allowed_increase = allowed_increase - self.last_avg = last_avg - self.resamplings = resamplings - self.perturbation_dims = perturbation_dims - - # 2-SPSA specific arguments - if regularization is None: - regularization = 0.01 - - self.second_order = second_order - self.hessian_delay = hessian_delay - self.lse_solver = lse_solver - self.regularization = regularization - self.initial_hessian = initial_hessian - - # runtime arguments - self._nfev: int | None = None # the number of function evaluations - self._smoothed_hessian: np.ndarray | None = None # smoothed average of the Hessians - - @staticmethod - def calibrate( - loss: Callable[[np.ndarray], float], - initial_point: np.ndarray, - c: float = 0.2, - stability_constant: float = 0, - target_magnitude: float | None = None, # 2 pi / 10 - alpha: float = 0.602, - gamma: float = 0.101, - modelspace: bool = False, - max_evals_grouped: int = 1, - ) -> tuple[Callable, Callable]: - r"""Calibrate SPSA parameters with a powerseries as learning rate and perturbation coeffs. - - The powerseries are: - - .. math:: - - a_k = \frac{a}{(A + k + 1)^\alpha}, c_k = \frac{c}{(k + 1)^\gamma} - - Args: - loss: The loss function. - initial_point: The initial guess of the iteration. - c: The initial perturbation magnitude. - stability_constant: The value of `A`. - target_magnitude: The target magnitude for the first update step, defaults to - :math:`2\pi / 10`. - alpha: The exponent of the learning rate powerseries. - gamma: The exponent of the perturbation powerseries. - modelspace: Whether the target magnitude is the difference of parameter values - or function values (= model space). - max_evals_grouped: The number of grouped evaluations supported by the loss function. - Defaults to 1, i.e. no grouping. - - Returns: - tuple(generator, generator): A tuple of powerseries generators, the first one for the - learning rate and the second one for the perturbation. - """ - logger.info("SPSA: Starting calibration of learning rate and perturbation.") - if target_magnitude is None: - target_magnitude = 2 * np.pi / 10 - - dim = len(initial_point) - - # compute the average magnitude of the first step - steps = 25 - points = [] - for _ in range(steps): - # compute the random directon - pert = bernoulli_perturbation(dim) - points += [initial_point + c * pert, initial_point - c * pert] - - losses = _batch_evaluate(loss, points, max_evals_grouped) - - avg_magnitudes = 0.0 - for i in range(steps): - delta = losses[2 * i] - losses[2 * i + 1] - avg_magnitudes += np.abs(delta / (2 * c)) - - avg_magnitudes /= steps - - if modelspace: - a = target_magnitude / (avg_magnitudes**2) - else: - a = target_magnitude / avg_magnitudes - - # compute the rescaling factor for correct first learning rate - if a < 1e-10: - warnings.warn(f"Calibration failed, using {target_magnitude} for `a`") - a = target_magnitude - - logger.info("Finished calibration:") - logger.info( - " -- Learning rate: a / ((A + n) ^ alpha) with a = %s, A = %s, alpha = %s", - a, - stability_constant, - alpha, - ) - logger.info(" -- Perturbation: c / (n ^ gamma) with c = %s, gamma = %s", c, gamma) - - # set up the powerseries - def learning_rate(): - return powerseries(a, alpha, stability_constant) - - def perturbation(): - return powerseries(c, gamma) - - return learning_rate, perturbation - - @staticmethod - def estimate_stddev( - loss: Callable[[np.ndarray], float], - initial_point: np.ndarray, - avg: int = 25, - max_evals_grouped: int = 1, - ) -> float: - """Estimate the standard deviation of the loss function.""" - losses = _batch_evaluate(loss, avg * [initial_point], max_evals_grouped) - return np.std(losses) - - @property - def settings(self) -> dict[str, Any]: - # if learning rate or perturbation are custom iterators expand them - if callable(self.learning_rate): - iterator = self.learning_rate() - learning_rate = np.array([next(iterator) for _ in range(self.maxiter)]) - else: - learning_rate = self.learning_rate - - if callable(self.perturbation): - iterator = self.perturbation() - perturbation = np.array([next(iterator) for _ in range(self.maxiter)]) - else: - perturbation = self.perturbation - - return { - "maxiter": self.maxiter, - "learning_rate": learning_rate, - "perturbation": perturbation, - "trust_region": self.trust_region, - "blocking": self.blocking, - "allowed_increase": self.allowed_increase, - "resamplings": self.resamplings, - "perturbation_dims": self.perturbation_dims, - "second_order": self.second_order, - "hessian_delay": self.hessian_delay, - "regularization": self.regularization, - "lse_solver": self.lse_solver, - "initial_hessian": self.initial_hessian, - "callback": self.callback, - "termination_checker": self.termination_checker, - } - - def _point_sample(self, loss, x, eps, delta1, delta2): - """A single sample of the gradient at position ``x`` in direction ``delta``.""" - # points to evaluate - points = [x + eps * delta1, x - eps * delta1] - self._nfev += 2 - - if self.second_order: - points += [x + eps * (delta1 + delta2), x + eps * (-delta1 + delta2)] - self._nfev += 2 - - # batch evaluate the points (if possible) - values = _batch_evaluate(loss, points, self._max_evals_grouped) - - plus = values[0] - minus = values[1] - gradient_sample = (plus - minus) / (2 * eps) * delta1 - - hessian_sample = None - if self.second_order: - diff = (values[2] - plus) - (values[3] - minus) - diff /= 2 * eps**2 - - rank_one = np.outer(delta1, delta2) - hessian_sample = diff * (rank_one + rank_one.T) / 2 - - return np.mean(values), gradient_sample, hessian_sample - - def _point_estimate(self, loss, x, eps, num_samples): - """The gradient estimate at point x.""" - # set up variables to store averages - value_estimate = 0 - gradient_estimate = np.zeros(x.size) - hessian_estimate = np.zeros((x.size, x.size)) - - # iterate over the directions - deltas1 = [ - bernoulli_perturbation(x.size, self.perturbation_dims) for _ in range(num_samples) - ] - - if self.second_order: - deltas2 = [ - bernoulli_perturbation(x.size, self.perturbation_dims) for _ in range(num_samples) - ] - else: - deltas2 = None - - for i in range(num_samples): - delta1 = deltas1[i] - delta2 = deltas2[i] if self.second_order else None - - value_sample, gradient_sample, hessian_sample = self._point_sample( - loss, x, eps, delta1, delta2 - ) - value_estimate += value_sample - gradient_estimate += gradient_sample - - if self.second_order: - hessian_estimate += hessian_sample - - return ( - value_estimate / num_samples, - gradient_estimate / num_samples, - hessian_estimate / num_samples, - ) - - def _compute_update(self, loss, x, k, eps, lse_solver): - # compute the perturbations - if isinstance(self.resamplings, dict): - num_samples = self.resamplings.get(k, 1) - else: - num_samples = self.resamplings - - # accumulate the number of samples - value, gradient, hessian = self._point_estimate(loss, x, eps, num_samples) - - # precondition gradient with inverse Hessian, if specified - if self.second_order: - smoothed = k / (k + 1) * self._smoothed_hessian + 1 / (k + 1) * hessian - self._smoothed_hessian = smoothed - - if k > self.hessian_delay: - spd_hessian = _make_spd(smoothed, self.regularization) - - # solve for the gradient update - gradient = np.real(lse_solver(spd_hessian, gradient)) - - return value, gradient - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - # ensure learning rate and perturbation are correctly set: either none or both - # this happens only here because for the calibration the loss function is required - if self.learning_rate is None and self.perturbation is None: - get_eta, get_eps = self.calibrate(fun, x0, max_evals_grouped=self._max_evals_grouped) - else: - get_eta, get_eps = _validate_pert_and_learningrate( - self.perturbation, self.learning_rate - ) - eta, eps = get_eta(), get_eps() - - if self.lse_solver is None: - lse_solver = np.linalg.solve - else: - lse_solver = self.lse_solver - - # prepare some initials - x = np.asarray(x0) - if self.initial_hessian is None: - self._smoothed_hessian = np.identity(x.size) - else: - self._smoothed_hessian = self.initial_hessian - - self._nfev = 0 - - # if blocking is enabled we need to keep track of the function values - if self.blocking: - fx = fun(x) - - self._nfev += 1 - if self.allowed_increase is None: - self.allowed_increase = 2 * self.estimate_stddev( - fun, x, max_evals_grouped=self._max_evals_grouped - ) - - logger.info("SPSA: Starting optimization.") - start = time() - - # keep track of the last few steps to return their average - last_steps = deque([x]) - - # use a local variable and while loop to keep track of the number of iterations - # if the termination checker terminates early - k = 0 - while k < self.maxiter: - k += 1 - iteration_start = time() - # compute update - fx_estimate, update = self._compute_update(fun, x, k, next(eps), lse_solver) - - # trust region - if self.trust_region: - norm = np.linalg.norm(update) - if norm > 1: # stop from dividing by 0 - update = update / norm - - # compute next parameter value - update = update * next(eta) - x_next = x - update - fx_next = None - - # blocking - if self.blocking: - self._nfev += 1 - fx_next = fun(x_next) - - if fx + self.allowed_increase <= fx_next: # accept only if loss improved - if self.callback is not None: - self.callback( - self._nfev, # number of function evals - x_next, # next parameters - fx_next, # loss at next parameters - np.linalg.norm(update), # size of the update step - False, - ) # not accepted - - logger.info( - "Iteration %s/%s rejected in %s.", - k, - self.maxiter + 1, - time() - iteration_start, - ) - continue - fx = fx_next - - logger.info( - "Iteration %s/%s done in %s.", k, self.maxiter + 1, time() - iteration_start - ) - - if self.callback is not None: - # if we didn't evaluate the function yet, do it now - if not self.blocking: - self._nfev += 1 - fx_next = fun(x_next) - - self.callback( - self._nfev, # number of function evals - x_next, # next parameters - fx_next, # loss at next parameters - np.linalg.norm(update), # size of the update step - True, - ) # accepted - - # update parameters - x = x_next - - # update the list of the last ``last_avg`` parameters - if self.last_avg > 1: - last_steps.append(x_next) - if len(last_steps) > self.last_avg: - last_steps.popleft() - - if self.termination_checker is not None: - fx_check = fx_estimate if fx_next is None else fx_next - if self.termination_checker( - self._nfev, x_next, fx_check, np.linalg.norm(update), True - ): - logger.info("terminated optimization at {k}/{self.maxiter} iterations") - break - - logger.info("SPSA: Finished in %s", time() - start) - - if self.last_avg > 1: - x = np.mean(last_steps, axis=0) - - result = OptimizerResult() - result.x = x - result.fun = fun(x) - result.nfev = self._nfev - result.nit = k - - return result - - def get_support_level(self): - """Get the support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.ignored, - "initial_point": OptimizerSupportLevel.required, - } - - # pylint: disable=bad-docstring-quotes - @deprecate_func( - additional_msg=( - "Instead, use ``SPSA.minimize`` as a replacement, which supports the same arguments " - "but follows the interface of scipy.optimize and returns a complete result object " - "containing additional information." - ), - since="0.21.0", - ) - def optimize( - self, - num_vars, # pylint: disable=unused-argument - objective_function, - gradient_function=None, # pylint: disable=unused-argument - variable_bounds=None, # pylint: disable=unused-argument - initial_point=None, - ): - """Perform optimization. - - Args: - num_vars (int): Number of parameters to be optimized. - objective_function (callable): A function that computes the objective function. - gradient_function (callable): Not supported for SPSA. - variable_bounds (list[(float, float)]): Not supported for SPSA. - initial_point (numpy.ndarray[float]): Initial point. - - Returns: - tuple: point, value, nfev - point: is a 1D numpy.ndarray[float] containing the solution - value: is a float with the objective function value - nfev: number of objective function calls made if available or None - """ - result = self.minimize(objective_function, initial_point) - return result.x, result.fun, result.nfev - - -def bernoulli_perturbation(dim, perturbation_dims=None): - """Get a Bernoulli random perturbation.""" - if perturbation_dims is None: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - return 1 - 2 * algorithm_globals.random.binomial(1, 0.5, size=dim) - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - pert = 1 - 2 * algorithm_globals.random.binomial(1, 0.5, size=perturbation_dims) - indices = algorithm_globals.random.choice( - list(range(dim)), size=perturbation_dims, replace=False - ) - result = np.zeros(dim) - result[indices] = pert - - return result - - -def powerseries(eta=0.01, power=2, offset=0): - """Yield a series decreasing by a powerlaw.""" - - n = 1 - while True: - yield eta / ((n + offset) ** power) - n += 1 - - -def constant(eta=0.01): - """Yield a constant series.""" - - while True: - yield eta - - -def _batch_evaluate(function, points, max_evals_grouped, unpack_points=False): - """Evaluate a function on all points with batches of max_evals_grouped. - - The points are a list of inputs, as ``[in1, in2, in3, ...]``. If the individual - inputs are tuples (because the function takes multiple inputs), set ``unpack_points`` to ``True``. - """ - - # if the function cannot handle lists of points as input, cover this case immediately - if max_evals_grouped is None or max_evals_grouped == 1: - # support functions with multiple arguments where the points are given in a tuple - return [ - function(*point) if isinstance(point, tuple) else function(point) for point in points - ] - - num_points = len(points) - - # get the number of batches - num_batches = num_points // max_evals_grouped - if num_points % max_evals_grouped != 0: - num_batches += 1 - - # split the points - batched_points = np.array_split(np.asarray(points), num_batches) - - results = [] - for batch in batched_points: - if unpack_points: - batch = _repack_points(batch) - results += _as_list(function(*batch)) - else: - results += _as_list(function(batch)) - - return results - - -def _as_list(obj): - """Convert a list or numpy array into a list.""" - return obj.tolist() if isinstance(obj, np.ndarray) else obj - - -def _repack_points(points): - """Turn a list of tuples of points into a tuple of lists of points. - E.g. turns - [(a1, a2, a3), (b1, b2, b3)] - into - ([a1, b1], [a2, b2], [a3, b3]) - where all elements are np.ndarray. - """ - num_sets = len(points[0]) # length of (a1, a2, a3) - return ([x[i] for x in points] for i in range(num_sets)) - - -def _make_spd(matrix, bias=0.01): - identity = np.identity(matrix.shape[0]) - psd = scipy.linalg.sqrtm(matrix.dot(matrix)) - return psd + bias * identity - - -def _validate_pert_and_learningrate(perturbation, learning_rate): - if learning_rate is None or perturbation is None: - raise ValueError("If one of learning rate or perturbation is set, both must be set.") - - if isinstance(perturbation, float): - - def get_eps(): - return constant(perturbation) - - elif isinstance(perturbation, (list, np.ndarray)): - - def get_eps(): - return iter(perturbation) - - else: - get_eps = perturbation - - if isinstance(learning_rate, float): - - def get_eta(): - return constant(learning_rate) - - elif isinstance(learning_rate, (list, np.ndarray)): - - def get_eta(): - return iter(learning_rate) - - else: - get_eta = learning_rate - - return get_eta, get_eps diff --git a/qiskit/algorithms/optimizers/steppable_optimizer.py b/qiskit/algorithms/optimizers/steppable_optimizer.py deleted file mode 100644 index 0c2e73ce5909..000000000000 --- a/qiskit/algorithms/optimizers/steppable_optimizer.py +++ /dev/null @@ -1,303 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""SteppableOptimizer interface""" -from __future__ import annotations - -from abc import abstractmethod, ABC -from collections.abc import Callable -from dataclasses import dataclass -from .optimizer import Optimizer, POINT, OptimizerResult - - -@dataclass -class AskData(ABC): - """Base class for return type of :meth:`~.SteppableOptimizer.ask`. - - Args: - x_fun: Point or list of points where the function needs to be evaluated to compute the next - state of the optimizer. - x_jac: Point or list of points where the gradient/jacobian needs to be evaluated to compute - the next state of the optimizer. - - """ - - x_fun: POINT | list[POINT] | None = None - x_jac: POINT | list[POINT] | None = None - - -@dataclass -class TellData(ABC): - """Base class for argument type of :meth:`~.SteppableOptimizer.tell`. - - Args: - eval_fun: Image of the function at :attr:`~.ask_data.x_fun`. - eval_jac: Image of the gradient-jacobian at :attr:`~.ask_data.x_jac`. - - """ - - eval_fun: float | list[float] | None = None - eval_jac: POINT | list[POINT] | None = None - - -@dataclass -class OptimizerState: - """Base class representing the state of the optimizer. - - This class stores the current state of the optimizer, given by the current point and - (optionally) information like the function value, the gradient or the number of - function evaluations. This dataclass can also store any other individual variables that - change during the optimization. - - """ - - x: POINT - """Current optimization parameters.""" - fun: Callable[[POINT], float] | None - """Function being optimized.""" - jac: Callable[[POINT], POINT] | None - """Jacobian of the function being optimized.""" - nfev: int | None - """Number of function evaluations so far in the optimization.""" - njev: int | None - """Number of jacobian evaluations so far in the opimization.""" - nit: int | None - """Number of optmization steps performed so far in the optimization.""" - - -class SteppableOptimizer(Optimizer): - """ - Base class for a steppable optimizer. - - This family of optimizers uses the `ask and tell interface - `_. - When using this interface the user has to call :meth:`~.ask` to get information about - how to evaluate the fucntion (we are asking the optimizer about how to do the evaluation). - This information is typically the next points at which the function is evaluated, but depending - on the optimizer it can also determine whether to evaluate the function or its gradient. - Once the function has been evaluated, the user calls the method :meth:`~..tell` - to tell the optimizer what the result of the function evaluation(s) is. The optimizer then - updates its state accordingly and the user can decide whether to stop the optimization process - or to repeat a step. - - This interface is more customizable, and allows the user to have full control over the evaluation - of the function. - - Examples: - - An example where the evaluation of the function has a chance of failing. The user, with - specific knowledge about his function can catch this errors and handle them before passing - the result to the optimizer. - - .. code-block:: python - - import random - import numpy as np - from qiskit.algorithms.optimizers import GradientDescent - - def objective(x): - if random.choice([True, False]): - return None - else: - return (np.linalg.norm(x) - 1) ** 2 - - def grad(x): - if random.choice([True, False]): - return None - else: - return 2 * (np.linalg.norm(x) - 1) * x / np.linalg.norm(x) - - - initial_point = np.random.normal(0, 1, size=(100,)) - - optimizer = GradientDescent(maxiter=20) - optimizer.start(x0=initial_point, fun=objective, jac=grad) - - while optimizer.continue_condition(): - ask_data = optimizer.ask() - evaluated_gradient = None - - while evaluated_gradient is None: - evaluated_gradient = grad(ask_data.x_center) - optimizer.state.njev += 1 - - optmizer.state.nit += 1 - - cf = TellData(eval_jac=evaluated_gradient) - optimizer.tell(ask_data=ask_data, tell_data=tell_data) - - result = optimizer.create_result() - - - Users that aren't dealing with complicated functions and who are more familiar with step by step - optimization algorithms can use the :meth:`~.step` method which wraps the :meth:`~.ask` - and :meth:`~.tell` methods. In the same spirit the method :meth:`~.minimize` will optimize the - function and return the result. - - To see other libraries that use this interface one can visit: - https://optuna.readthedocs.io/en/stable/tutorial/20_recipes/009_ask_and_tell.html - - - """ - - def __init__( - self, - maxiter: int = 100, - ): - """ - Args: - maxiter: Number of steps in the optimization process before ending the loop. - """ - super().__init__() - self._state: OptimizerState | None = None - self.maxiter = maxiter - - @property - def state(self) -> OptimizerState: - """Return the current state of the optimizer.""" - return self._state - - @state.setter - def state(self, state: OptimizerState) -> None: - """Set the current state of the optimizer.""" - self._state = state - - def ask(self) -> AskData: - """Ask the optimizer for a set of points to evaluate. - - This method asks the optimizer which are the next points to evaluate. - These points can, e.g., correspond to function values and/or its derivative. - It may also correspond to variables that let the user infer which points to evaluate. - It is the first method inside of a :meth:`~.step` in the optimization process. - - Returns: - An object containing the data needed to make the funciton evaluation to advance the - optimization process. - - """ - raise NotImplementedError - - def tell(self, ask_data: AskData, tell_data: TellData) -> None: - """Updates the optimization state using the results of the function evaluation. - - A canonical optimization example using :meth:`~.ask` and :meth:`~.tell` can be seen - in :meth:`~.step`. - - Args: - ask_data: Contains the information on how the evaluation was done. - tell_data: Contains all relevant information about the evaluation of the objective - function. - """ - raise NotImplementedError - - @abstractmethod - def evaluate(self, ask_data: AskData) -> TellData: - """Evaluates the function according to the instructions contained in :attr:`~.ask_data`. - - If the user decides to use :meth:`~.step` instead of :meth:`~.ask` and :meth:`~.tell` - this function will contain the logic on how to evaluate the function. - - Args: - ask_data: Contains the information on how to do the evaluation. - - Returns: - Data of all relevant information about the function evaluation. - - """ - raise NotImplementedError - - def _callback_wrapper(self) -> None: - """ - Wraps the callback function to accomodate each optimizer. - """ - pass - - def step(self) -> None: - """Performs one step in the optimization process. - - This method composes :meth:`~.ask`, :meth:`~.evaluate`, and :meth:`~.tell` to make a "step" - in the optimization process. - """ - ask_data = self.ask() - tell_data = self.evaluate(ask_data=ask_data) - self.tell(ask_data=ask_data, tell_data=tell_data) - - # pylint: disable=invalid-name - @abstractmethod - def start( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> None: - """Populates the state of the optimizer with the data provided and sets all the counters to 0. - - Args: - fun: Function to minimize. - x0: Initial point. - jac: Function to compute the gradient. - bounds: Bounds of the search space. - - """ - raise NotImplementedError - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - """Minimizes the function. - - For well behaved functions the user can call this method to minimize a function. - If the user wants more control on how to evaluate the function a custom loop can be - created using :meth:`~.ask` and :meth:`~.tell` and evaluating the function manually. - - Args: - fun: Function to minimize. - x0: Initial point. - jac: Function to compute the gradient. - bounds: Bounds of the search space. - - Returns: - Object containing the result of the optimization. - - """ - self.start(x0=x0, fun=fun, jac=jac, bounds=bounds) - while self.continue_condition(): - self.step() - self._callback_wrapper() - return self.create_result() - - @abstractmethod - def create_result(self) -> OptimizerResult: - """Returns the result of the optimization. - - All the information needed to create such a result should be stored in the optimizer state - and will typically contain the best point found, the function value and gradient at that point, - the number of function and gradient evaluation and the number of iterations in the optimization. - - Returns: - The result of the optimization process. - - """ - raise NotImplementedError - - def continue_condition(self) -> bool: - """Condition that indicates the optimization process should continue. - - Returns: - ``True`` if the optimization process should continue, ``False`` otherwise. - """ - return self.state.nit < self.maxiter diff --git a/qiskit/algorithms/optimizers/tnc.py b/qiskit/algorithms/optimizers/tnc.py deleted file mode 100644 index 06174e51ace9..000000000000 --- a/qiskit/algorithms/optimizers/tnc.py +++ /dev/null @@ -1,83 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Truncated Newton (TNC) optimizer.""" -from __future__ import annotations - - -from .scipy_optimizer import SciPyOptimizer - - -class TNC(SciPyOptimizer): - """ - Truncated Newton (TNC) optimizer. - - TNC uses a truncated Newton algorithm to minimize a function with variables subject to bounds. - This algorithm uses gradient information; it is also called Newton Conjugate-Gradient. - It differs from the :class:`CG` method as it wraps a C implementation and allows each variable - to be given upper and lower bounds. - - Uses scipy.optimize.minimize TNC - For further detail, please refer to - See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "disp", "accuracy", "ftol", "xtol", "gtol", "eps"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int = 100, - disp: bool = False, - accuracy: float = 0, - ftol: float = -1, - xtol: float = -1, - gtol: float = -1, - tol: float | None = None, - eps: float = 1e-08, - options: dict | None = None, - max_evals_grouped: int = 1, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum number of function evaluation. - disp: Set to True to print convergence messages. - accuracy: Relative precision for finite difference calculations. - If <= machine_precision, set to sqrt(machine_precision). Defaults to 0. - ftol: Precision goal for the value of f in the stopping criterion. - If ftol < 0.0, ftol is set to 0.0 defaults to -1. - xtol: Precision goal for the value of x in the stopping criterion - (after applying x scaling factors). - If xtol < 0.0, xtol is set to sqrt(machine_precision). Defaults to -1. - gtol: Precision goal for the value of the projected gradient in - the stopping criterion (after applying x scaling factors). - If gtol < 0.0, gtol is set to 1e-2 * sqrt(accuracy). - Setting it to 0.0 is not recommended. Defaults to -1. - tol: Tolerance for termination. - eps: Step size used for numerical approximation of the Jacobian. - options: A dictionary of solver options. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__( - "TNC", - options=options, - tol=tol, - max_evals_grouped=max_evals_grouped, - **kwargs, - ) diff --git a/qiskit/algorithms/optimizers/umda.py b/qiskit/algorithms/optimizers/umda.py deleted file mode 100644 index f749f9853a3b..000000000000 --- a/qiskit/algorithms/optimizers/umda.py +++ /dev/null @@ -1,355 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Univariate Marginal Distribution Algorithm (Estimation-of-Distribution-Algorithm).""" - -from __future__ import annotations - -import warnings - -from collections.abc import Callable -from typing import Any -import numpy as np -from scipy.stats import norm -from qiskit.utils import algorithm_globals - -from .optimizer import OptimizerResult, POINT -from .scipy_optimizer import Optimizer, OptimizerSupportLevel - - -class UMDA(Optimizer): - """Continuous Univariate Marginal Distribution Algorithm (UMDA). - - UMDA [1] is a specific type of Estimation of Distribution Algorithm (EDA) where new individuals - are sampled from univariate normal distributions and are updated in each iteration of the - algorithm by the best individuals found in the previous iteration. - - .. seealso:: - - This original implementation of the UDMA optimizer for Qiskit was inspired by my - (Vicente P. Soloviev) work on the EDAspy Python package [2]. - - EDAs are stochastic search algorithms and belong to the family of the evolutionary algorithms. - The main difference is that EDAs have a probabilistic model which is updated in each iteration - from the best individuals of previous generations (elite selection). Depending on the complexity - of the probabilistic model, EDAs can be classified in different ways. In this case, UMDA is a - univariate EDA as the embedded probabilistic model is univariate. - - UMDA has been compared to some of the already implemented algorithms in Qiskit library to - optimize the parameters of variational algorithms such as QAOA or VQE and competitive results - have been obtained [1]. UMDA seems to provide very good solutions for those circuits in which - the number of layers is not big. - - The optimization process can be personalized depending on the paremeters chosen in the - initialization. The main parameter is the population size. The bigger it is, the final result - will be better. However, this increases the complexity of the algorithm and the runtime will - be much heavier. In the work [1] different experiments have been performed where population - size has been set to 20 - 30. - - .. note:: - - The UMDA implementation has more parameters but these have default values for the - initialization for better understanding of the user. For example, ``\alpha`` parameter has - been set to 0.5 and is the percentage of the population which is selected in each iteration - to update the probabilistic model. - - - Example: - - This short example runs UMDA to optimize the parameters of a variational algorithm. Here we - will use the same operator as used in the algorithms introduction, which was originally - computed by Qiskit Nature for an H2 molecule. The minimum energy of the H2 Hamiltonian can - be found quite easily so we are able to set maxiters to a small value. - - .. code-block:: python - - from qiskit.opflow import X, Z, I - from qiskit import Aer - from qiskit.algorithms.optimizers import UMDA - from qiskit.algorithms import QAOA - from qiskit.utils import QuantumInstance - - - H2_op = (-1.052373245772859 * I ^ I) + \ - (0.39793742484318045 * I ^ Z) + \ - (-0.39793742484318045 * Z ^ I) + \ - (-0.01128010425623538 * Z ^ Z) + \ - (0.18093119978423156 * X ^ X) - - p = 2 # Toy example: 2 layers with 2 parameters in each layer: 4 variables - - opt = UMDA(maxiter=100, size_gen=20) - - backend = Aer.get_backend('statevector_simulator') - vqe = QAOA(opt, - quantum_instance=QuantumInstance(backend=backend), - reps=p) - - result = vqe.compute_minimum_eigenvalue(operator=H2_op) - - If it is desired to modify the percentage of individuals considered to update the - probabilistic model, then this code can be used. Here for example we set the 60% instead - of the 50% predefined. - - .. code-block:: python - - opt = UMDA(maxiter=100, size_gen=20, alpha = 0.6) - - backend = Aer.get_backend('statevector_simulator') - vqe = QAOA(opt, - quantum_instance=QuantumInstance(backend=backend), - reps=p) - - result = vqe.compute_minimum_eigenvalue(operator=qubit_op) - - - References: - - [1]: Vicente P. Soloviev, Pedro Larrañaga and Concha Bielza (2022, July). Quantum Parametric - Circuit Optimization with Estimation of Distribution Algorithms. In 2022 The Genetic and - Evolutionary Computation Conference (GECCO). DOI: https://doi.org/10.1145/3520304.3533963 - - [2]: Vicente P. Soloviev. Python package EDAspy. - https://github.com/VicentePerezSoloviev/EDAspy. - """ - - ELITE_FACTOR = 0.4 - STD_BOUND = 0.3 - - def __init__( - self, - maxiter: int = 100, - size_gen: int = 20, - alpha: float = 0.5, - callback: Callable[[int, np.array, float], None] | None = None, - ) -> None: - r""" - Args: - maxiter: Maximum number of iterations. - size_gen: Population size of each generation. - alpha: Percentage (0, 1] of the population to be selected as elite selection. - callback: A callback function passed information in each iteration step. The - information is, in this order: the number of function evaluations, the parameters, - the best function value in this iteration. - """ - - self.size_gen = size_gen - self.maxiter = maxiter - self.alpha = alpha - self._vector: np.ndarray | None = None - # initialization of generation - self._generation: np.ndarray | None = None - self._dead_iter = int(self._maxiter / 5) - - self._truncation_length = int(size_gen * alpha) - - super().__init__() - - self._best_cost_global: float | None = None - self._best_ind_global: int | None = None - self._evaluations: np.ndarray | None = None - - self._n_variables: int | None = None - - self.callback = callback - - def _initialization(self) -> np.ndarray: - vector = np.zeros((4, self._n_variables)) - - vector[0, :] = np.pi # mu - vector[1, :] = 0.5 # std - - return vector - - # build a generation of size SIZE_GEN from prob vector - def _new_generation(self): - """Build a new generation sampled from the vector of probabilities. - Updates the generation pandas dataframe - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - gen = algorithm_globals.random.normal( - self._vector[0, :], self._vector[1, :], [self._size_gen, self._n_variables] - ) - - self._generation = self._generation[: int(self.ELITE_FACTOR * len(self._generation))] - self._generation = np.vstack((self._generation, gen)) - - # truncate the generation at alpha percent - def _truncation(self): - """Selection of the best individuals of the actual generation. - Updates the generation by selecting the best individuals. - """ - best_indices = self._evaluations.argsort()[: self._truncation_length] - self._generation = self._generation[best_indices, :] - self._evaluations = np.take(self._evaluations, best_indices) - - # check each individual of the generation - def _check_generation(self, objective_function): - """Check the cost of each individual in the cost function implemented by the user.""" - self._evaluations = np.apply_along_axis(objective_function, 1, self._generation) - - # update the probability vector - def _update_vector(self): - """From the best individuals update the vector of normal distributions in order to the next - generation can sample from it. Update the vector of normal distributions - """ - - for i in range(self._n_variables): - self._vector[0, i], self._vector[1, i] = norm.fit(self._generation[:, i]) - if self._vector[1, i] < self.STD_BOUND: - self._vector[1, i] = self.STD_BOUND - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - - not_better_count = 0 - result = OptimizerResult() - - if isinstance(x0, float): - x0 = [x0] - self._n_variables = len(x0) - - self._best_cost_global = 999999999999 - self._best_ind_global = 9999999 - history = [] - self._evaluations = np.array(0) - - self._vector = self._initialization() - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - # initialization of generation - self._generation = algorithm_globals.random.normal( - self._vector[0, :], self._vector[1, :], [self._size_gen, self._n_variables] - ) - - for _ in range(self._maxiter): - self._check_generation(fun) - self._truncation() - self._update_vector() - - best_mae_local: float = min(self._evaluations) - - history.append(best_mae_local) - best_ind_local = np.where(self._evaluations == best_mae_local)[0][0] - best_ind_local = self._generation[best_ind_local] - - # update the best values ever - if best_mae_local < self._best_cost_global: - self._best_cost_global = best_mae_local - self._best_ind_global = best_ind_local - not_better_count = 0 - - else: - not_better_count += 1 - if not_better_count >= self._dead_iter: - break - - if self.callback is not None: - self.callback( - len(history) * self._size_gen, self._best_ind_global, self._best_cost_global - ) - - self._new_generation() - - result.x = self._best_ind_global - result.fun = self._best_cost_global - result.nfev = len(history) * self._size_gen - - return result - - @property - def size_gen(self) -> int: - """Returns the size of the generations (number of individuals per generation)""" - return self._size_gen - - @size_gen.setter - def size_gen(self, value: int): - """ - Sets the size of the generations of the algorithm. - - Args: - value: Size of the generations (number of individuals per generation). - - Raises: - ValueError: If `value` is lower than 1. - """ - if value <= 0: - raise ValueError("The size of the generation should be greater than 0.") - self._size_gen = value - - @property - def maxiter(self) -> int: - """Returns the maximum number of iterations""" - return self._maxiter - - @maxiter.setter - def maxiter(self, value: int): - """ - Sets the maximum number of iterations of the algorithm. - - Args: - value: Maximum number of iterations of the algorithm. - - Raises: - ValueError: If `value` is lower than 1. - """ - if value <= 0: - raise ValueError("The maximum number of iterations should be greater than 0.") - - self._maxiter = value - - @property - def alpha(self) -> float: - """Returns the alpha parameter value (percentage of population selected to update - probabilistic model)""" - return self._alpha - - @alpha.setter - def alpha(self, value: float): - """ - Sets the alpha parameter (percentage of individuals selected to update the probabilistic - model) - - Args: - value: Percentage (0,1] of generation selected to update the probabilistic model. - - Raises: - ValueError: If `value` is lower than 0 or greater than 1. - """ - if (value <= 0) or (value > 1): - raise ValueError(f"alpha must be in the range (0, 1], value given was {value}") - - self._alpha = value - - @property - def settings(self) -> dict[str, Any]: - return { - "maxiter": self.maxiter, - "alpha": self.alpha, - "size_gen": self.size_gen, - "callback": self.callback, - } - - def get_support_level(self): - """Get the support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.ignored, - "initial_point": OptimizerSupportLevel.required, - } diff --git a/qiskit/algorithms/phase_estimators/__init__.py b/qiskit/algorithms/phase_estimators/__init__.py deleted file mode 100644 index 2ef5b089aaed..000000000000 --- a/qiskit/algorithms/phase_estimators/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Phase Estimators.""" - -from .phase_estimator import PhaseEstimator -from .phase_estimation import PhaseEstimation -from .phase_estimation_result import PhaseEstimationResult -from .phase_estimation_scale import PhaseEstimationScale -from .hamiltonian_phase_estimation import HamiltonianPhaseEstimation -from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult -from .ipe import IterativePhaseEstimation - -__all__ = [ - "PhaseEstimator", - "PhaseEstimation", - "PhaseEstimationResult", - "PhaseEstimationScale", - "HamiltonianPhaseEstimation", - "HamiltonianPhaseEstimationResult", - "IterativePhaseEstimation", -] diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py deleted file mode 100644 index 87907c39e304..000000000000 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ /dev/null @@ -1,309 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# 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. - -"""Phase estimation for the spectrum of a Hamiltonian""" - -from __future__ import annotations - -import warnings - -from qiskit import QuantumCircuit -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg -from qiskit.opflow import ( - SummedOp, - PauliOp, - MatrixOp, - PauliSumOp, - StateFn, - EvolutionBase, - PauliTrotterEvolution, - I, -) -from qiskit.providers import Backend -from .phase_estimation import PhaseEstimation -from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult -from .phase_estimation_scale import PhaseEstimationScale -from ...circuit.library import PauliEvolutionGate -from ...primitives import BaseSampler -from ...quantum_info import SparsePauliOp, Statevector, Pauli -from ...synthesis import EvolutionSynthesis - - -class HamiltonianPhaseEstimation: - r"""Run the Quantum Phase Estimation algorithm to find the eigenvalues of a Hermitian operator. - - This class is nearly the same as :class:`~qiskit.algorithms.PhaseEstimation`, differing only - in that the input in that class is a unitary operator, whereas here the input is a Hermitian - operator from which a unitary will be obtained by scaling and exponentiating. The scaling is - performed in order to prevent the phases from wrapping around :math:`2\pi`. - The problem of estimating eigenvalues :math:`\lambda_j` of the Hermitian operator - :math:`H` is solved by running a circuit representing - - .. math:: - - \exp(i b H) |\psi\rangle = \sum_j \exp(i b \lambda_j) c_j |\lambda_j\rangle, - - where the input state is - - .. math:: - - |\psi\rangle = \sum_j c_j |\lambda_j\rangle, - - and :math:`\lambda_j` are the eigenvalues of :math:`H`. - - Here, :math:`b` is a scaling factor sufficiently large to map positive :math:`\lambda` to - :math:`[0,\pi)` and negative :math:`\lambda` to :math:`[\pi,2\pi)`. Each time the circuit is - run, one measures a phase corresponding to :math:`lambda_j` with probability :math:`|c_j|^2`. - - If :math:`H` is a Pauli sum, the bound :math:`b` is computed from the sum of the absolute - values of the coefficients of the terms. There is no way to reliably recover eigenvalues - from phases very near the endpoints of these intervals. Because of this you should be aware - that for degenerate cases, such as :math:`H=Z`, the eigenvalues :math:`\pm 1` will be - mapped to the same phase, :math:`\pi`, and so cannot be distinguished. In this case, you need - to specify a larger bound as an argument to the method ``estimate``. - - This class uses and works together with :class:`~qiskit.algorithms.PhaseEstimationScale` to - manage scaling the Hamiltonian and the phases that are obtained by the QPE algorithm. This - includes setting, or computing, a bound on the eigenvalues of the operator, using this - bound to obtain a scale factor, scaling the operator, and shifting and scaling the measured - phases to recover the eigenvalues. - - Note that, although we speak of "evolving" the state according the Hamiltonian, in the - present algorithm, we are not actually considering time evolution. Rather, the role of time is - played by the scaling factor, which is chosen to best extract the eigenvalues of the - Hamiltonian. - - A few of the ideas in the algorithm may be found in Ref. [1]. - - **Reference:** - - [1]: Quantum phase estimation of multiple eigenvalues for small-scale (noisy) experiments - T.E. O'Brien, B. Tarasinski, B.M. Terhal - `arXiv:1809.09697 `_ - - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - num_evaluation_qubits: int, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will - be estimated as a binary string with this many bits. - quantum_instance: Deprecated: The quantum instance on which - the circuit will be run. - sampler: The sampler primitive on which the circuit will be sampled. - """ - # Avoid double warning on deprecated used of `quantum_instance`. - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - self._phase_estimation = PhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, - quantum_instance=quantum_instance, - sampler=sampler, - ) - - def _get_scale(self, hamiltonian, bound=None) -> PhaseEstimationScale: - if bound is None: - return PhaseEstimationScale.from_pauli_sum(hamiltonian) - - return PhaseEstimationScale(bound) - - def _get_unitary( - self, hamiltonian, pe_scale, evolution: EvolutionSynthesis | EvolutionBase - ) -> QuantumCircuit: - """Evolve the Hamiltonian to obtain a unitary. - - Apply the scaling to the Hamiltonian that has been computed from an eigenvalue bound - and compute the unitary by applying the evolution object. - """ - - if self._phase_estimation._sampler is not None: - - evo = PauliEvolutionGate(hamiltonian, -pe_scale.scale, synthesis=evolution) - unitary = QuantumCircuit(evo.num_qubits) - unitary.append(evo, unitary.qubits) - - return unitary.decompose().decompose() - else: - # scale so that phase does not wrap. - scaled_hamiltonian = -pe_scale.scale * hamiltonian - unitary = evolution.convert(scaled_hamiltonian.exp_i()) - if not isinstance(unitary, QuantumCircuit): - unitary = unitary.to_circuit() - - return unitary.decompose().decompose() - - # Decomposing twice allows some 1Q Hamiltonians to give correct results - # when using MatrixEvolution(), that otherwise would give incorrect results. - # It does not break any others that we tested. - - def estimate( - self, - hamiltonian: PauliOp | MatrixOp | SummedOp | Pauli | SparsePauliOp | PauliSumOp, - state_preparation: StateFn | QuantumCircuit | Statevector | None = None, - evolution: EvolutionSynthesis | EvolutionBase | None = None, - bound: float | None = None, - ) -> HamiltonianPhaseEstimationResult: - """Run the Hamiltonian phase estimation algorithm. - - Args: - hamiltonian: A Hermitian operator. If the algorithm is used with a ``Sampler`` - primitive, the allowed types are ``Pauli``, ``SparsePauliOp``, and ``PauliSumOp``. - If the algorithm is used with a ``QuantumInstance``, ``PauliOp, ``MatrixOp``, - ``PauliSumOp``, and ``SummedOp`` types are allowed. - state_preparation: The ``StateFn`` to be prepared, whose eigenphase will be - measured. If this parameter is omitted, no preparation circuit will be run and - input state will be the all-zero state in the computational basis. - evolution: An evolution converter that generates a unitary from ``hamiltonian``. If - ``None``, then the default ``PauliTrotterEvolution`` is used. - bound: An upper bound on the absolute value of the eigenvalues of - ``hamiltonian``. If omitted, then ``hamiltonian`` must be a Pauli sum, or a - ``PauliOp``, in which case a bound will be computed. If ``hamiltonian`` - is a ``MatrixOp``, then ``bound`` may not be ``None``. The tighter the bound, - the higher the resolution of computed phases. - - Returns: - ``HamiltonianPhaseEstimationResult`` instance containing the result of the estimation - and diagnostic information. - - Raises: - TypeError: If ``evolution`` is not of type ``EvolutionSynthesis`` when a ``Sampler`` is - provided. - TypeError: If ``hamiltonian`` type is not ``Pauli`` or ``SparsePauliOp`` or - ``PauliSumOp`` when a ``Sampler`` is provided. - ValueError: If ``bound`` is ``None`` and ``hamiltonian`` is not a Pauli sum, i.e. a - ``PauliSumOp`` or a ``SummedOp`` whose terms are of type ``PauliOp``. - TypeError: If ``evolution`` is not of type ``EvolutionBase`` when no ``Sampler`` is - provided. - """ - if self._phase_estimation._sampler is not None: - if evolution is not None and not isinstance(evolution, EvolutionSynthesis): - raise TypeError(f"Expecting type EvolutionSynthesis, got {type(evolution)}") - if not isinstance(hamiltonian, (Pauli, SparsePauliOp, PauliSumOp)): - raise TypeError( - f"Expecting Hamiltonian type Pauli, SparsePauliOp or PauliSumOp, " - f"got {type(hamiltonian)}." - ) - - if isinstance(state_preparation, Statevector): - circuit = QuantumCircuit(state_preparation.num_qubits) - circuit.prepare_state(state_preparation.data) - state_preparation = circuit - if isinstance(hamiltonian, PauliSumOp): - id_coefficient, hamiltonian_no_id = _remove_identity_pauli_sum_op(hamiltonian) - else: - id_coefficient = 0.0 - hamiltonian_no_id = hamiltonian - pe_scale = self._get_scale(hamiltonian_no_id, bound) - unitary = self._get_unitary(hamiltonian_no_id, pe_scale, evolution) - else: - if evolution is None: - evolution = PauliTrotterEvolution() - elif not isinstance(evolution, EvolutionBase): - raise TypeError(f"Expecting type EvolutionBase, got {type(evolution)}") - - if isinstance(hamiltonian, PauliSumOp): - hamiltonian = hamiltonian.to_pauli_op() - elif isinstance(hamiltonian, PauliOp): - hamiltonian = SummedOp([hamiltonian]) - - if isinstance(hamiltonian, SummedOp): - # remove identitiy terms - # The term prop to the identity is removed from hamiltonian. - # This is done for three reasons: - # 1. Work around an unknown bug that otherwise causes the energies to be wrong in some - # cases. - # 2. Allow working with a simpler Hamiltonian, one with fewer terms. - # 3. Tighten the bound on the eigenvalues so that the spectrum is better resolved, i.e. - # occupies more of the range of values representable by the qubit register. - # The coefficient of this term will be added to the eigenvalues. - id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) - # get the rescaling object - pe_scale = self._get_scale(hamiltonian_no_id, bound) - - # get the unitary - unitary = self._get_unitary(hamiltonian_no_id, pe_scale, evolution) - - elif isinstance(hamiltonian, MatrixOp): - if bound is None: - raise ValueError("bound must be specified if Hermitian operator is MatrixOp") - - # Do not subtract an identity term from the matrix, so do not compensate. - id_coefficient = 0.0 - pe_scale = self._get_scale(hamiltonian, bound) - unitary = self._get_unitary(hamiltonian, pe_scale, evolution) - else: - raise TypeError(f"Hermitian operator of type {type(hamiltonian)} not supported.") - - if state_preparation is not None and isinstance(state_preparation, StateFn): - state_preparation = state_preparation.to_circuit_op().to_circuit() - # run phase estimation - phase_estimation_result = self._phase_estimation.estimate( - unitary=unitary, state_preparation=state_preparation - ) - return HamiltonianPhaseEstimationResult( - phase_estimation_result=phase_estimation_result, - id_coefficient=id_coefficient, - phase_estimation_scale=pe_scale, - ) - - -def _remove_identity(pauli_sum: SummedOp): - """Remove any identity operators from `pauli_sum`. Return - the sum of the coefficients of the identities and the new operator. - """ - idcoeff = 0.0 - ops = [] - for op in pauli_sum: - p = op.primitive - if p.x.any() or p.z.any(): - ops.append(op) - else: - idcoeff += op.coeff - - return idcoeff, SummedOp(ops) - - -def _remove_identity_pauli_sum_op(pauli_sum: PauliSumOp | SparsePauliOp): - """Remove any identity operators from ``pauli_sum``. Return - the sum of the coefficients of the identities and the new operator. - """ - - def _get_identity(size): - identity = I - for _ in range(size - 1): - identity = identity ^ I - return identity - - idcoeff = 0.0 - if isinstance(pauli_sum, PauliSumOp): - for operator in pauli_sum: - if operator.primitive.paulis == ["I" * pauli_sum.num_qubits]: - idcoeff += operator.primitive.coeffs[0] - pauli_sum = pauli_sum - operator.primitive.coeffs[0] * _get_identity( - pauli_sum.num_qubits - ) - - return idcoeff, pauli_sum.reduce() diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py deleted file mode 100644 index ce844427b04a..000000000000 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py +++ /dev/null @@ -1,108 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Result from running HamiltonianPhaseEstimation""" - -from __future__ import annotations - -from collections.abc import Mapping -from typing import cast -from qiskit.algorithms.algorithm_result import AlgorithmResult -from .phase_estimation_result import PhaseEstimationResult -from .phase_estimation_scale import PhaseEstimationScale - - -class HamiltonianPhaseEstimationResult(AlgorithmResult): - """Store and manipulate results from running `HamiltonianPhaseEstimation`. - - This API of this class is nearly the same as `PhaseEstimatorResult`, differing only in - the presence of an additional keyword argument in the methods. If `scaled` - is `False`, then the phases are not translated and scaled to recover the - eigenvalues of the Hamiltonian. Instead `phi` in :math:`[0, 1)` is returned, - as is the case when then unitary is not derived from a Hamiltonian. - - This class is meant to be instantiated via `HamiltonianPhaseEstimation.estimate`. - """ - - def __init__( - self, - phase_estimation_result: PhaseEstimationResult, - phase_estimation_scale: PhaseEstimationScale, - id_coefficient: float, - ) -> None: - """ - Args: - phase_estimation_result: The result object returned by PhaseEstimation.estimate. - phase_estimation_scale: object used to scale phases to obtain eigenvalues. - id_coefficient: The coefficient of the identity term in the Hamiltonian. - Eigenvalues are computed without this term so that the - coefficient must added to give correct eigenvalues. - This is done automatically when retrieving eigenvalues. - """ - super().__init__() - self._phase_estimation_scale = phase_estimation_scale - self._id_coefficient = id_coefficient - self._phase_estimation_result = phase_estimation_result - - def filter_phases( - self, cutoff: float = 0.0, scaled: bool = True, as_float: bool = True - ) -> Mapping[str | float, float]: - """Filter phases as does `PhaseEstimatorResult.filter_phases`, with - the addition that `phi` is shifted and translated to return eigenvalues - of the Hamiltonian. - - Args: - cutoff: Minimum weight of number of counts required to keep a bit string. - The default value is `0.0`. - scaled: If False, return `phi` in :math:`[0, 1)` rather than the eigenvalues of - the Hamiltonian. - as_float: If `True`, returned keys are floats in :math:`[0.0, 1.0)`. If `False` - returned keys are bit strings. - - Raises: - ValueError: if `as_float` is `False` and `scaled` is `True`. - - Returns: - A dict of filtered phases. - """ - if scaled and not as_float: - raise ValueError("`as_float` must be `True` if `scaled` is `True`.") - - phases = self._phase_estimation_result.filter_phases(cutoff, as_float=as_float) - if scaled: - return cast( - dict, self._phase_estimation_scale.scale_phases(phases, self._id_coefficient) - ) - else: - return cast(dict, phases) - - @property - def phase(self) -> float: - """The most likely phase of the unitary corresponding to the Hamiltonian. - - Returns: - The most likely phase. - """ - return self._phase_estimation_result.phase - - @property - def most_likely_eigenvalue(self) -> float: - """The most likely eigenvalue of the Hamiltonian. - - This method calls `most_likely_phase` and scales the result to - obtain an eigenvalue. - - Returns: - The most likely eigenvalue of the Hamiltonian. - """ - phase = self.phase - return self._phase_estimation_scale.scale_phase(phase, self._id_coefficient) diff --git a/qiskit/algorithms/phase_estimators/ipe.py b/qiskit/algorithms/phase_estimators/ipe.py deleted file mode 100644 index e8e583027a92..000000000000 --- a/qiskit/algorithms/phase_estimators/ipe.py +++ /dev/null @@ -1,229 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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. - - -"""The Iterative Quantum Phase Estimation Algorithm.""" - -from __future__ import annotations - -import numpy - -import qiskit -from qiskit.circuit import QuantumCircuit, QuantumRegister -from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.providers import Backend -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg -from qiskit.algorithms.exceptions import AlgorithmError -from .phase_estimator import PhaseEstimator -from .phase_estimator import PhaseEstimatorResult -from ...primitives import BaseSampler - - -class IterativePhaseEstimation(PhaseEstimator): - """Run the Iterative quantum phase estimation (QPE) algorithm. - - Given a unitary circuit and a circuit preparing an eigenstate, return the phase of the - eigenvalue as a number in :math:`[0,1)` using the iterative phase estimation algorithm. - - [1]: Dobsicek et al. (2006), Arbitrary accuracy iterative phase estimation algorithm as a two - qubit benchmark, `arxiv/quant-ph/0610214 `_ - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - num_iterations: int, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - num_iterations: The number of iterations (rounds) of the phase estimation to run. - quantum_instance: Deprecated: The quantum instance on which the - circuit will be run. - sampler: The sampler primitive on which the circuit will be sampled. - - Raises: - ValueError: if num_iterations is not greater than zero. - AlgorithmError: If neither sampler nor quantum instance is provided. - """ - if sampler is None and quantum_instance is None: - raise AlgorithmError( - "Neither a sampler nor a quantum instance was provided. Please provide one of them." - ) - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - if num_iterations <= 0: - raise ValueError("`num_iterations` must be greater than zero.") - self._num_iterations = num_iterations - self._sampler = sampler - - def construct_circuit( - self, - unitary: QuantumCircuit, - state_preparation: QuantumCircuit, - k: int, - omega: float = 0.0, - measurement: bool = False, - ) -> QuantumCircuit: - """Construct the kth iteration Quantum Phase Estimation circuit. - - For details of parameters, see Fig. 2 in https://arxiv.org/pdf/quant-ph/0610214.pdf. - - Args: - unitary: The circuit representing the unitary operator whose eigenvalue (via phase) - will be measured. - state_preparation: The circuit that prepares the state whose eigenphase will be - measured. If this parameter is omitted, no preparation circuit - will be run and input state will be the all-zero state in the - computational basis. - k: the iteration idx. - omega: the feedback angle. - measurement: Boolean flag to indicate if measurement should - be included in the circuit. - - Returns: - QuantumCircuit: the quantum circuit per iteration - """ - k = self._num_iterations if k is None else k - # The auxiliary (phase measurement) qubit - phase_register = QuantumRegister(1, name="a") - eigenstate_register = QuantumRegister(unitary.num_qubits, name="q") - qc = QuantumCircuit(eigenstate_register) - qc.add_register(phase_register) - if isinstance(state_preparation, QuantumCircuit): - qc.append(state_preparation, eigenstate_register) - elif state_preparation is not None: - qc += state_preparation.construct_circuit("circuit", eigenstate_register) - # hadamard on phase_register[0] - qc.h(phase_register[0]) - # controlled-U - # TODO: We may want to allow flexibility in how the power is computed - # For example, it may be desirable to compute the power via Trotterization, if - # we are doing Trotterization anyway. - unitary_power = unitary.power(2 ** (k - 1)).control() - qc = qc.compose(unitary_power, [unitary.num_qubits] + list(range(0, unitary.num_qubits))) - qc.p(omega, phase_register[0]) - # hadamard on phase_register[0] - qc.h(phase_register[0]) - if measurement: - c = ClassicalRegister(1, name="c") - qc.add_register(c) - qc.measure(phase_register, c) - return qc - - def _estimate_phase_iteratively(self, unitary, state_preparation): - """ - Main loop of iterative phase estimation. - """ - omega_coef = 0 - # k runs from the number of iterations back to 1 - for k in range(self._num_iterations, 0, -1): - omega_coef /= 2 - - if self._sampler is not None: - - qc = self.construct_circuit( - unitary, state_preparation, k, -2 * numpy.pi * omega_coef, True - ) - try: - sampler_job = self._sampler.run([qc]) - result = sampler_job.result().quasi_dists[0] - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - x = 1 if result.get(1, 0) > result.get(0, 0) else 0 - - elif self._quantum_instance.is_statevector: - qc = self.construct_circuit( - unitary, state_preparation, k, -2 * numpy.pi * omega_coef, measurement=False - ) - result = self._quantum_instance.execute(qc) - complete_state_vec = result.get_statevector(qc) - ancilla_density_mat = qiskit.quantum_info.partial_trace( - complete_state_vec, range(unitary.num_qubits) - ) - ancilla_density_mat_diag = numpy.diag(ancilla_density_mat) - max_amplitude = max( - ancilla_density_mat_diag.min(), ancilla_density_mat_diag.max(), key=abs - ) - x = numpy.where(ancilla_density_mat_diag == max_amplitude)[0][0] - else: - qc = self.construct_circuit( - unitary, state_preparation, k, -2 * numpy.pi * omega_coef, measurement=True - ) - measurements = self._quantum_instance.execute(qc).get_counts(qc) - x = 1 if measurements.get("1", 0) > measurements.get("0", 0) else 0 - omega_coef = omega_coef + x / 2 - return omega_coef - - # pylint: disable=signature-differs - def estimate( - self, unitary: QuantumCircuit, state_preparation: QuantumCircuit - ) -> "IterativePhaseEstimationResult": - """ - Estimate the eigenphase of the input unitary and initial-state pair. - - Args: - unitary: The circuit representing the unitary operator whose eigenvalue (via phase) - will be measured. - state_preparation: The circuit that prepares the state whose eigenphase will be - measured. If this parameter is omitted, no preparation circuit - will be run and input state will be the all-zero state in the - computational basis. - - Returns: - Estimated phase in an IterativePhaseEstimationResult object. - - Raises: - AlgorithmError: If neither sampler nor quantum instance is provided. - """ - phase = self._estimate_phase_iteratively(unitary, state_preparation) - - return IterativePhaseEstimationResult(self._num_iterations, phase) - - -class IterativePhaseEstimationResult(PhaseEstimatorResult): - """Phase Estimation Result.""" - - def __init__(self, num_iterations: int, phase: float) -> None: - """ - Args: - num_iterations: number of iterations used in the phase estimation. - phase: the estimated phase. - """ - - self._num_iterations = num_iterations - self._phase = phase - - @property - def phase(self) -> float: - r"""Return the estimated phase as a number in :math:`[0.0, 1.0)`. - - 1.0 corresponds to a phase of :math:`2\pi`. It is assumed that the input vector is an - eigenvector of the unitary so that the peak of the probability density occurs at the bit - string that most closely approximates the true phase. - """ - return self._phase - - @property - def num_iterations(self) -> int: - r"""Return the number of iterations used in the estimation algorithm.""" - return self._num_iterations diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py deleted file mode 100644 index a3bc1d59c60d..000000000000 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ /dev/null @@ -1,268 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# 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. - - -"""The Quantum Phase Estimation Algorithm.""" - -from __future__ import annotations - -import numpy - -from qiskit.circuit import QuantumCircuit -import qiskit -from qiskit import circuit -from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.providers import Backend -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg -from qiskit.result import Result -from qiskit.algorithms.exceptions import AlgorithmError -from .phase_estimation_result import PhaseEstimationResult, _sort_phases -from .phase_estimator import PhaseEstimator -from ...primitives import BaseSampler - - -class PhaseEstimation(PhaseEstimator): - r"""Run the Quantum Phase Estimation (QPE) algorithm. - - This runs QPE with a multi-qubit register for reading the phases [1] - of input states. - - The algorithm takes as input a unitary :math:`U` and a state :math:`|\psi\rangle`, - which may be written - - .. math:: - - |\psi\rangle = \sum_j c_j |\phi_j\rangle, - - where :math:`|\phi_j\rangle` are eigenstates of :math:`U`. We prepare the quantum register - in the state :math:`|\psi\rangle` then apply :math:`U` leaving the register in the state - - .. math:: - - U|\psi\rangle = \sum_j \exp(i \phi_j) c_j |\phi_j\rangle. - - In the ideal case, one then measures the phase :math:`\phi_j` with probability - :math:`|c_j|^2`. In practice, many (or all) of the bit strings may be measured due to - noise and the possibility that :math:`\phi_j` may not be representable exactly by the - output register. In the latter case the probability for each eigenphase will be spread - across bitstrings, with amplitudes that decrease with distance from the bitstring most - closely approximating the eigenphase. - - The main input to the constructor is the number of qubits in the phase-reading register. - For phase estimation, there are two methods: - - first. `estimate`, which takes a state preparation circuit to prepare an input state, and - a unitary that will act on the input state. In this case, an instance of - :class:`qiskit.circuit.PhaseEstimation`, a QPE circuit, containing - the state preparation and input unitary will be constructed. - second. `estimate_from_pe_circuit`, which takes a quantum-phase-estimation circuit in which - the unitary and state preparation are already embedded. - - In both estimation methods, the QPE circuit is run on a backend - and the frequencies or counts of the phases represented by bitstrings - are recorded. The results are returned as an instance of - :class:`~qiskit.algorithms.phase_estimator_result.PhaseEstimationResult`. - - **Reference:** - - [1]: Michael A. Nielsen and Isaac L. Chuang. 2011. - Quantum Computation and Quantum Information: 10th Anniversary Edition (10th ed.). - Cambridge University Press, New York, NY, USA. - - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - num_evaluation_qubits: int, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will - be estimated as a binary string with this many bits. - quantum_instance: Deprecated: The quantum instance on which the - circuit will be run. - sampler: The sampler primitive on which the circuit will be sampled. - - Raises: - AlgorithmError: If neither sampler nor quantum instance is provided. - """ - if sampler is None and quantum_instance is None: - raise AlgorithmError( - "Neither a sampler nor a quantum instance was provided. Please provide one of them." - ) - self._measurements_added = False - if num_evaluation_qubits is not None: - self._num_evaluation_qubits = num_evaluation_qubits - - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - self._sampler = sampler - - def construct_circuit( - self, unitary: QuantumCircuit, state_preparation: QuantumCircuit | None = None - ) -> QuantumCircuit: - """Return the circuit to be executed to estimate phases. - - This circuit includes as sub-circuits the core phase estimation circuit, - with the addition of the state-preparation circuit and possibly measurement instructions. - """ - num_evaluation_qubits = self._num_evaluation_qubits - num_unitary_qubits = unitary.num_qubits - - pe_circuit = circuit.library.PhaseEstimation(num_evaluation_qubits, unitary) - - if state_preparation is not None: - pe_circuit.compose( - state_preparation, - qubits=range(num_evaluation_qubits, num_evaluation_qubits + num_unitary_qubits), - inplace=True, - front=True, - ) - - return pe_circuit - - def _add_measurement_if_required(self, pe_circuit): - if self._sampler is not None or not self._quantum_instance.is_statevector: - # Measure only the evaluation qubits. - regname = "meas" - creg = ClassicalRegister(self._num_evaluation_qubits, regname) - pe_circuit.add_register(creg) - pe_circuit.barrier() - pe_circuit.measure( - range(self._num_evaluation_qubits), range(self._num_evaluation_qubits) - ) - - return circuit - - def _compute_phases( - self, num_unitary_qubits: int, circuit_result: Result - ) -> numpy.ndarray | qiskit.result.Counts: - """Compute frequencies/counts of phases from the result of running the QPE circuit. - - How the frequencies are computed depends on whether the backend computes amplitude or - samples outcomes. - - 1) If the backend is a statevector simulator, then the reduced density matrix of the - phase-reading register is computed from the combined phase-reading- and input-state - registers. The elements of the diagonal :math:`(i, i)` give the probability to measure the - each of the states `i`. The index `i` expressed as a binary integer with the LSB rightmost - gives the state of the phase-reading register with the LSB leftmost when interpreted as a - phase. In order to maintain the compact representation, the phases are maintained as decimal - integers. They may be converted to other forms via the results object, - `PhaseEstimationResult` or `HamiltonianPhaseEstimationResult`. - - 2) If the backend samples bitstrings, then the counts are first retrieved as a dict. The - binary strings (the keys) are then reversed so that the LSB is rightmost and the counts are - converted to frequencies. Then the keys are sorted according to increasing phase, so that - they can be easily understood when displaying or plotting a histogram. - - Args: - num_unitary_qubits: The number of qubits in the unitary. - circuit_result: the result object returned by the backend that ran the QPE circuit. - - Returns: - Either a dict or numpy.ndarray representing the frequencies of the phases. - - """ - if self._quantum_instance.is_statevector: - state_vec = circuit_result.get_statevector() - evaluation_density_matrix = qiskit.quantum_info.partial_trace( - state_vec, - range( - self._num_evaluation_qubits, self._num_evaluation_qubits + num_unitary_qubits - ), - ) - phases = evaluation_density_matrix.probabilities() - else: - # return counts with keys sorted numerically - num_shots = circuit_result.results[0].shots - counts = circuit_result.get_counts() - phases = {k[::-1]: counts[k] / num_shots for k in counts.keys()} - phases = _sort_phases(phases) - phases = qiskit.result.Counts( - phases, memory_slots=counts.memory_slots, creg_sizes=counts.creg_sizes - ) - - return phases - - def estimate_from_pe_circuit( - self, pe_circuit: QuantumCircuit, num_unitary_qubits: int - ) -> PhaseEstimationResult: - """Run the phase estimation algorithm on a phase estimation circuit - - Args: - pe_circuit: The phase estimation circuit. - num_unitary_qubits: Must agree with the number of qubits in the unitary in `pe_circuit`. - - Returns: - An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. - - Raises: - AlgorithmError: Primitive job failed. - """ - - self._add_measurement_if_required(pe_circuit) - - if self._sampler is not None: - try: - circuit_job = self._sampler.run([pe_circuit]) - circuit_result = circuit_job.result() - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - phases = circuit_result.quasi_dists[0] - phases_bitstrings = {} - for key, phase in phases.items(): - bitstring_key = self._get_reversed_bitstring(self._num_evaluation_qubits, key) - phases_bitstrings[bitstring_key] = phase - phases = phases_bitstrings - - else: - circuit_result = self._quantum_instance.execute(pe_circuit) - phases = self._compute_phases(num_unitary_qubits, circuit_result) - return PhaseEstimationResult( - self._num_evaluation_qubits, circuit_result=circuit_result, phases=phases - ) - - def estimate( - self, - unitary: QuantumCircuit, - state_preparation: QuantumCircuit | None = None, - ) -> PhaseEstimationResult: - """Build a phase estimation circuit and run the corresponding algorithm. - - Args: - unitary: The circuit representing the unitary operator whose eigenvalues (via phase) - will be measured. - state_preparation: The circuit that prepares the state whose eigenphase will be - measured. If this parameter is omitted, no preparation circuit - will be run and input state will be the all-zero state in the - computational basis. - - Returns: - An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. - """ - pe_circuit = self.construct_circuit(unitary, state_preparation) - num_unitary_qubits = unitary.num_qubits - - return self.estimate_from_pe_circuit(pe_circuit, num_unitary_qubits) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_result.py b/qiskit/algorithms/phase_estimators/phase_estimation_result.py deleted file mode 100644 index 8d87571af7e7..000000000000 --- a/qiskit/algorithms/phase_estimators/phase_estimation_result.py +++ /dev/null @@ -1,174 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Result of running PhaseEstimation""" -from __future__ import annotations -import numpy - -from qiskit.utils.deprecation import deprecate_func -from qiskit.result import Result -from .phase_estimator import PhaseEstimatorResult - - -class PhaseEstimationResult(PhaseEstimatorResult): - """Store and manipulate results from running `PhaseEstimation`. - - This class is instantiated by the ``PhaseEstimation`` class, not via user code. - The ``PhaseEstimation`` class generates a list of phases and corresponding weights. Upon - completion it returns the results as an instance of this class. The main method for - accessing the results is `filter_phases`. - - The canonical phase satisfying the ``PhaseEstimator`` interface, returned by the - attribute `phase`, is the most likely phase. - """ - - def __init__( - self, - num_evaluation_qubits: int, - circuit_result: Result, - phases: numpy.ndarray | dict[str, float], - ) -> None: - """ - Args: - num_evaluation_qubits: number of qubits in phase-readout register. - circuit_result: result object returned by method running circuit. - phases: ndarray or dict of phases and frequencies determined by QPE. - """ - super().__init__() - - self._phases = phases - # int: number of qubits in phase-readout register - self._num_evaluation_qubits = num_evaluation_qubits - self._circuit_result = circuit_result - - @property - def phases(self) -> numpy.ndarray | dict: - """Return all phases and their frequencies computed by QPE. - - This is an array or dict whose values correspond to weights on bit strings. - """ - return self._phases - - @property - def circuit_result(self) -> Result: - """Return the result object returned by running the QPE circuit (on hardware or simulator). - - This is useful for inspecting and troubleshooting the QPE algorithm. - """ - return self._circuit_result - - @property - @deprecate_func( - additional_msg="Instead, use the property ``phase``, which behaves the same.", - since="0.18.0", - is_property=True, - ) - def most_likely_phase(self) -> float: - r"""DEPRECATED - Return the most likely phase as a number in :math:`[0.0, 1.0)`. - - 1.0 corresponds to a phase of :math:`2\pi`. This selects the phase corresponding - to the bit string with the highesest probability. This is the most likely phase. - """ - return self.phase - - @property - def phase(self) -> float: - r"""Return the most likely phase as a number in :math:`[0.0, 1.0)`. - - 1.0 corresponds to a phase of :math:`2\pi`. This selects the phase corresponding - to the bit string with the highesest probability. This is the most likely phase. - """ - if isinstance(self.phases, dict): - binary_phase_string = max(self.phases, key=self.phases.get) - else: - # numpy.argmax ignores complex part of number. But, we take abs anyway - idx = numpy.argmax(abs(self.phases)) - binary_phase_string = numpy.binary_repr(idx, self._num_evaluation_qubits)[::-1] - phase = _bit_string_to_phase(binary_phase_string) - return phase - - def filter_phases(self, cutoff: float = 0.0, as_float: bool = True) -> dict: - """Return a filtered dict of phases (keys) and frequencies (values). - - Only phases with frequencies (counts) larger than `cutoff` are included. - It is assumed that the `run` method has been called so that the phases have been computed. - When using a noiseless, shot-based simulator to read a single phase that can - be represented exactly by `num_evaluation_qubits`, all the weight will - be concentrated on a single phase. In all other cases, many, or all, bit - strings will have non-zero weight. This method is useful for filtering - out these uninteresting bit strings. - - Args: - cutoff: Minimum weight of number of counts required to keep a bit string. - The default value is `0.0`. - as_float: If `True`, returned keys are floats in :math:`[0.0, 1.0)`. If `False` - returned keys are bit strings. - - Returns: - A filtered dict of phases (keys) and frequencies (values). - """ - if isinstance(self.phases, dict): - counts = self.phases - if as_float: - phases = { - _bit_string_to_phase(k): counts[k] for k in counts.keys() if counts[k] > cutoff - } - else: - phases = {k: counts[k] for k in counts.keys() if counts[k] > cutoff} - - else: - phases = {} - for idx, amplitude in enumerate(self.phases): - if amplitude > cutoff: - # Each index corresponds to a computational basis state with the LSB rightmost. - # But, we chose to apply the unitaries such that the phase is recorded - # in reverse order. So, we reverse the bitstrings here. - binary_phase_string = numpy.binary_repr(idx, self._num_evaluation_qubits)[::-1] - if as_float: - _key: str | float = _bit_string_to_phase(binary_phase_string) - else: - _key = binary_phase_string - phases[_key] = amplitude - - phases = _sort_phases(phases) - - return phases - - -def _bit_string_to_phase(binary_string: str) -> float: - """Convert bit string to a normalized phase in :math:`[0,1)`. - - It is assumed that the bit string is correctly padded and that the order of - the bits has been reversed relative to their order when the counts - were recorded. The LSB is the right most when interpreting the bitstring as - a phase. - - Args: - binary_string: A string of characters '0' and '1'. - - Returns: - A phase scaled to :math:`[0,1)`. - """ - n_qubits = len(binary_string) - return int(binary_string, 2) / (2**n_qubits) - - -def _sort_phases(phases: dict) -> dict: - """Sort a dict of bit strings representing phases (keys) and frequencies (values) by bit string. - - The bit strings are sorted according to increasing phase. This relies on Python - preserving insertion order when building dicts. - """ - pkeys = list(phases.keys()) - pkeys.sort(reverse=False) # Sorts in order of the integer encoded by binary string - phases = {k: phases[k] for k in pkeys} - return phases diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py deleted file mode 100644 index e22b3e18cd9e..000000000000 --- a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py +++ /dev/null @@ -1,160 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Scaling for Hamiltonian and eigenvalues to avoid phase wrapping""" -from __future__ import annotations -import numpy as np - -from qiskit.opflow import SummedOp, PauliSumOp -from qiskit.quantum_info import SparsePauliOp, Operator -from qiskit.quantum_info.operators.base_operator import BaseOperator - - -class PhaseEstimationScale: - """Set and use a bound on eigenvalues of a Hermitian operator in order to ensure phases are in - the desired range and to convert measured phases into eigenvectors. - - The ``bound`` is set when constructing this class. Then the method ``scale`` is used to find the - factor by which to scale the operator. - - If ``bound`` is equal exactly to the largest eigenvalue, and the smallest eigenvalue is minus - the largest, then these two eigenvalues will not be distinguished. For example, if the Hermitian - operator is the Pauli Z operator with eigenvalues :math:`1` and :math:`-1`, and ``bound`` is - :math:`1`, then both eigenvalues will be mapped to :math:`1`. - This can be avoided by making ``bound`` a bit larger. - - Increasing ``bound`` decreases the part of the interval :math:`[0, 1)` that is used to map - eigenvalues to ``phi``. However, sometimes this results in a better determination of the - eigenvalues, because 1) although there are fewer discrete phases in the useful range, it may - shift one of the discrete phases closer to the actual phase. And, 2) If one of the discrete - phases is close to, or exactly equal to the actual phase, then artifacts (probability) in - neighboring phases will be reduced. This is important because the artifacts may be larger than - the probability in a phase representing another eigenvalue of interest whose corresponding - eigenstate has a relatively small weight in the input state. - - """ - - def __init__(self, bound: float) -> None: - """ - Args: - bound: an upper bound on the absolute value of the eigenvalues of a Hermitian operator. - (The operator is not needed here.) - """ - self._bound = bound - - @property - def scale(self) -> float: - r"""Return the Hamiltonian scaling factor. - - Return the scale factor by which a Hermitian operator must be multiplied - so that the phase of the corresponding unitary is restricted to :math:`[-\pi, \pi]`. - This factor is computed from the bound on the absolute values of the eigenvalues - of the operator. The methods ``scale_phase`` and ``scale_phases`` are used recover - the eigenvalues corresponding the original (unscaled) Hermitian operator. - - Returns: - The scale factor. - """ - return np.pi / self._bound - - def scale_phase(self, phi: float, id_coefficient: float = 0.0) -> float: - r"""Convert a phase into an eigenvalue. - - The input phase ``phi`` corresponds to the eigenvalue of a unitary obtained by - exponentiating a scaled Hermitian operator. Recall that the phase - is obtained from ``phi`` as :math:`2\pi\phi`. Furthermore, the Hermitian operator - was scaled so that ``phi`` is restricted to :math:`[-1/2, 1/2]`, corresponding to - phases in :math:`[-\pi, \pi]`. But the values of `phi` read from the phase-readout - register are in :math:`[0, 1)`. Any value of ``phi`` greater than :math:`1/2` corresponds - to a raw phase of minus the complement with respect to 1. After this possible - shift, the phase is scaled by the inverse of the factor by which the - Hermitian operator was scaled to recover the eigenvalue of the Hermitian - operator. - - Args: - phi: Normalized phase in :math:`[0, 1)` to be converted to an eigenvalue. - id_coefficient: All eigenvalues are shifted by this value. - - Returns: - An eigenvalue computed from the input phase. - """ - w = 2 * self._bound - if phi <= 0.5: - return phi * w + id_coefficient - else: - return (phi - 1) * w + id_coefficient - - def scale_phases(self, phases: list | dict, id_coefficient: float = 0.0) -> dict | list: - """Convert a list or dict of phases to eigenvalues. - - The values in the list, or keys in the dict, are values of ``phi` and - are converted as described in the description of ``scale_phase``. In case - ``phases`` is a dict, the values of the dict are passed unchanged. - - Args: - phases: a list or dict of values of ``phi``. - id_coefficient: All eigenvalues are shifted by this value. - - Returns: - Eigenvalues computed from phases. - """ - if isinstance(phases, list): - phases = [self.scale_phase(x, id_coefficient) for x in phases] - else: - phases = {self.scale_phase(x, id_coefficient): phases[x] for x in phases.keys()} - - return phases - - @classmethod - def from_pauli_sum( - cls, pauli_sum: SummedOp | PauliSumOp | SparsePauliOp | Operator - ) -> "PhaseEstimationScale" | float: - """Create a PhaseEstimationScale from a `SummedOp` representing a sum of Pauli Operators. - - It is assumed that the ``pauli_sum`` is the sum of ``PauliOp`` objects. The bound on - the absolute value of the eigenvalues of the sum is obtained as the sum of the - absolute values of the coefficients of the terms. This is the best bound available in - the generic case. A ``PhaseEstimationScale`` object is instantiated using this bound. - - Args: - pauli_sum: A ``SummedOp`` whose terms are ``PauliOp`` objects. - - Raises: - ValueError: if ``pauli_sum`` is not a sum of Pauli operators. - - Returns: - A ``PhaseEstimationScale`` object - """ - if isinstance(pauli_sum, PauliSumOp): - bound = abs(pauli_sum.coeff) * sum(abs(coeff) for coeff in pauli_sum.coeffs) - return PhaseEstimationScale(bound) - elif isinstance(pauli_sum, SparsePauliOp): - bound = sum(abs(coeff) for coeff in pauli_sum.coeffs) - return PhaseEstimationScale(bound) - elif isinstance(pauli_sum, Operator): - bound = np.sum(np.abs(np.linalg.eigvalsh(pauli_sum))) - return PhaseEstimationScale(bound) - elif isinstance(pauli_sum, BaseOperator): - raise ValueError( - f"For the operator of type {type(pauli_sum)} the bound needs to be provided in the " - f"algorithm." - ) - else: - if pauli_sum.primitive_strings() != {"Pauli"}: - raise ValueError( - "`pauli_sum` must be a sum of Pauli operators. Got primitives {}.".format( - pauli_sum.primitive_strings() - ) - ) - - bound = abs(pauli_sum.coeff) * sum(abs(pauli.coeff) for pauli in pauli_sum) - return PhaseEstimationScale(bound) diff --git a/qiskit/algorithms/phase_estimators/phase_estimator.py b/qiskit/algorithms/phase_estimators/phase_estimator.py deleted file mode 100644 index 09f8113e5f4a..000000000000 --- a/qiskit/algorithms/phase_estimators/phase_estimator.py +++ /dev/null @@ -1,58 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# 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. - -"""The Phase Estimator interface.""" - -from __future__ import annotations -from abc import ABC, abstractmethod -from qiskit.circuit import QuantumCircuit -from qiskit.algorithms.algorithm_result import AlgorithmResult - - -class PhaseEstimator(ABC): - """The Phase Estimator interface. - - Algorithms that can compute a phase for a unitary operator and initial state may implement this - interface to allow different algorithms to be used interchangeably. - - The phase returned is a canonical phase determined by the specific algorithm, such as the most - likely phase. In addition, the algorithm may provide an interface to retrieve phases by other - criteria. - """ - - @abstractmethod - def estimate( - self, - unitary: QuantumCircuit, - state_preparation: QuantumCircuit | None = None, - ) -> "PhaseEstimatorResult": - """Estimate the phase.""" - raise NotImplementedError - - @staticmethod - def _get_reversed_bitstring(length: int, number: int) -> str: - return f"{number:b}".zfill(length)[::-1] - - -class PhaseEstimatorResult(AlgorithmResult): - """Phase Estimator Result.""" - - @property - @abstractmethod - def phase(self) -> float: - r"""Return the estimated phase as a number in :math:`[0.0, 1.0)`. - - 1.0 corresponds to a phase of :math:`2\pi`. In case the phase estimation algorithm - computes more than one phase, this attribute returns a canonical single phase; for - example, the most likely phase. - """ - raise NotImplementedError diff --git a/qiskit/algorithms/state_fidelities/__init__.py b/qiskit/algorithms/state_fidelities/__init__.py deleted file mode 100644 index ea8e4e03bf89..000000000000 --- a/qiskit/algorithms/state_fidelities/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -""" -===================================================================== -State Fidelity Interfaces (:mod:`qiskit.algorithms.state_fidelities`) -===================================================================== - -.. currentmodule:: qiskit.algorithms.state_fidelities - -State Fidelities -================ - -.. autosummary:: - :toctree: ../stubs/ - - BaseStateFidelity - ComputeUncompute - -Results -======= - - .. autosummary:: - :toctree: ../stubs/ - - StateFidelityResult - -""" - -from .base_state_fidelity import BaseStateFidelity -from .compute_uncompute import ComputeUncompute -from .state_fidelity_result import StateFidelityResult - -__all__ = ["BaseStateFidelity", "ComputeUncompute", "StateFidelityResult"] diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py deleted file mode 100644 index 9395889bc5da..000000000000 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ /dev/null @@ -1,308 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -""" -Base state fidelity interface -""" - -from __future__ import annotations -from abc import ABC, abstractmethod -from collections.abc import Sequence, Mapping -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import ParameterVector - -from ..algorithm_job import AlgorithmJob -from .state_fidelity_result import StateFidelityResult - - -class BaseStateFidelity(ABC): - r""" - An interface to calculate state fidelities (state overlaps) for pairs of - (parametrized) quantum circuits. The calculation depends on the particular - fidelity method implementation, but can be always defined as the state overlap: - - .. math:: - - |\langle\psi(x)|\phi(y)\rangle|^2 - - where :math:`x` and :math:`y` are optional parametrizations of the - states :math:`\psi` and :math:`\phi` prepared by the circuits - ``circuit_1`` and ``circuit_2``, respectively. - - """ - - def __init__(self) -> None: - - # use cache for preventing unnecessary circuit compositions - self._circuit_cache: Mapping[tuple[int, int], QuantumCircuit] = {} - - @staticmethod - def _preprocess_values( - circuits: QuantumCircuit | Sequence[QuantumCircuit], - values: Sequence[float] | Sequence[Sequence[float]] | None = None, - ) -> Sequence[Sequence[float]]: - """ - Checks whether the passed values match the shape of the parameters - of the corresponding circuits and formats values to 2D list. - - Args: - circuits: List of circuits to be checked. - values: Parameter values corresponding to the circuits to be checked. - - Returns: - A 2D value list if the values match the circuits, or an empty 2D list - if values is None. - - Raises: - ValueError: if the number of parameter values doesn't match the number of - circuit parameters - TypeError: if the input values are not a sequence. - """ - - if isinstance(circuits, QuantumCircuit): - circuits = [circuits] - - if values is None: - for circuit in circuits: - if circuit.num_parameters != 0: - raise ValueError( - f"`values` cannot be `None` because circuit <{circuit.name}> has " - f"{circuit.num_parameters} free parameters." - ) - return [[]] - else: - - # Support ndarray - if isinstance(values, np.ndarray): - values = values.tolist() - if len(values) > 0 and isinstance(values[0], np.ndarray): - values = [v.tolist() for v in values] - - if not isinstance(values, Sequence): - raise TypeError( - f"Expected a sequence of numerical parameter values, " - f"but got input type {type(values)} instead." - ) - - # ensure 2d - if len(values) > 0 and not isinstance(values[0], Sequence) or len(values) == 0: - values = [values] - - return values - - def _check_qubits_match(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit) -> None: - """ - Checks that the number of qubits of 2 circuits matches. - Args: - circuit_1: (Parametrized) quantum circuit. - circuit_2: (Parametrized) quantum circuit. - - Raises: - ValueError: when ``circuit_1`` and ``circuit_2`` don't have the - same number of qubits. - """ - - if circuit_1.num_qubits != circuit_2.num_qubits: - raise ValueError( - f"The number of qubits for the first circuit ({circuit_1.num_qubits}) " - f"and second circuit ({circuit_2.num_qubits}) are not the same." - ) - - @abstractmethod - def create_fidelity_circuit( - self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit - ) -> QuantumCircuit: - """ - Implementation-dependent method to create a fidelity circuit - from 2 circuit inputs. - - Args: - circuit_1: (Parametrized) quantum circuit. - circuit_2: (Parametrized) quantum circuit. - - Returns: - The fidelity quantum circuit corresponding to ``circuit_1`` and ``circuit_2``. - """ - raise NotImplementedError - - def _construct_circuits( - self, - circuits_1: QuantumCircuit | Sequence[QuantumCircuit], - circuits_2: QuantumCircuit | Sequence[QuantumCircuit], - ) -> Sequence[QuantumCircuit]: - """ - Constructs the list of fidelity circuits to be evaluated. - These circuits represent the state overlap between pairs of input circuits, - and their construction depends on the fidelity method implementations. - - Args: - circuits_1: (Parametrized) quantum circuits. - circuits_2: (Parametrized) quantum circuits. - - Returns: - List of constructed fidelity circuits. - - Raises: - ValueError: if the length of the input circuit lists doesn't match. - """ - - if isinstance(circuits_1, QuantumCircuit): - circuits_1 = [circuits_1] - if isinstance(circuits_2, QuantumCircuit): - circuits_2 = [circuits_2] - - if len(circuits_1) != len(circuits_2): - raise ValueError( - f"The length of the first circuit list({len(circuits_1)}) " - f"and second circuit list ({len(circuits_2)}) is not the same." - ) - - circuits = [] - for (circuit_1, circuit_2) in zip(circuits_1, circuits_2): - - # TODO: improve caching, what if the circuit is modified without changing the id? - circuit = self._circuit_cache.get((id(circuit_1), id(circuit_2))) - - if circuit is not None: - circuits.append(circuit) - else: - self._check_qubits_match(circuit_1, circuit_2) - - # re-parametrize input circuits - # TODO: make smarter checks to avoid unnecesary reparametrizations - parameters_1 = ParameterVector("x", circuit_1.num_parameters) - parametrized_circuit_1 = circuit_1.assign_parameters(parameters_1) - parameters_2 = ParameterVector("y", circuit_2.num_parameters) - parametrized_circuit_2 = circuit_2.assign_parameters(parameters_2) - - circuit = self.create_fidelity_circuit( - parametrized_circuit_1, parametrized_circuit_2 - ) - circuits.append(circuit) - # update cache - self._circuit_cache[id(circuit_1), id(circuit_2)] = circuit - - return circuits - - def _construct_value_list( - self, - circuits_1: Sequence[QuantumCircuit], - circuits_2: Sequence[QuantumCircuit], - values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, - values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - ) -> list[float]: - """ - Preprocesses input parameter values to match the fidelity - circuit parametrization, and return in list format. - - Args: - circuits_1: (Parametrized) quantum circuits preparing the - first list of quantum states. - circuits_2: (Parametrized) quantum circuits preparing the - second list of quantum states. - values_1: Numerical parameters to be bound to the first circuits. - values_2: Numerical parameters to be bound to the second circuits. - - Returns: - List of parameter values for fidelity circuit. - - """ - values_1 = self._preprocess_values(circuits_1, values_1) - values_2 = self._preprocess_values(circuits_2, values_2) - - values = [] - if len(values_2[0]) == 0: - values = list(values_1) - elif len(values_1[0]) == 0: - values = list(values_2) - else: - for (val_1, val_2) in zip(values_1, values_2): - values.append(val_1 + val_2) - - return values - - @abstractmethod - def _run( - self, - circuits_1: QuantumCircuit | Sequence[QuantumCircuit], - circuits_2: QuantumCircuit | Sequence[QuantumCircuit], - values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, - values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - **options, - ) -> StateFidelityResult: - r""" - Computes the state overlap (fidelity) calculation between two - (parametrized) circuits (first and second) for a specific set of parameter - values (first and second). - - Args: - circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. - circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. - values_1: Numerical parameters to be bound to the first set of circuits - values_2: Numerical parameters to be bound to the second set of circuits. - options: Primitive backend runtime options used for circuit execution. The order - of priority is\: options in ``run`` method > fidelity's default - options > primitive's default setting. - Higher priority setting overrides lower priority setting. - - Returns: - The result of the fidelity calculation. - """ - raise NotImplementedError - - def run( - self, - circuits_1: QuantumCircuit | Sequence[QuantumCircuit], - circuits_2: QuantumCircuit | Sequence[QuantumCircuit], - values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, - values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - **options, - ) -> AlgorithmJob: - r""" - Runs asynchronously the state overlap (fidelity) calculation between two - (parametrized) circuits (first and second) for a specific set of parameter - values (first and second). This calculation depends on the particular - fidelity method implementation. - - Args: - circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. - circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. - values_1: Numerical parameters to be bound to the first set of circuits. - values_2: Numerical parameters to be bound to the second set of circuits. - options: Primitive backend runtime options used for circuit execution. The order - of priority is\: options in ``run`` method > fidelity's default - options > primitive's default setting. - Higher priority setting overrides lower priority setting. - - Returns: - Primitive job for the fidelity calculation. - The job's result is an instance of ``StateFidelityResult``. - """ - - job = AlgorithmJob(self._run, circuits_1, circuits_2, values_1, values_2, **options) - - job.submit() - return job - - def _truncate_fidelities(self, fidelities: Sequence[float]) -> Sequence[float]: - """ - Ensures fidelity result in [0,1]. - - Args: - fidelities: Sequence of raw fidelity results. - - Returns: - List of truncated fidelities. - - """ - return np.clip(fidelities, 0, 1).tolist() diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py deleted file mode 100644 index be0879fadc27..000000000000 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ /dev/null @@ -1,249 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. -""" -Compute-uncompute fidelity interface using primitives -""" - -from __future__ import annotations -from collections.abc import Sequence -from copy import copy - -from qiskit import QuantumCircuit -from qiskit.primitives import BaseSampler -from qiskit.providers import Options - -from ..exceptions import AlgorithmError -from .base_state_fidelity import BaseStateFidelity -from .state_fidelity_result import StateFidelityResult - - -class ComputeUncompute(BaseStateFidelity): - r""" - This class leverages the sampler primitive to calculate the state - fidelity of two quantum circuits following the compute-uncompute - method (see [1] for further reference). - The fidelity can be defined as the state overlap. - - .. math:: - - |\langle\psi(x)|\phi(y)\rangle|^2 - - where :math:`x` and :math:`y` are optional parametrizations of the - states :math:`\psi` and :math:`\phi` prepared by the circuits - ``circuit_1`` and ``circuit_2``, respectively. - - **Reference:** - [1] Havlíček, V., Córcoles, A. D., Temme, K., Harrow, A. W., Kandala, - A., Chow, J. M., & Gambetta, J. M. (2019). Supervised learning - with quantum-enhanced feature spaces. Nature, 567(7747), 209-212. - `arXiv:1804.11326v2 [quant-ph] `_ - - """ - - def __init__( - self, - sampler: BaseSampler, - options: Options | None = None, - local: bool = False, - ) -> None: - r""" - Args: - sampler: Sampler primitive instance. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > fidelity's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting. - local: If set to ``True``, the fidelity is averaged over - single-qubit projectors - - .. math:: - - \hat{O} = \frac{1}{N}\sum_{i=1}^N|0_i\rangle\langle 0_i|, - - instead of the global projector :math:`|0\rangle\langle 0|^{\otimes n}`. - This coincides with the standard (global) fidelity in the limit of - the fidelity approaching 1. Might be used to increase the variance - to improve trainability in algorithms such as :class:`~.time_evolvers.PVQD`. - - Raises: - ValueError: If the sampler is not an instance of ``BaseSampler``. - """ - if not isinstance(sampler, BaseSampler): - raise ValueError( - f"The sampler should be an instance of BaseSampler, " f"but got {type(sampler)}" - ) - self._sampler: BaseSampler = sampler - self._local = local - self._default_options = Options() - if options is not None: - self._default_options.update_options(**options) - super().__init__() - - def create_fidelity_circuit( - self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit - ) -> QuantumCircuit: - """ - Combines ``circuit_1`` and ``circuit_2`` to create the - fidelity circuit following the compute-uncompute method. - - Args: - circuit_1: (Parametrized) quantum circuit. - circuit_2: (Parametrized) quantum circuit. - - Returns: - The fidelity quantum circuit corresponding to circuit_1 and circuit_2. - """ - if len(circuit_1.clbits) > 0: - circuit_1.remove_final_measurements() - if len(circuit_2.clbits) > 0: - circuit_2.remove_final_measurements() - - circuit = circuit_1.compose(circuit_2.inverse()) - circuit.measure_all() - return circuit - - def _run( - self, - circuits_1: QuantumCircuit | Sequence[QuantumCircuit], - circuits_2: QuantumCircuit | Sequence[QuantumCircuit], - values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, - values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - **options, - ) -> StateFidelityResult: - r""" - Computes the state overlap (fidelity) calculation between two - (parametrized) circuits (first and second) for a specific set of parameter - values (first and second) following the compute-uncompute method. - - Args: - circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. - circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. - values_1: Numerical parameters to be bound to the first circuits. - values_2: Numerical parameters to be bound to the second circuits. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > fidelity's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting. - - Returns: - The result of the fidelity calculation. - - Raises: - ValueError: At least one pair of circuits must be defined. - AlgorithmError: If the sampler job is not completed successfully. - """ - - circuits = self._construct_circuits(circuits_1, circuits_2) - if len(circuits) == 0: - raise ValueError( - "At least one pair of circuits must be defined to calculate the state overlap." - ) - values = self._construct_value_list(circuits_1, circuits_2, values_1, values_2) - - # The priority of run options is as follows: - # options in `evaluate` method > fidelity's default options > - # primitive's default options. - opts = copy(self._default_options) - opts.update_options(**options) - - job = self._sampler.run(circuits=circuits, parameter_values=values, **opts.__dict__) - - try: - result = job.result() - except Exception as exc: - raise AlgorithmError("Sampler job failed!") from exc - - if self._local: - raw_fidelities = [ - self._get_local_fidelity(prob_dist, circuit.num_qubits) - for prob_dist, circuit in zip(result.quasi_dists, circuits) - ] - else: - raw_fidelities = [ - self._get_global_fidelity(prob_dist) for prob_dist in result.quasi_dists - ] - fidelities = self._truncate_fidelities(raw_fidelities) - - return StateFidelityResult( - fidelities=fidelities, - raw_fidelities=raw_fidelities, - metadata=result.metadata, - options=self._get_local_options(opts.__dict__), - ) - - @property - def options(self) -> Options: - """Return the union of estimator options setting and fidelity default options, - where, if the same field is set in both, the fidelity's default options override - the primitive's default setting. - - Returns: - The fidelity default + estimator options. - """ - return self._get_local_options(self._default_options.__dict__) - - def update_default_options(self, **options): - """Update the fidelity's default options setting. - - Args: - **options: The fields to update the default options. - """ - - self._default_options.update_options(**options) - - def _get_local_options(self, options: Options) -> Options: - """Return the union of the primitive's default setting, - the fidelity default options, and the options in the ``run`` method. - The order of priority is: options in ``run`` method > fidelity's - default options > primitive's default setting. - - Args: - options: The fields to update the options - - Returns: - The fidelity default + estimator + run options. - """ - opts = copy(self._sampler.options) - opts.update_options(**options) - return opts - - def _get_global_fidelity(self, probability_distribution: dict[int, float]) -> float: - """Process the probability distribution of a measurement to determine the - global fidelity. - - Args: - probability_distribution: Obtained from the measurement result - - Returns: - The global fidelity. - """ - return probability_distribution.get(0, 0) - - def _get_local_fidelity( - self, probability_distribution: dict[int, float], num_qubits: int - ) -> float: - """Process the probability distribution of a measurement to determine the - local fidelity by averaging over single-qubit projectors. - - Args: - probability_distribution: Obtained from the measurement result - - Returns: - The local fidelity. - """ - fidelity = 0.0 - for qubit in range(num_qubits): - for bitstring, prob in probability_distribution.items(): - # Check whether the bit representing the current qubit is 0 - if not bitstring >> qubit & 1: - fidelity += prob / num_qubits - return fidelity diff --git a/qiskit/algorithms/state_fidelities/state_fidelity_result.py b/qiskit/algorithms/state_fidelities/state_fidelity_result.py deleted file mode 100644 index 88dca035f94c..000000000000 --- a/qiskit/algorithms/state_fidelities/state_fidelity_result.py +++ /dev/null @@ -1,37 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -""" -Fidelity result class -""" - -from __future__ import annotations - -from collections.abc import Sequence, Mapping -from typing import Any -from dataclasses import dataclass - -from qiskit.providers import Options - - -@dataclass(frozen=True) -class StateFidelityResult: - """This class stores the result of StateFidelity computations.""" - - fidelities: Sequence[float] - """List of truncated fidelity values for each pair of input circuits, ensured to be in [0,1].""" - raw_fidelities: Sequence[float] - """List of raw fidelity values for each pair of input circuits, which might not be in [0,1] - depending on the error mitigation method used.""" - metadata: Sequence[Mapping[str, Any]] - """Additional information about the fidelity calculation.""" - options: Options - """Primitive runtime options for the execution of the fidelity job.""" diff --git a/qiskit/algorithms/time_evolvers/__init__.py b/qiskit/algorithms/time_evolvers/__init__.py deleted file mode 100644 index c2ad1fe7ec26..000000000000 --- a/qiskit/algorithms/time_evolvers/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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 Time Evolution package.""" - -from .imaginary_time_evolver import ImaginaryTimeEvolver -from .real_time_evolver import RealTimeEvolver -from .time_evolution_problem import TimeEvolutionProblem -from .time_evolution_result import TimeEvolutionResult -from .trotterization import TrotterQRTE -from .pvqd import PVQD, PVQDResult -from .classical_methods import SciPyImaginaryEvolver, SciPyRealEvolver -from .variational import VarQITE, VarQRTE, VarQTE, VarQTEResult - -__all__ = [ - "ImaginaryTimeEvolver", - "RealTimeEvolver", - "TimeEvolutionProblem", - "TimeEvolutionResult", - "TrotterQRTE", - "PVQD", - "PVQDResult", - "SciPyImaginaryEvolver", - "SciPyRealEvolver", - "VarQITE", - "VarQRTE", - "VarQTE", - "VarQTEResult", -] diff --git a/qiskit/algorithms/time_evolvers/classical_methods/__init__.py b/qiskit/algorithms/time_evolvers/classical_methods/__init__.py deleted file mode 100644 index 266b349fbef7..000000000000 --- a/qiskit/algorithms/time_evolvers/classical_methods/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Classical Methods for Quantum Time Evolution package.""" - -from .scipy_real_evolver import SciPyRealEvolver -from .scipy_imaginary_evolver import SciPyImaginaryEvolver - -__all__ = ["SciPyRealEvolver", "SciPyImaginaryEvolver"] diff --git a/qiskit/algorithms/time_evolvers/classical_methods/evolve.py b/qiskit/algorithms/time_evolvers/classical_methods/evolve.py deleted file mode 100644 index 18c679a03c8f..000000000000 --- a/qiskit/algorithms/time_evolvers/classical_methods/evolve.py +++ /dev/null @@ -1,219 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -"""Auxiliary functions for SciPy Time Evolvers""" -from __future__ import annotations -import logging -from scipy.sparse import csr_matrix -from scipy.sparse.linalg import expm_multiply -import numpy as np - -from qiskit.quantum_info.states import Statevector -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from qiskit import QuantumCircuit -from qiskit.opflow import PauliSumOp -from ..time_evolution_problem import TimeEvolutionProblem -from ..time_evolution_result import TimeEvolutionResult -from ...exceptions import AlgorithmError - -from ...list_or_dict import ListOrDict - -logger = logging.getLogger(__name__) - - -def _create_observable_output( - ops_ev_mean: np.ndarray, - evolution_problem: TimeEvolutionProblem, -) -> tuple[ListOrDict[tuple[np.ndarray, np.ndarray]], np.ndarray]: - """Creates the right output format for the evaluated auxiliary operators. - Args: - ops_ev_mean: Array containing the expectation value of each observable at each timestep. - evolution_problem: Time Evolution Problem to create the output of. - - Returns: - An output with the observables mean value at the appropriate times depending on whether - the auxiliary operators in the time evolution problem are a `list` or a `dict`. - - """ - - aux_ops = evolution_problem.aux_operators - - time_array = np.linspace(0, evolution_problem.time, ops_ev_mean.shape[-1]) - zero_array = np.zeros(ops_ev_mean.shape[-1]) # std=0 since it is an exact method - - operators_number = 0 if aux_ops is None else len(aux_ops) - - observable_evolution = [(ops_ev_mean[i], zero_array) for i in range(operators_number)] - - if isinstance(aux_ops, dict): - observable_evolution = dict(zip(aux_ops.keys(), observable_evolution)) - - return observable_evolution, time_array - - -def _create_obs_final( - ops_ev_mean: np.ndarray, - evolution_problem: TimeEvolutionProblem, -) -> ListOrDict[tuple[complex, complex]]: - """Creates the right output format for the final value of the auxiliary operators. - - Args: - ops_ev_mean: Array containing the expectation value of each observable at the final timestep. - evolution_problem: Evolution problem to create the output of. - - Returns: - An output with the observables mean value at the appropriate times depending on whether - the auxiliary operators in the evolution problem are a `list` or a `dict`. - - """ - - aux_ops = evolution_problem.aux_operators - aux_ops_evaluated: ListOrDict[tuple[complex, complex]] = [(op_ev, 0) for op_ev in ops_ev_mean] - if isinstance(aux_ops, dict): - aux_ops_evaluated = dict(zip(aux_ops.keys(), aux_ops_evaluated)) - return aux_ops_evaluated - - -def _evaluate_aux_ops( - aux_ops: list[csr_matrix], - state: np.ndarray, -) -> np.ndarray: - """Evaluates the aux operators if they are provided and stores their value. - - Returns: - Mean of the aux operators for a given state. - """ - op_means = np.array([np.real(state.conjugate().dot(op.dot(state))) for op in aux_ops]) - return op_means - - -def _operator_to_matrix(operator: BaseOperator | PauliSumOp): - - if isinstance(operator, PauliSumOp): - op_matrix = operator.to_spmatrix() - else: - try: - op_matrix = operator.to_matrix(sparse=True) - except TypeError: - logger.debug( - "WARNING: operator of type `%s` does not support sparse matrices. " - "Trying dense computation", - type(operator), - ) - try: - op_matrix = operator.to_matrix() - except AttributeError as ex: - raise AlgorithmError(f"Unsupported operator type `{type(operator)}`.") from ex - return op_matrix - - -def _build_scipy_operators( - evolution_problem: TimeEvolutionProblem, num_timesteps: int, real_time: bool -) -> tuple[np.ndarray, list[csr_matrix], csr_matrix]: - """Returns the matrices and parameters needed for time evolution in the appropriate format. - - Args: - evolution_problem: The definition of the evolution problem. - num_timesteps: Number of timesteps to be performed. - real_time: If `True`, returned operators will correspond to real time evolution, - Else, they will correspond to imaginary time evolution. - - Returns: - A tuple with the initial state, the list of operators to evaluate and the operator to be - exponentiated to perform one timestep. - - Raises: - ValueError: If the Hamiltonian can not be converted into a sparse matrix or dense matrix. - """ - # Convert the initial state and Hamiltonian into sparse matrices. - if isinstance(evolution_problem.initial_state, QuantumCircuit): - state = Statevector(evolution_problem.initial_state).data - else: - state = evolution_problem.initial_state.data - - hamiltonian = _operator_to_matrix(operator=evolution_problem.hamiltonian) - - if isinstance(evolution_problem.aux_operators, list): - aux_ops = [ - _operator_to_matrix(operator=aux_op) for aux_op in evolution_problem.aux_operators - ] - elif isinstance(evolution_problem.aux_operators, dict): - aux_ops = [ - _operator_to_matrix(operator=aux_op) - for aux_op in evolution_problem.aux_operators.values() - ] - else: - aux_ops = [] - timestep = evolution_problem.time / num_timesteps - step_operator = -((1.0j) ** real_time) * timestep * hamiltonian - return state, aux_ops, step_operator - - -def _evolve( - evolution_problem: TimeEvolutionProblem, num_timesteps: int, real_time: bool -) -> TimeEvolutionResult: - r"""Performs either real or imaginary time evolution :math:`\exp(-i t H)|\Psi\rangle`. - - Args: - evolution_problem: The definition of the evolution problem. - num_timesteps: Number of timesteps to be performed. - real_time: If `True`, returned operators will correspond to real time evolution, - Else, they will correspond to imaginary time evolution. - - Returns: - Evolution result which includes an evolved quantum state. - - Raises: - ValueError: If the Hamiltonian is time dependent. - ValueError: If the initial state is `None`. - - """ - if num_timesteps <= 0: - raise ValueError("Variable `num_timesteps` needs to be a positive integer.") - - if evolution_problem.t_param is not None: - raise ValueError("Time dependent Hamiltonians are not supported.") - - if evolution_problem.initial_state is None: - raise ValueError("Initial state is `None`") - - state, aux_ops, step_operator = _build_scipy_operators( - evolution_problem=evolution_problem, num_timesteps=num_timesteps, real_time=real_time - ) - - # Create empty arrays to store the time evolution of the aux operators. - number_operators = ( - 0 if evolution_problem.aux_operators is None else len(evolution_problem.aux_operators) - ) - ops_ev_mean = np.empty(shape=(number_operators, num_timesteps + 1), dtype=complex) - - renormalize = ( - (lambda state: state) if real_time else (lambda state: state / np.linalg.norm(state)) - ) - - # Perform the time evolution and stores the value of the operators at each timestep. - for ts in range(num_timesteps): - ops_ev_mean[:, ts] = _evaluate_aux_ops(aux_ops, state) - state = expm_multiply(A=step_operator, B=state) - state = renormalize(state) - - ops_ev_mean[:, num_timesteps] = _evaluate_aux_ops(aux_ops, state) - - observable_history, times = _create_observable_output(ops_ev_mean, evolution_problem) - aux_ops_evaluated = _create_obs_final(ops_ev_mean[:, -1], evolution_problem) - - return TimeEvolutionResult( - evolved_state=Statevector(state), - aux_ops_evaluated=aux_ops_evaluated, - observables=observable_history, - times=times, - ) diff --git a/qiskit/algorithms/time_evolvers/classical_methods/scipy_imaginary_evolver.py b/qiskit/algorithms/time_evolvers/classical_methods/scipy_imaginary_evolver.py deleted file mode 100644 index f181da10f436..000000000000 --- a/qiskit/algorithms/time_evolvers/classical_methods/scipy_imaginary_evolver.py +++ /dev/null @@ -1,51 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Classical Quantum Imaginary Time Evolution.""" - -from ..time_evolution_problem import TimeEvolutionProblem -from ..time_evolution_result import TimeEvolutionResult -from ..imaginary_time_evolver import ImaginaryTimeEvolver -from .evolve import _evolve - - -class SciPyImaginaryEvolver(ImaginaryTimeEvolver): - r"""Classical Evolver for imaginary time evolution. - - Evolves an initial state :math:`|\Psi\rangle` for an imaginary time :math:`\tau = it` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - Note that the precision of the evolver does not depend on the number of - timesteps taken. - """ - - def __init__(self, num_timesteps: int): - r""" - Args: - num_timesteps: The number of timesteps in the simulation. - Raises: - ValueError: If `num_timesteps` is not a positive integer. - """ - self.num_timesteps = num_timesteps - - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - r"""Perform imaginary time evolution :math:`\exp(-\tau H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for an imaginary time :math:`\tau` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - return _evolve(evolution_problem, self.num_timesteps, real_time=False) diff --git a/qiskit/algorithms/time_evolvers/classical_methods/scipy_real_evolver.py b/qiskit/algorithms/time_evolvers/classical_methods/scipy_real_evolver.py deleted file mode 100644 index b01c16205bfd..000000000000 --- a/qiskit/algorithms/time_evolvers/classical_methods/scipy_real_evolver.py +++ /dev/null @@ -1,50 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Classical Quantum Real Time Evolution.""" -from .evolve import _evolve -from ..time_evolution_problem import TimeEvolutionProblem -from ..time_evolution_result import TimeEvolutionResult -from ..real_time_evolver import RealTimeEvolver - - -class SciPyRealEvolver(RealTimeEvolver): - r"""Classical Evolver for real time evolution. - - Evolves an initial state :math:`|\Psi\rangle` for a time :math:`t` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - Note that the precision of the evolver does not depend on the number of - timesteps taken. - """ - - def __init__(self, num_timesteps: int): - """ - Args: - num_timesteps: The number of timesteps in the simulation. - Raises: - ValueError: If `steps` is not a positive integer. - """ - self.num_timesteps = num_timesteps - - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - r"""Perform real time evolution :math:`\exp(-i t H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for a time :math:`t` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - return _evolve(evolution_problem, self.num_timesteps, real_time=True) diff --git a/qiskit/algorithms/time_evolvers/imaginary_time_evolver.py b/qiskit/algorithms/time_evolvers/imaginary_time_evolver.py deleted file mode 100644 index e62d02e5ab9c..000000000000 --- a/qiskit/algorithms/time_evolvers/imaginary_time_evolver.py +++ /dev/null @@ -1,37 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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. - -"""Interface for Quantum Imaginary Time Evolution.""" - -from abc import ABC, abstractmethod - -from .time_evolution_problem import TimeEvolutionProblem -from .time_evolution_result import TimeEvolutionResult - - -class ImaginaryTimeEvolver(ABC): - """Interface for Quantum Imaginary Time Evolution.""" - - @abstractmethod - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - r"""Perform imaginary time evolution :math:`\exp(-\tau H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for an imaginary time :math:`\tau` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - raise NotImplementedError() diff --git a/qiskit/algorithms/time_evolvers/pvqd/__init__.py b/qiskit/algorithms/time_evolvers/pvqd/__init__.py deleted file mode 100644 index 9377ce631b4e..000000000000 --- a/qiskit/algorithms/time_evolvers/pvqd/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""The projected Variational Quantum Dynamic (p-VQD) module.""" - -from .pvqd_result import PVQDResult -from .pvqd import PVQD - -__all__ = ["PVQD", "PVQDResult"] diff --git a/qiskit/algorithms/time_evolvers/pvqd/pvqd.py b/qiskit/algorithms/time_evolvers/pvqd/pvqd.py deleted file mode 100644 index bbd48df86651..000000000000 --- a/qiskit/algorithms/time_evolvers/pvqd/pvqd.py +++ /dev/null @@ -1,435 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2022, 2023. -# -# 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. - -"""The projected Variational Quantum Dynamics Algorithm.""" -from __future__ import annotations - -import logging -import warnings -from collections.abc import Callable - -import numpy as np - -from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit -from qiskit.circuit.library import PauliEvolutionGate -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.synthesis import EvolutionSynthesis, LieTrotter -from qiskit.utils import algorithm_globals - -from ...exceptions import AlgorithmError, QiskitError -from ...optimizers import Minimizer, Optimizer -from ...state_fidelities.base_state_fidelity import BaseStateFidelity -from ..real_time_evolver import RealTimeEvolver -from ..time_evolution_problem import TimeEvolutionProblem -from ..time_evolution_result import TimeEvolutionResult -from .pvqd_result import PVQDResult -from .utils import _get_observable_evaluator, _is_gradient_supported - -logger = logging.getLogger(__name__) - - -class PVQD(RealTimeEvolver): - """The projected Variational Quantum Dynamics (p-VQD) Algorithm. - - In each timestep, this algorithm computes the next state with a Trotter formula - (specified by the ``evolution`` argument) and projects the timestep onto a variational form - (``ansatz``). The projection is determined by maximizing the fidelity of the Trotter-evolved - state and the ansatz, using a classical optimization routine. See Ref. [1] for details. - - The following attributes can be set via the initializer but can also be read and - updated once the PVQD object has been constructed. - - Attributes: - - ansatz (QuantumCircuit): The parameterized circuit representing the time-evolved state. - initial_parameters (np.ndarray): The parameters of the ansatz at time 0. - optimizer (Optional[Union[Optimizer, Minimizer]]): The classical optimization routine - used to maximize the fidelity of the Trotter step and ansatz. - num_timesteps (Optional[int]): The number of timesteps to take. If None, it is automatically - selected to achieve a timestep of approximately 0.01. - evolution (Optional[EvolutionSynthesis]): The method to perform the Trotter step. - Defaults to first-order Lie-Trotter evolution. - use_parameter_shift (bool): If True, use the parameter shift rule for loss function - gradients (if the ansatz supports). - initial_guess (Optional[np.ndarray]): The starting point for the first classical optimization - run, at time 0. Defaults to random values in :math:`[-0.01, 0.01]`. - - Example: - - This snippet computes the real time evolution of a quantum Ising model on two - neighboring sites and keeps track of the magnetization. - - .. code-block:: python - - import numpy as np - - from qiskit.algorithms.state_fidelities import ComputeUncompute - from qiskit.algorithms.time_evolvers import TimeEvolutionProblem, PVQD - from qiskit.primitives import Estimator, Sampler - from qiskit.circuit.library import EfficientSU2 - from qiskit.quantum_info import SparsePauliOp, Pauli - from qiskit.algorithms.optimizers import L_BFGS_B - - sampler = Sampler() - fidelity = ComputeUncompute(sampler) - estimator = Estimator() - hamiltonian = 0.1 * SparsePauliOp(["ZZ", "IX", "XI"]) - observable = Pauli("ZZ") - ansatz = EfficientSU2(2, reps=1) - initial_parameters = np.zeros(ansatz.num_parameters) - - time = 1 - optimizer = L_BFGS_B() - - # setup the algorithm - pvqd = PVQD( - fidelity, - ansatz, - initial_parameters, - estimator, - num_timesteps=100, - optimizer=optimizer, - ) - - # specify the evolution problem - problem = TimeEvolutionProblem( - hamiltonian, time, aux_operators=[hamiltonian, observable] - ) - - # and evolve! - result = pvqd.evolve(problem) - - References: - - [1] Stefano Barison, Filippo Vicentini, and Giuseppe Carleo (2021), An efficient - quantum algorithm for the time evolution of parameterized circuits, - `Quantum 5, 512 `_. - """ - - def __init__( - self, - fidelity: BaseStateFidelity, - ansatz: QuantumCircuit, - initial_parameters: np.ndarray, - estimator: BaseEstimator | None = None, - optimizer: Optimizer | Minimizer | None = None, - num_timesteps: int | None = None, - evolution: EvolutionSynthesis | None = None, - use_parameter_shift: bool = True, - initial_guess: np.ndarray | None = None, - ) -> None: - """ - Args: - fidelity: A fidelity primitive used by the algorithm. - ansatz: A parameterized circuit preparing the variational ansatz to model the - time evolved quantum state. - initial_parameters: The initial parameters for the ansatz. Together with the ansatz, - these define the initial state of the time evolution. - estimator: An estimator primitive used for calculating expected values of auxiliary - operators (if provided via the problem). - optimizer: The classical optimizers used to minimize the overlap between - Trotterization and ansatz. Can be either a :class:`.Optimizer` or a callable - using the :class:`.Minimizer` protocol. This argument is optional since it is - not required for :meth:`get_loss`, but it has to be set before :meth:`evolve` - is called. - num_timesteps: The number of time steps. If ``None`` it will be set such that the - timestep is close to 0.01. - evolution: The evolution synthesis to use for the construction of the Trotter step. - Defaults to first-order Lie-Trotter decomposition, see also - :mod:`~qiskit.synthesis.evolution` for different options. - use_parameter_shift: If True, use the parameter shift rule to compute gradients. - If False, the optimizer will not be passed a gradient callable. In that case, - Qiskit optimizers will use a finite difference rule to approximate the gradients. - initial_guess: The initial guess for the first VQE optimization. Afterwards the - previous iteration result is used as initial guess. If None, this is set to - a random vector with elements in the interval :math:`[-0.01, 0.01]`. - """ - super().__init__() - if evolution is None: - evolution = LieTrotter() - - self.ansatz = ansatz - self.initial_parameters = initial_parameters - self.num_timesteps = num_timesteps - self.optimizer = optimizer - self.initial_guess = initial_guess - self.estimator = estimator - self.fidelity_primitive = fidelity - self.evolution = evolution - self.use_parameter_shift = use_parameter_shift - - def step( - self, - hamiltonian: BaseOperator | PauliSumOp, - ansatz: QuantumCircuit, - theta: np.ndarray, - dt: float, - initial_guess: np.ndarray, - ) -> tuple[np.ndarray, float]: - """Perform a single time step. - - Args: - hamiltonian: The Hamiltonian under which to evolve. - ansatz: The parameterized quantum circuit which attempts to approximate the - time-evolved state. - theta: The current parameters. - dt: The time step. - initial_guess: The initial guess for the classical optimization of the - fidelity between the next variational state and the Trotter-evolved last state. - If None, this is set to a random vector with elements in the interval - :math:`[-0.01, 0.01]`. - - Returns: - A tuple consisting of the next parameters and the fidelity of the optimization. - """ - self._validate_setup() - - loss, gradient = self.get_loss(hamiltonian, ansatz, dt, theta) - - if initial_guess is None: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - initial_guess = algorithm_globals.random.random(self.initial_parameters.size) * 0.01 - - if isinstance(self.optimizer, Optimizer): - optimizer_result = self.optimizer.minimize(loss, initial_guess, gradient) - else: - optimizer_result = self.optimizer(loss, initial_guess, gradient) - - # clip the fidelity to [0, 1] - fidelity = np.clip(1 - optimizer_result.fun, 0, 1) - - return theta + optimizer_result.x, fidelity - - def get_loss( - self, - hamiltonian: BaseOperator | PauliSumOp, - ansatz: QuantumCircuit, - dt: float, - current_parameters: np.ndarray, - ) -> tuple[Callable[[np.ndarray], float], Callable[[np.ndarray], np.ndarray]] | None: - """Get a function to evaluate the infidelity between Trotter step and ansatz. - - Args: - hamiltonian: The Hamiltonian under which to evolve. - ansatz: The parameterized quantum circuit which attempts to approximate the - time-evolved state. - dt: The time step. - current_parameters: The current parameters. - - Returns: - A callable to evaluate the infidelity and, if gradients are supported and required, - a second callable to evaluate the gradient of the infidelity. - """ - self._validate_setup(skip={"optimizer"}) - - # use Trotterization to evolve the current state - trotterized = ansatz.assign_parameters(current_parameters) - - evolution_gate = PauliEvolutionGate(hamiltonian, time=dt, synthesis=self.evolution) - - trotterized.append(evolution_gate, ansatz.qubits) - - # define the overlap of the Trotterized state and the ansatz - x = ParameterVector("w", ansatz.num_parameters) - shifted = ansatz.assign_parameters(current_parameters + x) - - def evaluate_loss(displacement: np.ndarray | list[np.ndarray]) -> float | np.ndarray: - """Evaluate the overlap of the ansatz with the Trotterized evolution. - - Args: - displacement: The parameters for the ansatz. - - Returns: - The fidelity of the ansatz with parameters ``theta`` and the Trotterized evolution. - - Raises: - AlgorithmError: If a primitive job fails. - """ - if isinstance(displacement, list): - displacement = np.asarray(displacement) - value_dict = {x_i: displacement[:, i].tolist() for i, x_i in enumerate(x)} - else: - value_dict = dict(zip(x, displacement)) - - param_dicts = self._transpose_param_dicts(value_dict) - num_of_param_sets = len(param_dicts) - states1 = [trotterized] * num_of_param_sets - states2 = [shifted] * num_of_param_sets - param_dicts2 = [list(param_dict.values()) for param_dict in param_dicts] - # the first state does not have free parameters so values_1 will be None by default - try: - job = self.fidelity_primitive.run(states1, states2, values_2=param_dicts2) - fidelities = np.array(job.result().fidelities) - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - - if len(fidelities) == 1: - fidelities = fidelities[0] - - # in principle, we could add different loss functions here, but we're currently - # not aware of a use-case for a different one than in the paper - return 1 - fidelities - - if _is_gradient_supported(ansatz) and self.use_parameter_shift: - - def evaluate_gradient(displacement: np.ndarray) -> np.ndarray: - """Evaluate the gradient with the parameter-shift rule. - - This is hardcoded here since the gradient framework does not support computing - gradients for overlaps. - - Args: - displacement: The parameters for the ansatz. - - Returns: - The gradient. - """ - # construct lists where each element is shifted by plus (or minus) pi/2 - dim = displacement.size - plus_shifts = (displacement + np.pi / 2 * np.identity(dim)).tolist() - minus_shifts = (displacement - np.pi / 2 * np.identity(dim)).tolist() - - evaluated = evaluate_loss(plus_shifts + minus_shifts) - - gradient = (evaluated[:dim] - evaluated[dim:]) / 2 - - return gradient - - else: - evaluate_gradient = None - - return evaluate_loss, evaluate_gradient - - def _transpose_param_dicts(self, params: dict) -> list[dict[Parameter, float]]: - p_0 = list(params.values())[0] - if isinstance(p_0, (list, np.ndarray)): - num_parameterizations = len(p_0) - param_bindings = [ - {param: value_list[i] for param, value_list in params.items()} # type: ignore - for i in range(num_parameterizations) - ] - else: - param_bindings = [params] - - return param_bindings - - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - r"""Perform real time evolution :math:`\exp(-i t H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for a time :math:`t` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The evolution problem containing the hamiltonian, total evolution - time and observables to evaluate. - - Returns: - A result object containing the evolution information and evaluated observables. - - Raises: - ValueError: If ``aux_operators`` provided in the time evolution problem but no estimator - provided to the algorithm. - NotImplementedError: If the evolution problem contains an initial state. - """ - self._validate_setup() - - time = evolution_problem.time - observables = evolution_problem.aux_operators - hamiltonian = evolution_problem.hamiltonian - - # determine the number of timesteps and set the timestep - num_timesteps = ( - int(np.ceil(time / 0.01)) if self.num_timesteps is None else self.num_timesteps - ) - timestep = time / num_timesteps - - if evolution_problem.initial_state is not None: - raise NotImplementedError( - "Setting an initial state for the evolution is not yet supported for PVQD." - ) - - # get the function to evaluate the observables for a given set of ansatz parameters - if observables is not None: - if self.estimator is None: - raise ValueError( - "The evolution problem contained aux_operators but no estimator was provided. " - ) - evaluate_observables = _get_observable_evaluator( - self.ansatz, observables, self.estimator - ) - observable_values = [evaluate_observables(self.initial_parameters)] - - fidelities = [1.0] - parameters = [self.initial_parameters] - times = np.linspace(0, time, num_timesteps + 1).tolist() # +1 to include initial time 0 - - initial_guess = self.initial_guess - - for _ in range(num_timesteps): - # perform VQE to find the next parameters - next_parameters, fidelity = self.step( - hamiltonian, self.ansatz, parameters[-1], timestep, initial_guess - ) - - # set initial guess to last parameter update - initial_guess = next_parameters - parameters[-1] - - parameters.append(next_parameters) - fidelities.append(fidelity) - if observables is not None: - observable_values.append(evaluate_observables(next_parameters)) - - evolved_state = self.ansatz.assign_parameters(parameters[-1]) - - result = PVQDResult( - evolved_state=evolved_state, - times=times, - parameters=parameters, - fidelities=fidelities, - estimated_error=1 - np.prod(fidelities), - ) - if observables is not None: - result.observables = observable_values - result.aux_ops_evaluated = observable_values[-1] - - return result - - def _validate_setup(self, skip=None): - """Validate the current setup and raise an error if something misses to run.""" - - if skip is None: - skip = {} - - required_attributes = {"optimizer"}.difference(skip) - - for attr in required_attributes: - if getattr(self, attr, None) is None: - raise ValueError(f"The {attr} cannot be None.") - - if self.num_timesteps is not None and self.num_timesteps <= 0: - raise ValueError( - f"The number of timesteps must be positive but is {self.num_timesteps}." - ) - - if self.ansatz.num_parameters == 0: - raise QiskitError( - "The ansatz cannot have 0 parameters, otherwise it cannot be trained." - ) - - if len(self.initial_parameters) != self.ansatz.num_parameters: - raise QiskitError( - f"Mismatching number of parameters in the ansatz ({self.ansatz.num_parameters}) " - f"and the initial parameters ({len(self.initial_parameters)})." - ) diff --git a/qiskit/algorithms/time_evolvers/pvqd/pvqd_result.py b/qiskit/algorithms/time_evolvers/pvqd/pvqd_result.py deleted file mode 100644 index 65c2a8b18604..000000000000 --- a/qiskit/algorithms/time_evolvers/pvqd/pvqd_result.py +++ /dev/null @@ -1,54 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Result object for p-VQD.""" -from __future__ import annotations - -from collections.abc import Sequence - -import numpy as np -from qiskit.circuit import QuantumCircuit -from ..time_evolution_result import TimeEvolutionResult - - -class PVQDResult(TimeEvolutionResult): - """The result object for the p-VQD algorithm.""" - - def __init__( - self, - evolved_state: QuantumCircuit, - aux_ops_evaluated: list[tuple[complex, complex]] | None = None, - times: list[float] | None = None, - parameters: list[np.ndarray] | None = None, - fidelities: Sequence[float] | None = None, - estimated_error: float | None = None, - observables: list[list[float]] | None = None, - ): - """ - Args: - evolved_state: An evolved quantum state. - aux_ops_evaluated: Optional list of observables for which expected values on an evolved - state are calculated. These values are in fact tuples formatted as (mean, standard - deviation). - times: The times evaluated during the time integration. - parameters: The parameter values at each evaluation time. - fidelities: The fidelity of the Trotter step and variational update at each iteration. - estimated_error: The overall estimated error evaluated as one minus the - product of all fidelities. - observables: The value of the observables evaluated at each iteration. - """ - super().__init__(evolved_state, aux_ops_evaluated) - self.times = times - self.parameters = parameters - self.fidelities = fidelities - self.estimated_error = estimated_error - self.observables = observables diff --git a/qiskit/algorithms/time_evolvers/pvqd/utils.py b/qiskit/algorithms/time_evolvers/pvqd/utils.py deleted file mode 100644 index 9b3f330dd350..000000000000 --- a/qiskit/algorithms/time_evolvers/pvqd/utils.py +++ /dev/null @@ -1,109 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - - -"""Utilities for p-VQD.""" -from __future__ import annotations -import logging -from collections.abc import Callable - -import numpy as np - -from qiskit.circuit import QuantumCircuit, Parameter, ParameterExpression -from qiskit.compiler import transpile -from qiskit.exceptions import QiskitError -from qiskit.opflow.gradients.circuit_gradients import ParamShift -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info.operators.base_operator import BaseOperator -from ...exceptions import AlgorithmError - -logger = logging.getLogger(__name__) - - -def _is_gradient_supported(ansatz: QuantumCircuit) -> bool: - """Check whether we can apply a simple parameter shift rule to obtain gradients.""" - - # check whether the circuit can be unrolled to supported gates - try: - unrolled = transpile(ansatz, basis_gates=ParamShift.SUPPORTED_GATES, optimization_level=0) - except QiskitError: - # failed to map to supported basis - logger.log( - logging.INFO, - "No gradient support: Failed to unroll to gates supported by parameter-shift.", - ) - return False - - # check whether all parameters are unique and we do not need to apply the chain rule - # (since it's not implemented yet) - total_num_parameters = 0 - for circuit_instruction in unrolled.data: - for param in circuit_instruction.operation.params: - if isinstance(param, ParameterExpression): - if isinstance(param, Parameter): - total_num_parameters += 1 - else: - logger.log( - logging.INFO, - "No gradient support: Circuit is only allowed to have plain parameters, " - "as the chain rule is not yet implemented.", - ) - return False - - if total_num_parameters != ansatz.num_parameters: - logger.log( - logging.INFO, - "No gradient support: Circuit is only allowed to have unique parameters, " - "as the product rule is not yet implemented.", - ) - return False - - return True - - -def _get_observable_evaluator( - ansatz: QuantumCircuit, - observables: BaseOperator | list[BaseOperator], - estimator: BaseEstimator, -) -> Callable[[np.ndarray], float | list[float]]: - """Get a callable to evaluate a (list of) observable(s) for given circuit parameters.""" - - def evaluate_observables(theta: np.ndarray) -> float | list[float]: - """Evaluate the observables for the ansatz parameters ``theta``. - - Args: - theta: The ansatz parameters. - - Returns: - The observables evaluated at the ansatz parameters. - - Raises: - AlgorithmError: If a primitive job fails. - """ - if isinstance(observables, list): - num_observables = len(observables) - obs = observables - else: - num_observables = 1 - obs = [observables] - states = [ansatz] * num_observables - parameter_values = [theta] * num_observables - - try: - estimator_job = estimator.run(states, obs, parameter_values=parameter_values) - results = estimator_job.result().values - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - - return results - - return evaluate_observables diff --git a/qiskit/algorithms/time_evolvers/real_time_evolver.py b/qiskit/algorithms/time_evolvers/real_time_evolver.py deleted file mode 100644 index 585da953755b..000000000000 --- a/qiskit/algorithms/time_evolvers/real_time_evolver.py +++ /dev/null @@ -1,37 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# 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. - -"""Interface for Quantum Real Time Evolution.""" - -from abc import ABC, abstractmethod - -from .time_evolution_problem import TimeEvolutionProblem -from .time_evolution_result import TimeEvolutionResult - - -class RealTimeEvolver(ABC): - """Interface for Quantum Real Time Evolution.""" - - @abstractmethod - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - r"""Perform real time evolution :math:`\exp(-i t H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for a time :math:`t` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - raise NotImplementedError() diff --git a/qiskit/algorithms/time_evolvers/time_evolution_problem.py b/qiskit/algorithms/time_evolvers/time_evolution_problem.py deleted file mode 100644 index 87159558baf5..000000000000 --- a/qiskit/algorithms/time_evolvers/time_evolution_problem.py +++ /dev/null @@ -1,114 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Time evolution problem class.""" -from __future__ import annotations - -from collections.abc import Mapping - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter, ParameterExpression -from qiskit.opflow import PauliSumOp -from ..list_or_dict import ListOrDict -from ...quantum_info import Statevector -from ...quantum_info.operators.base_operator import BaseOperator - - -class TimeEvolutionProblem: - """Time evolution problem class. - - This class is the input to time evolution algorithms and must contain information on the total - evolution time, a quantum state to be evolved and under which Hamiltonian the state is evolved. - - Attributes: - hamiltonian (BaseOperator | PauliSumOp): The Hamiltonian under which to evolve the system. - initial_state (QuantumCircuit | Statevector | None): The quantum state to be evolved for - methods like Trotterization. For variational time evolutions, where the evolution - happens in an ansatz, this argument is not required. - aux_operators (ListOrDict[BaseOperator | PauliSumOp] | None): Optional list of auxiliary - operators to be evaluated with the evolved ``initial_state`` and their expectation - values returned. - truncation_threshold (float): Defines a threshold under which values can be assumed to be 0. - Used when ``aux_operators`` is provided. - t_param (Parameter | None): Time parameter in case of a time-dependent Hamiltonian. This - free parameter must be within the ``hamiltonian``. - param_value_map (dict[Parameter, complex] | None): Maps free parameters in the problem to - values. Depending on the algorithm, it might refer to e.g. a Hamiltonian or an initial - state. - """ - - def __init__( - self, - hamiltonian: BaseOperator | PauliSumOp, - time: float, - initial_state: QuantumCircuit | Statevector | None = None, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - truncation_threshold: float = 1e-12, - t_param: Parameter | None = None, - param_value_map: Mapping[Parameter, complex] | None = None, - ): - """ - Args: - hamiltonian: The Hamiltonian under which to evolve the system. - time: Total time of evolution. - initial_state: The quantum state to be evolved for methods like Trotterization. - For variational time evolutions, where the evolution happens in an ansatz, - this argument is not required. - aux_operators: Optional list of auxiliary operators to be evaluated with the - evolved ``initial_state`` and their expectation values returned. - truncation_threshold: Defines a threshold under which values can be assumed to be 0. - Used when ``aux_operators`` is provided. - t_param: Time parameter in case of a time-dependent Hamiltonian. This - free parameter must be within the ``hamiltonian``. - param_value_map: Maps free parameters in the problem to values. Depending on the - algorithm, it might refer to e.g. a Hamiltonian or an initial state. - - Raises: - ValueError: If non-positive time of evolution is provided. - """ - - self.t_param = t_param - self.param_value_map = param_value_map - self.hamiltonian = hamiltonian - self.time = time - if isinstance(initial_state, Statevector): - circuit = QuantumCircuit(initial_state.num_qubits) - circuit.prepare_state(initial_state.data) - initial_state = circuit - self.initial_state: QuantumCircuit | None = initial_state - self.aux_operators = aux_operators - self.truncation_threshold = truncation_threshold - - @property - def time(self) -> float: - """Returns time.""" - return self._time - - @time.setter - def time(self, time: float) -> None: - """ - Sets time and validates it. - """ - self._time = time - - def validate_params(self) -> None: - """ - Checks if all parameters present in the Hamiltonian are also present in the dictionary - that maps them to values. - - Raises: - ValueError: If Hamiltonian parameters cannot be bound with data provided. - """ - if isinstance(self.hamiltonian, PauliSumOp) and isinstance( - self.hamiltonian.coeff, ParameterExpression - ): - raise ValueError("A global parametrized coefficient for PauliSumOp is not allowed.") diff --git a/qiskit/algorithms/time_evolvers/time_evolution_result.py b/qiskit/algorithms/time_evolvers/time_evolution_result.py deleted file mode 100644 index 8741367f681f..000000000000 --- a/qiskit/algorithms/time_evolvers/time_evolution_result.py +++ /dev/null @@ -1,60 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# 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. - -"""Class for holding time evolution result.""" -from __future__ import annotations -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.quantum_info import Statevector -from qiskit.algorithms.list_or_dict import ListOrDict -from ..algorithm_result import AlgorithmResult - - -class TimeEvolutionResult(AlgorithmResult): - """ - Class for holding time evolution result. - - Attributes: - evolved_state (QuantumCircuit|Statevector): An evolved quantum state. - aux_ops_evaluated (ListOrDict[tuple[complex, complex]] | None): Optional list of - observables for which expected values on an evolved state are calculated. These values - are in fact tuples formatted as (mean, standard deviation). - observables (ListOrDict[tuple[np.ndarray, np.ndarray]] | None): Optional list of - observables for which expected on an evolved state are calculated at each timestep. - These values are in fact lists of tuples formatted as (mean, standard deviation). - times (np.array | None): Optional list of times at which each observable has been evaluated. - """ - - def __init__( - self, - evolved_state: QuantumCircuit | Statevector, - aux_ops_evaluated: ListOrDict[tuple[complex, complex]] | None = None, - observables: ListOrDict[tuple[np.ndarray, np.ndarray]] | None = None, - times: np.ndarray | None = None, - ): - """ - Args: - evolved_state: An evolved quantum state. - aux_ops_evaluated: Optional list of observables for which expected values on an evolved - state are calculated. These values are in fact tuples formatted as (mean, standard - deviation). - observables: Optional list of observables for which expected values are calculated for - each timestep. These values are in fact tuples formatted as (mean array, standard - deviation array). - times: Optional list of times at which each observable has been evaluated. - """ - - self.evolved_state = evolved_state - self.aux_ops_evaluated = aux_ops_evaluated - self.observables = observables - self.times = times diff --git a/qiskit/algorithms/time_evolvers/trotterization/__init__.py b/qiskit/algorithms/time_evolvers/trotterization/__init__.py deleted file mode 100644 index c5e7e128728d..000000000000 --- a/qiskit/algorithms/time_evolvers/trotterization/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -"""This package contains Trotterization-based Quantum Real Time Evolution algorithm. -It is compliant with the new Quantum Time Evolution Framework and makes use of -:class:`qiskit.synthesis.evolution.ProductFormula` and -:class:`~qiskit.circuit.library.PauliEvolutionGate` implementations. - -Trotterization-based Quantum Real Time Evolution ------------------------------------------------- - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - TrotterQRTE -""" - -from qiskit.algorithms.time_evolvers.trotterization.trotter_qrte import TrotterQRTE - -__all__ = ["TrotterQRTE"] diff --git a/qiskit/algorithms/time_evolvers/trotterization/trotter_qrte.py b/qiskit/algorithms/time_evolvers/trotterization/trotter_qrte.py deleted file mode 100644 index cb43e297aed2..000000000000 --- a/qiskit/algorithms/time_evolvers/trotterization/trotter_qrte.py +++ /dev/null @@ -1,246 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# 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. - -"""An algorithm to implement a Trotterization real time-evolution.""" - -from __future__ import annotations - -from qiskit import QuantumCircuit -from qiskit.algorithms.time_evolvers.time_evolution_problem import TimeEvolutionProblem -from qiskit.algorithms.time_evolvers.time_evolution_result import TimeEvolutionResult -from qiskit.algorithms.time_evolvers.real_time_evolver import RealTimeEvolver -from qiskit.algorithms.observables_evaluator import estimate_observables -from qiskit.opflow import PauliSumOp -from qiskit.circuit.library import PauliEvolutionGate -from qiskit.circuit.parametertable import ParameterView -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info import Pauli, SparsePauliOp -from qiskit.synthesis import ProductFormula, LieTrotter - - -class TrotterQRTE(RealTimeEvolver): - """Quantum Real Time Evolution using Trotterization. - Type of Trotterization is defined by a ``ProductFormula`` provided. - - Examples: - - .. code-block:: python - - from qiskit.opflow import PauliSumOp - from qiskit.quantum_info import Pauli, SparsePauliOp - from qiskit import QuantumCircuit - from qiskit.algorithms import TimeEvolutionProblem - from qiskit.algorithms.time_evolvers import TrotterQRTE - from qiskit.primitives import Estimator - - operator = PauliSumOp(SparsePauliOp([Pauli("X"), Pauli("Z")])) - initial_state = QuantumCircuit(1) - time = 1 - evolution_problem = TimeEvolutionProblem(operator, time, initial_state) - # LieTrotter with 1 rep - estimator = Estimator() - trotter_qrte = TrotterQRTE(estimator=estimator) - evolved_state = trotter_qrte.evolve(evolution_problem).evolved_state - """ - - def __init__( - self, - product_formula: ProductFormula | None = None, - estimator: BaseEstimator | None = None, - num_timesteps: int = 1, - ) -> None: - """ - Args: - product_formula: A Lie-Trotter-Suzuki product formula. If ``None`` provided, the - Lie-Trotter first order product formula with a single repetition is used. ``reps`` - should be 1 to obtain a number of time-steps equal to ``num_timesteps`` and an - evaluation of :attr:`.TimeEvolutionProblem.aux_operators` at every time-step. If ``reps`` - is larger than 1, the true number of time-steps will be ``num_timesteps * reps``. - num_timesteps: The number of time-steps the full evolution time is devided into - (repetitions of ``product_formula``) - estimator: An estimator primitive used for calculating expectation values of - ``TimeEvolutionProblem.aux_operators``. - """ - - self.product_formula = product_formula - self.num_timesteps = num_timesteps - self.estimator = estimator - - @property - def product_formula(self) -> ProductFormula: - """Returns a product formula.""" - return self._product_formula - - @product_formula.setter - def product_formula(self, product_formula: ProductFormula | None): - """Sets a product formula. If ``None`` provided, sets the Lie-Trotter first order product - formula with a single repetition.""" - if product_formula is None: - product_formula = LieTrotter() - self._product_formula = product_formula - - @property - def estimator(self) -> BaseEstimator | None: - """ - Returns an estimator. - """ - return self._estimator - - @estimator.setter - def estimator(self, estimator: BaseEstimator) -> None: - """ - Sets an estimator. - """ - self._estimator = estimator - - @property - def num_timesteps(self) -> int: - """Returns the number of timesteps.""" - return self._num_timesteps - - @num_timesteps.setter - def num_timesteps(self, num_timesteps: int) -> None: - """ - Sets the number of time-steps. - - Raises: - ValueError: If num_timesteps is not positive. - """ - if num_timesteps <= 0: - raise ValueError( - f"Number of time steps must be positive integer, {num_timesteps} provided" - ) - self._num_timesteps = num_timesteps - - @classmethod - def supports_aux_operators(cls) -> bool: - """ - Whether computing the expectation value of auxiliary operators is supported. - - Returns: - ``True`` if ``aux_operators`` expectations in the ``TimeEvolutionProblem`` can be - evaluated, ``False`` otherwise. - """ - return True - - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - """ - Evolves a quantum state for a given time using the Trotterization method - based on a product formula provided. The result is provided in the form of a quantum - circuit. If auxiliary operators are included in the ``evolution_problem``, they are - evaluated on the ``init_state`` and on the evolved state at every step (``num_timesteps`` - times) using an estimator primitive provided. - - Args: - evolution_problem: Instance defining evolution problem. For the included Hamiltonian, - ``Pauli`` or ``PauliSumOp`` are supported by TrotterQRTE. - - Returns: - Evolution result that includes an evolved state as a quantum circuit and, optionally, - auxiliary operators evaluated for a resulting state on an estimator primitive. - - Raises: - ValueError: If ``t_param`` is not set to ``None`` in the ``TimeEvolutionProblem`` - (feature not currently supported). - ValueError: If ``aux_operators`` provided in the time evolution problem but no estimator - provided to the algorithm. - ValueError: If the ``initial_state`` is not provided in the ``TimeEvolutionProblem``. - ValueError: If an unsupported Hamiltonian type is provided. - """ - evolution_problem.validate_params() - - if evolution_problem.aux_operators is not None and self.estimator is None: - raise ValueError( - "The time evolution problem contained ``aux_operators`` but no estimator was " - "provided. The algorithm continues without calculating these quantities. " - ) - - # ensure the hamiltonian is a sparse pauli op - hamiltonian = evolution_problem.hamiltonian - if not isinstance(hamiltonian, (Pauli, PauliSumOp, SparsePauliOp)): - raise ValueError( - f"TrotterQRTE only accepts Pauli | PauliSumOp | SparsePauliOp, {type(hamiltonian)} " - "provided." - ) - if isinstance(hamiltonian, PauliSumOp): - hamiltonian = hamiltonian.primitive * hamiltonian.coeff - elif isinstance(hamiltonian, Pauli): - hamiltonian = SparsePauliOp(hamiltonian) - - t_param = evolution_problem.t_param - free_parameters = hamiltonian.parameters - if t_param is not None and free_parameters != ParameterView([t_param]): - raise ValueError( - f"Hamiltonian time parameters ({free_parameters}) do not match " - f"evolution_problem.t_param ({t_param})." - ) - - # make sure PauliEvolutionGate does not implement more than one Trotter step - dt = evolution_problem.time / self.num_timesteps - - if evolution_problem.initial_state is not None: - initial_state = evolution_problem.initial_state - else: - raise ValueError("``initial_state`` must be provided in the ``TimeEvolutionProblem``.") - - evolved_state = QuantumCircuit(initial_state.num_qubits) - evolved_state.append(initial_state, evolved_state.qubits) - - if evolution_problem.aux_operators is not None: - observables = [] - observables.append( - estimate_observables( - self.estimator, - evolved_state, - evolution_problem.aux_operators, - None, - evolution_problem.truncation_threshold, - ) - ) - else: - observables = None - - if t_param is None: - # the evolution gate - single_step_evolution_gate = PauliEvolutionGate( - hamiltonian, dt, synthesis=self.product_formula - ) - - for n in range(self.num_timesteps): - # if hamiltonian is time-dependent, bind new time-value at every step to construct - # evolution for next step - if t_param is not None: - time_value = (n + 1) * dt - bound_hamiltonian = hamiltonian.assign_parameters([time_value]) - single_step_evolution_gate = PauliEvolutionGate( - bound_hamiltonian, - dt, - synthesis=self.product_formula, - ) - evolved_state.append(single_step_evolution_gate, evolved_state.qubits) - - if evolution_problem.aux_operators is not None: - observables.append( - estimate_observables( - self.estimator, - evolved_state, - evolution_problem.aux_operators, - None, - evolution_problem.truncation_threshold, - ) - ) - - evaluated_aux_ops = None - if evolution_problem.aux_operators is not None: - evaluated_aux_ops = observables[-1] - - return TimeEvolutionResult(evolved_state, evaluated_aux_ops, observables) diff --git a/qiskit/algorithms/time_evolvers/variational/__init__.py b/qiskit/algorithms/time_evolvers/variational/__init__.py deleted file mode 100644 index ae22bca5adea..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/__init__.py +++ /dev/null @@ -1,117 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. -""" -Variational Quantum Time Evolutions (:mod:`qiskit.algorithms.time_evolvers.variational`) -======================================================================================== - -Algorithms for performing Variational Quantum Time Evolution of quantum states, -which can be tailored to near-term devices. -:class:`~qiskit.algorithms.time_evolvers.variational.VarQTE` base class exposes an interface, compliant -with the Quantum Time Evolution Framework in Qiskit Terra, that is implemented by -:class:`~qiskit.algorithms.VarQRTE` and :class:`~qiskit.algorithms.VarQITE` classes for real and -imaginary time evolution respectively. The variational approach is taken according to a variational -principle chosen by a user. - -Example: - - .. code-block:: python - - import numpy as np - - from qiskit.algorithms import TimeEvolutionProblem, VarQITE - from qiskit.algorithms.time_evolvers.variational import ImaginaryMcLachlanPrinciple - from qiskit.circuit.library import EfficientSU2 - from qiskit.quantum_info import SparsePauliOp - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - ansatz = EfficientSU2(observable.num_qubits, reps=1) - init_param_values = np.zeros(len(ansatz.parameters)) - for i in range(len(ansatz.parameters)): - init_param_values[i] = np.pi / 2 - var_principle = ImaginaryMcLachlanPrinciple() - time = 1 - evolution_problem = TimeEvolutionProblem(observable, time) - var_qite = VarQITE(ansatz, var_principle, init_param_values) - evolution_result = var_qite.evolve(evolution_problem) - -.. currentmodule:: qiskit.algorithms.time_evolvers.variational - -Variational Principles ----------------------- - -With variational principles we can project time evolution of a quantum state -onto the parameters of a model, in our case a variational quantum circuit. - -They can be divided into two categories: Variational Quantum _Real_ Time Evolution, which evolves -the variational ansatz under the standard Schroediger equation and -Variational Quantum _Imaginary_ Time Evolution, which evolves under the normalized -Wick-rotated Schroedinger equation. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - VariationalPrinciple - RealVariationalPrinciple - ImaginaryVariationalPrinciple - RealMcLachlanPrinciple - ImaginaryMcLachlanPrinciple - -ODE solvers ------------ -ODE solvers that implement the SciPy ODE Solver interface. The Forward Euler Solver is -a preferred choice in the presence of noise. One might also use solvers provided by SciPy directly, -e.g. RK45. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - ForwardEulerSolver - -""" -from .solvers.ode.forward_euler_solver import ForwardEulerSolver -from .var_qrte import VarQRTE -from .var_qite import VarQITE - -from .var_qte import VarQTE -from .var_qte_result import VarQTEResult -from .variational_principles import ( - VariationalPrinciple, - RealVariationalPrinciple, - ImaginaryVariationalPrinciple, - ImaginaryMcLachlanPrinciple, - RealMcLachlanPrinciple, -) - -__all__ = [ - "ForwardEulerSolver", - "VarQTE", - "VarQTEResult", - "VariationalPrinciple", - "RealVariationalPrinciple", - "ImaginaryVariationalPrinciple", - "RealMcLachlanPrinciple", - "ImaginaryMcLachlanPrinciple", - "VarQITE", - "VarQRTE", -] diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/__init__.py b/qiskit/algorithms/time_evolvers/variational/solvers/__init__.py deleted file mode 100644 index b4537b41e839..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -""" -Solvers (:mod:`qiskit.algorithms.time_evolvers.variational.solvers`) -==================================================================== - -This package contains the necessary classes to solve systems of equations arising in the -Variational Quantum Time Evolution. They include ordinary differential equations (ODE) which -describe ansatz parameter propagation and systems of linear equations. - - -Systems of Linear Equations Solver ----------------------------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - VarQTELinearSolver - - -ODE Solver ----------- -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - VarQTEOdeSolver -""" - -from qiskit.algorithms.time_evolvers.variational.solvers.ode.var_qte_ode_solver import ( - VarQTEOdeSolver, -) -from qiskit.algorithms.time_evolvers.variational.solvers.var_qte_linear_solver import ( - VarQTELinearSolver, -) - -__all__ = ["VarQTELinearSolver", "VarQTEOdeSolver"] diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/__init__.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/__init__.py deleted file mode 100644 index 06684cb2d012..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""ODE Solvers""" diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/abstract_ode_function.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/abstract_ode_function.py deleted file mode 100644 index b94ded552a81..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/abstract_ode_function.py +++ /dev/null @@ -1,51 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Abstract class for generating ODE functions.""" -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Mapping, Iterable - -from qiskit.circuit import Parameter - -from ..var_qte_linear_solver import VarQTELinearSolver - - -class AbstractOdeFunction(ABC): - """Abstract class for generating ODE functions.""" - - def __init__( - self, - varqte_linear_solver: VarQTELinearSolver, - param_dict: Mapping[Parameter, float], - t_param: Parameter | None = None, - ) -> None: - - self._varqte_linear_solver = varqte_linear_solver - self._param_dict = param_dict - self._t_param = t_param - - @abstractmethod - def var_qte_ode_function(self, time: float, parameter_values: Iterable) -> Iterable: - """ - Evaluates an ODE function for a given time and parameter values. It is used by an ODE - solver. - - Args: - time: Current time of evolution. - parameter_values: Current values of parameters. - - Returns: - ODE gradient arising from solving a system of linear equations. - """ - pass diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/forward_euler_solver.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/forward_euler_solver.py deleted file mode 100644 index d48ee5b6c4e1..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/forward_euler_solver.py +++ /dev/null @@ -1,72 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. -"""Forward Euler ODE solver.""" -from collections.abc import Callable, Sequence - -import numpy as np -from scipy.integrate import OdeSolver -from scipy.integrate._ivp.base import ConstantDenseOutput - - -class ForwardEulerSolver(OdeSolver): - """Forward Euler ODE solver.""" - - def __init__( - self, - function: Callable, - t0: float, - y0: Sequence, - t_bound: float, - vectorized: bool = False, - support_complex: bool = False, - num_t_steps: int = 15, - ): - """ - Forward Euler ODE solver that implements an interface from SciPy. - - Args: - function: Right-hand side of the system. The calling signature is ``fun(t, y)``. Here - ``t`` is a scalar, and there are two options for the ndarray ``y``: - It can either have shape (n,); then ``fun`` must return array_like with - shape (n,). Alternatively it can have shape (n, k); then ``fun`` - must return an array_like with shape (n, k), i.e., each column - corresponds to a single column in ``y``. The choice between the two - options is determined by `vectorized` argument (see below). The - vectorized implementation allows a faster approximation of the Jacobian - by finite differences (required for this solver). - t0: Initial time. - y0: Initial state. - t_bound: Boundary time - the integration won't continue beyond it. It also determines - the direction of the integration. - vectorized: Whether ``fun`` is implemented in a vectorized fashion. Default is False. - support_complex: Whether integration in a complex domain should be supported. - Generally determined by a derived solver class capabilities. Default is False. - num_t_steps: Number of time steps for the forward Euler method. - """ - self._y_old = None - self._step_length = (t_bound - t0) / num_t_steps - super().__init__(function, t0, y0, t_bound, vectorized, support_complex) - - def _step_impl(self): - """ - Takes an Euler step. - """ - try: - self._y_old = self.y - self.y = list(np.add(self.y, self._step_length * self.fun(self.t, self.y))) - self.t += self._step_length - return True, None - except Exception as ex: # pylint: disable=broad-except - return False, f"Unknown ODE solver error: {str(ex)}." - - def _dense_output_impl(self): - return ConstantDenseOutput(self.t_old, self.t, self._y_old) diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function.py deleted file mode 100644 index a7d8453c29b8..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function.py +++ /dev/null @@ -1,41 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Class for generating ODE functions based on ODE gradients.""" -from collections.abc import Iterable - -from .abstract_ode_function import AbstractOdeFunction - - -class OdeFunction(AbstractOdeFunction): - """Class for generating ODE functions based on ODE gradients.""" - - def var_qte_ode_function(self, time: float, parameter_values: Iterable) -> Iterable: - """ - Evaluates an ODE function for a given time and parameter values. It is used by an ODE - solver. - - Args: - time: Current time of evolution. - parameter_values: Current values of parameters. - - Returns: - ODE gradient arising from solving a system of linear equations. - """ - current_param_dict = dict(zip(self._param_dict.keys(), parameter_values)) - - ode_grad_res, _, _ = self._varqte_linear_solver.solve_lse( - current_param_dict, - time, - ) - - return ode_grad_res diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function_factory.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function_factory.py deleted file mode 100644 index 0d094c4b7950..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function_factory.py +++ /dev/null @@ -1,72 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Abstract class for generating ODE functions.""" -from __future__ import annotations - -from abc import ABC -from collections.abc import Mapping -from enum import Enum - -from qiskit.circuit import Parameter - -from .abstract_ode_function import AbstractOdeFunction -from .ode_function import OdeFunction - -from ..var_qte_linear_solver import VarQTELinearSolver - - -class OdeFunctionType(Enum): - """Types of ODE functions for VatQTE algorithms.""" - - # Other types may be supported in the future - STANDARD_ODE = "STANDARD_ODE" - - -class OdeFunctionFactory(ABC): - """Factory for building ODE functions.""" - - def __init__(self, ode_function_type: OdeFunctionType = OdeFunctionType.STANDARD_ODE) -> None: - """ - Args: - ode_function_type: An Enum that defines a type of an ODE function to be built. If - not provided, a default ``STANDARD_ODE`` is used. - """ - self._ode_function_type = ode_function_type - - def _build( - self, - varqte_linear_solver: VarQTELinearSolver, - param_dict: Mapping[Parameter, float], - t_param: Parameter | None = None, - ) -> AbstractOdeFunction: - """ - Initializes an ODE function specified in the class. - - Args: - varqte_linear_solver: Solver of LSE for the VarQTE algorithm. - param_dict: Dictionary which relates parameter values to the parameters in the ansatz. - t_param: Time parameter in case of a time-dependent Hamiltonian. - - Returns: - An ODE function. - - Raises: - ValueError: If unsupported ODE function provided. - - """ - if self._ode_function_type == OdeFunctionType.STANDARD_ODE: - return OdeFunction(varqte_linear_solver, param_dict, t_param) - raise ValueError( - f"Unsupported ODE function provided: {self._ode_function_type}." - f" Only {[tp.value for tp in OdeFunctionType]} are supported." - ) diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/var_qte_ode_solver.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/var_qte_ode_solver.py deleted file mode 100644 index aad1d96155ce..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/var_qte_ode_solver.py +++ /dev/null @@ -1,89 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Class for solving ODEs for Quantum Time Evolution.""" -from __future__ import annotations - -from collections.abc import Sequence -from functools import partial -from typing import Type - -import numpy as np -from scipy.integrate import OdeSolver, solve_ivp - -from .abstract_ode_function import AbstractOdeFunction -from .forward_euler_solver import ForwardEulerSolver - - -class VarQTEOdeSolver: - """Class for solving ODEs for Quantum Time Evolution.""" - - def __init__( - self, - init_params: Sequence[float], - ode_function: AbstractOdeFunction, - ode_solver: Type[OdeSolver] | str = ForwardEulerSolver, - num_timesteps: int | None = None, - ) -> None: - """ - Initialize ODE Solver. - - Args: - init_params: Set of initial parameters for time 0. - ode_function: Generates the ODE function. - ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a - string indicating a valid method offered by SciPy. - num_timesteps: The number of timesteps to take. If None, it is - automatically selected to achieve a timestep of approximately 0.01. Only - relevant in case of the ``ForwardEulerSolver``. - """ - self._init_params = init_params - self._ode_function = ode_function.var_qte_ode_function - self._ode_solver = ode_solver - self._num_timesteps = num_timesteps - - def run( - self, evolution_time: float - ) -> tuple[Sequence[float], Sequence[Sequence[float]], Sequence[float]]: - """ - Finds numerical solution with ODE Solver. - - Args: - evolution_time: Evolution time. - - Returns: - List of parameters found by an ODE solver for a given ODE function callable. - """ - # determine the number of timesteps and set the timestep - num_timesteps = ( - int(np.ceil(evolution_time / 0.01)) - if self._num_timesteps is None - else self._num_timesteps - ) - - if self._ode_solver == ForwardEulerSolver: - solve = partial(solve_ivp, num_t_steps=num_timesteps) - else: - solve = solve_ivp - - sol = solve( - self._ode_function, - (0, evolution_time), - self._init_params, - method=self._ode_solver, - ) - - param_vals = sol.y.T - time_points = sol.t - final_param_vals = param_vals[-1] - - return final_param_vals, param_vals, time_points diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/var_qte_linear_solver.py b/qiskit/algorithms/time_evolvers/variational/solvers/var_qte_linear_solver.py deleted file mode 100644 index ec06fba0a685..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/var_qte_linear_solver.py +++ /dev/null @@ -1,129 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Class for solving linear equations for Quantum Time Evolution.""" -from __future__ import annotations - -from collections.abc import Mapping, Sequence, Callable - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.quantum_info import SparsePauliOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..variational_principles import VariationalPrinciple - - -class VarQTELinearSolver: - """Class for solving linear equations for Quantum Time Evolution.""" - - def __init__( - self, - var_principle: VariationalPrinciple, - hamiltonian: BaseOperator, - ansatz: QuantumCircuit, - gradient_params: Sequence[Parameter] | None = None, - t_param: Parameter | None = None, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - imag_part_tol: float = 1e-7, - ) -> None: - """ - Args: - var_principle: Variational Principle to be used. - hamiltonian: Operator used for Variational Quantum Time Evolution. - ansatz: Quantum state in the form of a parametrized quantum circuit. - gradient_params: List of parameters with respect to which gradients should be computed. - If ``None`` given, gradients w.r.t. all parameters will be computed. - t_param: Time parameter in case of a time-dependent Hamiltonian. - lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to - solve ``Ax=b`` and returns ``x``. If ``None``, the default ``np.linalg.lstsq`` - solver is used. - imag_part_tol: Allowed value of an imaginary part that can be neglected if no - imaginary part is expected. - - Raises: - TypeError: If t_param is provided and Hamiltonian is not of type SparsePauliOp. - """ - self._var_principle = var_principle - self._hamiltonian = hamiltonian - self._ansatz = ansatz - self._gradient_params = gradient_params - self._bind_params = gradient_params - self._time_param = t_param - self.lse_solver = lse_solver - self._imag_part_tol = imag_part_tol - - if self._time_param is not None and not isinstance(self._hamiltonian, SparsePauliOp): - raise TypeError( - f"A time parameter {t_param} has been specified, so a time-dependent " - f"hamiltonian is expected. The operator provided is of type {type(self._hamiltonian)}, " - f"which might not support parametrization. " - f"Please provide the parametrized hamiltonian as a SparsePauliOp." - ) - - @property - def lse_solver(self) -> Callable[[np.ndarray, np.ndarray], np.ndarray]: - """Returns an LSE solver callable.""" - return self._lse_solver - - @lse_solver.setter - def lse_solver(self, lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None) -> None: - """Sets an LSE solver. Uses a ``np.linalg.lstsq`` callable if ``None`` provided.""" - if lse_solver is None: - lse_solver = lambda a, b: np.linalg.lstsq(a, b, rcond=1e-2)[0] - - self._lse_solver = lse_solver - - def solve_lse( - self, - param_dict: Mapping[Parameter, float], - time_value: float | None = None, - ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: - """ - Solve the system of linear equations underlying McLachlan's variational principle for the - calculation without error bounds. - - Args: - param_dict: Dictionary which relates parameter values to the parameters in the ansatz. - time_value: Time value that will be bound to ``t_param``. It is required if ``t_param`` - is not ``None``. - - Returns: - Solution to the LSE, A from Ax=b, b from Ax=b. - - Raises: - ValueError: If no time value is provided for time dependent hamiltonians. - - """ - param_values = list(param_dict.values()) - metric_tensor_lse_lhs = self._var_principle.metric_tensor(self._ansatz, param_values) - hamiltonian = self._hamiltonian - - if self._time_param is not None: - if time_value is not None: - hamiltonian = hamiltonian.assign_parameters([time_value]) - else: - raise ValueError( - "Providing a time_value is required for time-dependent hamiltonians, " - f"but got time_value = {time_value}. " - "Please provide a time_value to the solve_lse method." - ) - - evolution_grad_lse_rhs = self._var_principle.evolution_gradient( - hamiltonian, self._ansatz, param_values, self._gradient_params - ) - - x = self._lse_solver(metric_tensor_lse_lhs, evolution_grad_lse_rhs) - - return np.real(x), metric_tensor_lse_lhs, evolution_grad_lse_rhs diff --git a/qiskit/algorithms/time_evolvers/variational/var_qite.py b/qiskit/algorithms/time_evolvers/variational/var_qite.py deleted file mode 100644 index 4200389c83cf..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/var_qite.py +++ /dev/null @@ -1,120 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Variational Quantum Imaginary Time Evolution algorithm.""" -from __future__ import annotations - -from collections.abc import Mapping, Sequence -from typing import Type, Callable - -import numpy as np -from scipy.integrate import OdeSolver - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.primitives import BaseEstimator - -from .solvers.ode.forward_euler_solver import ForwardEulerSolver - -from .variational_principles import ImaginaryVariationalPrinciple, ImaginaryMcLachlanPrinciple -from .var_qte import VarQTE - -from ..imaginary_time_evolver import ImaginaryTimeEvolver - - -class VarQITE(VarQTE, ImaginaryTimeEvolver): - """Variational Quantum Imaginary Time Evolution algorithm. - - .. code-block::python - - import numpy as np - - from qiskit.algorithms import TimeEvolutionProblem, VarQITE - from qiskit.algorithms.time_evolvers.variational import ImaginaryMcLachlanPrinciple - from qiskit.circuit.library import EfficientSU2 - from qiskit.quantum_info import SparsePauliOp, Pauli - from qiskit.primitives import Estimator - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - ansatz = EfficientSU2(observable.num_qubits, reps=1) - init_param_values = np.ones(len(ansatz.parameters)) * np.pi/2 - var_principle = ImaginaryMcLachlanPrinciple() - time = 1 - - # without evaluating auxiliary operators - evolution_problem = TimeEvolutionProblem(observable, time) - var_qite = VarQITE(ansatz, init_param_values, var_principle) - evolution_result = var_qite.evolve(evolution_problem) - - # evaluating auxiliary operators - aux_ops = [Pauli("XX"), Pauli("YZ")] - evolution_problem = TimeEvolutionProblem(observable, time, aux_operators=aux_ops) - var_qite = VarQITE(ansatz, init_param_values, var_principle, Estimator()) - evolution_result = var_qite.evolve(evolution_problem) - """ - - def __init__( - self, - ansatz: QuantumCircuit, - initial_parameters: Mapping[Parameter, float] | Sequence[float], - variational_principle: ImaginaryVariationalPrinciple | None = None, - estimator: BaseEstimator | None = None, - ode_solver: Type[OdeSolver] | str = ForwardEulerSolver, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - num_timesteps: int | None = None, - imag_part_tol: float = 1e-7, - num_instability_tol: float = 1e-7, - ) -> None: - r""" - Args: - ansatz: Ansatz to be used for variational time evolution. - initial_parameters: Initial parameter values for the ansatz. - variational_principle: Variational Principle to be used. Defaults to - ``ImaginaryMcLachlanPrinciple``. - estimator: An estimator primitive used for calculating expectation values of - TimeEvolutionProblem.aux_operators. - ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a - string indicating a valid method offered by SciPy. - lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to - solve ``Ax=b`` and returns ``x``. If ``None``, the default ``np.linalg.lstsq`` - solver is used. - num_timesteps: The number of timesteps to take. If ``None``, it is - automatically selected to achieve a timestep of approximately 0.01. Only - relevant in case of the ``ForwardEulerSolver``. - imag_part_tol: Allowed value of an imaginary part that can be neglected if no - imaginary part is expected. - num_instability_tol: The amount of negative value that is allowed to be - rounded up to 0 for quantities that are expected to be non-negative. - """ - if variational_principle is None: - variational_principle = ImaginaryMcLachlanPrinciple() - super().__init__( - ansatz, - initial_parameters, - variational_principle, - estimator, - ode_solver, - lse_solver=lse_solver, - num_timesteps=num_timesteps, - imag_part_tol=imag_part_tol, - num_instability_tol=num_instability_tol, - ) diff --git a/qiskit/algorithms/time_evolvers/variational/var_qrte.py b/qiskit/algorithms/time_evolvers/variational/var_qrte.py deleted file mode 100644 index f8305f643cb5..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/var_qrte.py +++ /dev/null @@ -1,122 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Variational Quantum Real Time Evolution algorithm.""" -from __future__ import annotations - -from collections.abc import Mapping, Sequence -from typing import Type, Callable - -import numpy as np -from scipy.integrate import OdeSolver - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.primitives import BaseEstimator - -from .solvers.ode.forward_euler_solver import ForwardEulerSolver - -from .variational_principles import RealVariationalPrinciple, RealMcLachlanPrinciple -from .var_qte import VarQTE - -from ..real_time_evolver import RealTimeEvolver - - -class VarQRTE(VarQTE, RealTimeEvolver): - """Variational Quantum Real Time Evolution algorithm. - - .. code-block::python - - import numpy as np - - from qiskit.algorithms import TimeEvolutionProblem, VarQRTE - from qiskit.circuit.library import EfficientSU2 - from qiskit.algorithms.time_evolvers.variational import RealMcLachlanPrinciple - from qiskit.quantum_info import SparsePauliOp - from qiskit.quantum_info import SparsePauliOp, Pauli - from qiskit.primitives import Estimator - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - ansatz = EfficientSU2(observable.num_qubits, reps=1) - init_param_values = np.ones(len(ansatz.parameters)) * np.pi/2 - var_principle = RealMcLachlanPrinciple() - time = 1 - - # without evaluating auxiliary operators - evolution_problem = TimeEvolutionProblem(observable, time) - var_qrte = VarQRTE(ansatz, init_param_values, var_principle) - evolution_result = var_qrte.evolve(evolution_problem) - - # evaluating auxiliary operators - aux_ops = [Pauli("XX"), Pauli("YZ")] - evolution_problem = TimeEvolutionProblem(observable, time, aux_operators=aux_ops) - var_qrte = VarQRTE(ansatz, init_param_values, var_principle, Estimator()) - evolution_result = var_qrte.evolve(evolution_problem) - """ - - def __init__( - self, - ansatz: QuantumCircuit, - initial_parameters: Mapping[Parameter, float] | Sequence[float], - variational_principle: RealVariationalPrinciple | None = None, - estimator: BaseEstimator | None = None, - ode_solver: Type[OdeSolver] | str = ForwardEulerSolver, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - num_timesteps: int | None = None, - imag_part_tol: float = 1e-7, - num_instability_tol: float = 1e-7, - ) -> None: - r""" - Args: - ansatz: Ansatz to be used for variational time evolution. - initial_parameters: Initial parameter values for an ansatz. - variational_principle: Variational Principle to be used. Defaults to - ``RealMcLachlanPrinciple``. - estimator: An estimator primitive used for calculating expectation values of - TimeEvolutionProblem.aux_operators. - ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a - string indicating a valid method offered by SciPy. - lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to - solve ``Ax=b`` and returns ``x``. If ``None``, the default ``np.linalg.lstsq`` - solver is used. - num_timesteps: The number of timesteps to take. If ``None``, it is - automatically selected to achieve a timestep of approximately 0.01. Only - relevant in case of the ``ForwardEulerSolver``. - imag_part_tol: Allowed value of an imaginary part that can be neglected if no - imaginary part is expected. - num_instability_tol: The amount of negative value that is allowed to be - rounded up to 0 for quantities that are expected to be - non-negative. - """ - if variational_principle is None: - variational_principle = RealMcLachlanPrinciple() - super().__init__( - ansatz, - initial_parameters, - variational_principle, - estimator, - ode_solver, - lse_solver=lse_solver, - num_timesteps=num_timesteps, - imag_part_tol=imag_part_tol, - num_instability_tol=num_instability_tol, - ) diff --git a/qiskit/algorithms/time_evolvers/variational/var_qte.py b/qiskit/algorithms/time_evolvers/variational/var_qte.py deleted file mode 100644 index f0d33a6b8ae5..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/var_qte.py +++ /dev/null @@ -1,290 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""The Variational Quantum Time Evolution Interface""" -from __future__ import annotations - -from abc import ABC -from collections.abc import Mapping, Callable, Sequence -from typing import Type - -import numpy as np -from scipy.integrate import OdeSolver - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from .solvers.ode.forward_euler_solver import ForwardEulerSolver -from .solvers.ode.ode_function_factory import OdeFunctionFactory -from .solvers.ode.var_qte_ode_solver import VarQTEOdeSolver -from .solvers.var_qte_linear_solver import VarQTELinearSolver - -from .variational_principles.variational_principle import VariationalPrinciple -from .var_qte_result import VarQTEResult - -from ..time_evolution_problem import TimeEvolutionProblem - -from ...observables_evaluator import estimate_observables - - -class VarQTE(ABC): - """Variational Quantum Time Evolution. - - Algorithms that use variational principles to compute a time evolution for a given - Hermitian operator (Hamiltonian) and a quantum state prepared by a parameterized quantum - circuit. - - Attributes: - ansatz (QuantumCircuit): Ansatz to be used for variational time evolution. - initial_parameters (Mapping[Parameter, float] | Sequence[float]): Initial - parameter values for an ansatz. - variational_principle (VariationalPrinciple): Variational Principle to be used. - estimator (BaseEstimator): An estimator primitive used for calculating expectation - values of ``TimeEvolutionProblem.aux_operators``. - ode_solver(Type[OdeSolver] | str): ODE solver callable that implements a SciPy - ``OdeSolver`` interface or a string indicating a valid method offered by SciPy. - lse_solver (Callable[[np.ndarray, np.ndarray], np.ndarray] | None): Linear system - of equations solver callable. It accepts ``A`` and ``b`` to solve ``Ax=b`` - and returns ``x``. - num_timesteps (int | None): The number of timesteps to take. If None, it is - automatically selected to achieve a timestep of approximately 0.01. Only - relevant in case of the ``ForwardEulerSolver``. - imag_part_tol (float): Allowed value of an imaginary part that can be neglected if no - imaginary part is expected. - num_instability_tol (float): The amount of negative value that is allowed to be - rounded up to 0 for quantities that are expected to be - non-negative. - References: - - [1] Benjamin, Simon C. et al. (2019). - Theory of variational quantum simulation. ``_ - """ - - def __init__( - self, - ansatz: QuantumCircuit, - initial_parameters: Mapping[Parameter, float] | Sequence[float], - variational_principle: VariationalPrinciple, - estimator: BaseEstimator, - ode_solver: Type[OdeSolver] | str = ForwardEulerSolver, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - num_timesteps: int | None = None, - imag_part_tol: float = 1e-7, - num_instability_tol: float = 1e-7, - ) -> None: - r""" - Args: - ansatz: Ansatz to be used for variational time evolution. - initial_parameters: Initial parameter values for an ansatz. - variational_principle: Variational Principle to be used. - estimator: An estimator primitive used for calculating expectation values of - TimeEvolutionProblem.aux_operators. - ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a - string indicating a valid method offered by SciPy. - lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to - solve ``Ax=b`` and returns ``x``. - num_timesteps: The number of timesteps to take. If None, it is - automatically selected to achieve a timestep of approximately 0.01. Only - relevant in case of the ``ForwardEulerSolver``. - imag_part_tol: Allowed value of an imaginary part that can be neglected if no - imaginary part is expected. - num_instability_tol: The amount of negative value that is allowed to be - rounded up to 0 for quantities that are expected to be - non-negative. - """ - super().__init__() - self.ansatz = ansatz - self.initial_parameters = initial_parameters - self.variational_principle = variational_principle - self.estimator = estimator - self.num_timesteps = num_timesteps - self.lse_solver = lse_solver - self.ode_solver = ode_solver - self.imag_part_tol = imag_part_tol - self.num_instability_tol = num_instability_tol - # OdeFunction abstraction kept for potential extensions - unclear at the moment; - # currently hidden from the user - self._ode_function_factory = OdeFunctionFactory() - - def evolve(self, evolution_problem: TimeEvolutionProblem) -> VarQTEResult: - """Apply Variational Quantum Time Evolution to the given operator. - - Args: - evolution_problem: Instance defining an evolution problem. - Returns: - Result of the evolution which includes a quantum circuit with bound parameters as an - evolved state and, if provided, observables evaluated on the evolved state. - - Raises: - ValueError: If ``initial_state`` is included in the ``evolution_problem``. - """ - self._validate_aux_ops(evolution_problem) - - if evolution_problem.initial_state is not None: - raise ValueError( - "An initial_state was provided to the TimeEvolutionProblem but this is not " - "supported by VarQTE. Please remove this state from the problem definition " - "and set VarQTE.initial_parameters with the corresponding initial parameter " - "values instead." - ) - - init_state_param_dict = self._create_init_state_param_dict( - self.initial_parameters, self.ansatz.parameters - ) - - # unwrap PauliSumOp (in the future this will be deprecated) - if isinstance(evolution_problem.hamiltonian, PauliSumOp): - hamiltonian = ( - evolution_problem.hamiltonian.primitive * evolution_problem.hamiltonian.coeff - ) - else: - hamiltonian = evolution_problem.hamiltonian - - evolved_state, param_values, time_points = self._evolve( - init_state_param_dict, - hamiltonian, - evolution_problem.time, - evolution_problem.t_param, - ) - - observables = [] - if evolution_problem.aux_operators is not None: - for values in param_values: - # cannot batch evaluation because estimate_observables - # only accepts single circuits - evol_state = self.ansatz.assign_parameters( - dict(zip(init_state_param_dict.keys(), values)) - ) - observable = estimate_observables( - self.estimator, - evol_state, - evolution_problem.aux_operators, - ) - observables.append(observable) - - # TODO: deprecate returning evaluated_aux_ops. - # As these are the observables for the last time step. - evaluated_aux_ops = observables[-1] if len(observables) > 0 else None - - return VarQTEResult( - evolved_state, evaluated_aux_ops, observables, time_points, param_values - ) - - def _evolve( - self, - init_state_param_dict: Mapping[Parameter, float], - hamiltonian: BaseOperator, - time: float, - t_param: Parameter | None = None, - ) -> tuple[QuantumCircuit | None, Sequence[Sequence[float]], Sequence[float]]: - r""" - Helper method for performing time evolution. Works both for imaginary and real case. - - Args: - init_state_param_dict: Parameter dictionary with initial values for a given - parametrized state/ansatz. - hamiltonian: Operator used for Variational Quantum Time Evolution (VarQTE). - time: Total time of evolution. - t_param: Time parameter in case of a time-dependent Hamiltonian. - - Returns: - Result of the evolution which is a quantum circuit with bound parameters as an - evolved state. - """ - - init_state_parameters = list(init_state_param_dict.keys()) - init_state_parameter_values = list(init_state_param_dict.values()) - - linear_solver = VarQTELinearSolver( - self.variational_principle, - hamiltonian, - self.ansatz, - init_state_parameters, - t_param, - self.lse_solver, - self.imag_part_tol, - ) - - # Convert the operator that holds the Hamiltonian and ansatz into a NaturalGradient operator - ode_function = self._ode_function_factory._build( - linear_solver, init_state_param_dict, t_param - ) - - ode_solver = VarQTEOdeSolver( - init_state_parameter_values, ode_function, self.ode_solver, self.num_timesteps - ) - final_param_values, param_values, time_points = ode_solver.run(time) - param_dict_from_ode = dict(zip(init_state_parameters, final_param_values)) - - return self.ansatz.assign_parameters(param_dict_from_ode), param_values, time_points - - @staticmethod - def _create_init_state_param_dict( - param_values: Mapping[Parameter, float] | Sequence[float], - init_state_parameters: Sequence[Parameter], - ) -> Mapping[Parameter, float]: - r""" - If ``param_values`` is a dictionary, it looks for parameters present in an initial state - (an ansatz) in a ``param_values``. Based on that, it creates a new dictionary containing - only parameters present in an initial state and their respective values. - If ``param_values`` is a list of values, it creates a new dictionary containing - parameters present in an initial state and their respective values. - - Args: - param_values: Dictionary which relates parameter values to the parameters or a list of - values. - init_state_parameters: Parameters present in a quantum state. - - Returns: - Dictionary that maps parameters of an initial state to some values. - - Raises: - ValueError: If the dictionary with parameter values provided does not include all - parameters present in the initial state or if the list of values provided is not the - same length as the list of parameters. - TypeError: If an unsupported type of ``param_values`` provided. - """ - if isinstance(param_values, Mapping): - init_state_parameter_values: Sequence[float] = [] - for param in init_state_parameters: - if param in param_values.keys(): - init_state_parameter_values.append(param_values[param]) - else: - raise ValueError( - f"The dictionary with parameter values provided does not " - f"include all parameters present in the initial state." - f"Parameters present in the state: {init_state_parameters}, " - f"parameters in the dictionary: " - f"{list(param_values.keys())}." - ) - elif isinstance(param_values, (Sequence, np.ndarray)): - if len(init_state_parameters) != len(param_values): - raise ValueError( - f"Initial state has {len(init_state_parameters)} parameters and the" - f" list of values has {len(param_values)} elements. They should be" - f" equal in length." - ) - init_state_parameter_values = param_values - else: - raise TypeError(f"Unsupported type of param_values provided: {type(param_values)}.") - - init_state_param_dict = dict(zip(init_state_parameters, init_state_parameter_values)) - return init_state_param_dict - - def _validate_aux_ops(self, evolution_problem: TimeEvolutionProblem) -> None: - if evolution_problem.aux_operators is not None and self.estimator is None: - raise ValueError( - "aux_operators were provided for evaluations but no ``estimator`` was provided." - ) diff --git a/qiskit/algorithms/time_evolvers/variational/var_qte_result.py b/qiskit/algorithms/time_evolvers/variational/var_qte_result.py deleted file mode 100644 index 3efb5e7c1789..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/var_qte_result.py +++ /dev/null @@ -1,56 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Result object for varQTE.""" -from __future__ import annotations - -import numpy as np - -from qiskit.circuit import QuantumCircuit - -from ..time_evolution_result import TimeEvolutionResult - -from ...list_or_dict import ListOrDict - - -class VarQTEResult(TimeEvolutionResult): - """The result object for the variational quantum time evolution algorithms. - - Attributes: - parameter_values (np.array | None): Optional list of parameter values obtained after - each evolution step. - """ - - def __init__( - self, - evolved_state: QuantumCircuit, - aux_ops_evaluated: ListOrDict[tuple[complex, complex]] | None = None, - observables: ListOrDict[tuple[np.ndarray, np.ndarray]] | None = None, - times: np.ndarray | None = None, - parameter_values: np.ndarray | None = None, - ): - """ - Args: - evolved_state: An evolved quantum state. - aux_ops_evaluated: Optional list of observables for which expected values on an evolved - state are calculated. These values are in fact tuples formatted as (mean, standard - deviation). - observables: Optional list of observables for which expected on an evolved state are - calculated at each timestep. - These values are in fact lists of tuples formatted as (mean, standard deviation). - times: Optional list of times at which each observable has been evaluated. - parameter_values: Optional list of parameter values obtained after each evolution step. - - """ - - super().__init__(evolved_state, aux_ops_evaluated, observables, times) - self.parameter_values = parameter_values diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/__init__.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/__init__.py deleted file mode 100644 index be04c03d7bcf..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Variational Principles""" - -from .variational_principle import VariationalPrinciple -from .imaginary_mc_lachlan_principle import ImaginaryMcLachlanPrinciple -from .imaginary_variational_principle import ImaginaryVariationalPrinciple -from .real_mc_lachlan_principle import RealMcLachlanPrinciple -from .real_variational_principle import RealVariationalPrinciple - -__all__ = [ - "VariationalPrinciple", - "ImaginaryMcLachlanPrinciple", - "ImaginaryVariationalPrinciple", - "RealMcLachlanPrinciple", - "RealVariationalPrinciple", -] diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py deleted file mode 100644 index 09e1e03d3473..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py +++ /dev/null @@ -1,128 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Class for an Imaginary McLachlan's Variational Principle.""" -from __future__ import annotations - -import warnings - -from collections.abc import Sequence - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.primitives import Estimator -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from .imaginary_variational_principle import ImaginaryVariationalPrinciple - -from ....exceptions import AlgorithmError -from ....gradients import ( - BaseEstimatorGradient, - BaseQGT, - DerivativeType, - LinCombQGT, - LinCombEstimatorGradient, -) - - -class ImaginaryMcLachlanPrinciple(ImaginaryVariationalPrinciple): - """Class for an Imaginary McLachlan's Variational Principle. It aims to minimize the distance - between both sides of the Wick-rotated Schrödinger equation with a quantum state given as a - parametrized trial state. The principle leads to a system of linear equations handled by a - linear solver. The imaginary variant means that we consider imaginary time dynamics. - """ - - def __init__( - self, - qgt: BaseQGT | None = None, - gradient: BaseEstimatorGradient | None = None, - ) -> None: - """ - Args: - qgt: Instance of a the GQT class used to compute the QFI. - If ``None`` provided, ``LinCombQGT`` is used. - gradient: Instance of a class used to compute the state gradient. - If ``None`` provided, ``LinCombEstimatorGradient`` is used. - - Raises: - AlgorithmError: If the gradient instance does not contain an estimator. - """ - - self._validate_grad_settings(gradient) - - if gradient is not None: - try: - estimator = gradient._estimator - except Exception as exc: - raise AlgorithmError( - "The provided gradient instance does not contain an estimator primitive." - ) from exc - else: - estimator = Estimator() - gradient = LinCombEstimatorGradient(estimator) - - if qgt is None: - qgt = LinCombQGT(estimator) - - super().__init__(qgt, gradient) - - def evolution_gradient( - self, - hamiltonian: BaseOperator, - ansatz: QuantumCircuit, - param_values: Sequence[float], - gradient_params: Sequence[Parameter] | None = None, - ) -> np.ndarray: - """ - Calculates an evolution gradient according to the rules of this variational principle. - - Args: - hamiltonian: Operator used for Variational Quantum Time Evolution. - ansatz: Quantum state in the form of a parametrized quantum circuit. - param_values: Values of parameters to be bound. - gradient_params: List of parameters with respect to which gradients should be computed. - If ``None`` given, gradients w.r.t. all parameters will be computed. - - Returns: - An evolution gradient. - - Raises: - AlgorithmError: If a gradient job fails. - """ - - try: - evolution_grad_lse_rhs = ( - self.gradient.run([ansatz], [hamiltonian], [param_values], [gradient_params]) - .result() - .gradients[0] - ) - - except Exception as exc: - raise AlgorithmError("The gradient primitive job failed!") from exc - - return -0.5 * evolution_grad_lse_rhs - - @staticmethod - def _validate_grad_settings(gradient): - if ( - gradient is not None - and hasattr(gradient, "_derivative_type") - and gradient._derivative_type != DerivativeType.REAL - ): - warnings.warn( - "A gradient instance with a setting for calculating imaginary part of " - "the gradient was provided. This variational principle requires the" - "real part. The setting to real was changed automatically." - ) - gradient._derivative_type = DerivativeType.REAL diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_variational_principle.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_variational_principle.py deleted file mode 100644 index 1255e52b7c65..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_variational_principle.py +++ /dev/null @@ -1,22 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Abstract class for an Imaginary Variational Principle.""" - -from abc import ABC - -from .variational_principle import VariationalPrinciple - - -class ImaginaryVariationalPrinciple(VariationalPrinciple, ABC): - """Abstract class for an Imaginary Variational Principle. The imaginary variant - means that we consider imaginary time dynamics.""" diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py deleted file mode 100644 index d7a946b8ab70..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py +++ /dev/null @@ -1,166 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Class for a Real McLachlan's Variational Principle.""" -from __future__ import annotations - -import warnings - -from collections.abc import Sequence - -import numpy as np -from numpy import real - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.primitives import Estimator -from qiskit.quantum_info import SparsePauliOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from .real_variational_principle import RealVariationalPrinciple - -from ....exceptions import AlgorithmError -from ....gradients import ( - BaseEstimatorGradient, - BaseQGT, - DerivativeType, - LinCombQGT, - LinCombEstimatorGradient, -) - - -class RealMcLachlanPrinciple(RealVariationalPrinciple): - """Class for a Real McLachlan's Variational Principle. It aims to minimize the distance - between both sides of the Schrödinger equation with a quantum state given as a parametrized - trial state. The principle leads to a system of linear equations handled by a linear solver. - The real variant means that we consider real time dynamics. - """ - - def __init__( - self, - qgt: BaseQGT | None = None, - gradient: BaseEstimatorGradient | None = None, - ) -> None: - """ - Args: - qgt: Instance of a the GQT class used to compute the QFI. - If ``None`` provided, ``LinCombQGT`` is used. - gradient: Instance of a class used to compute the state gradient. - If ``None`` provided, ``LinCombEstimatorGradient`` is used. - - Raises: - AlgorithmError: If the gradient instance does not contain an estimator. - """ - self._validate_grad_settings(gradient) - - if gradient is not None: - try: - estimator = gradient._estimator - except Exception as exc: - raise AlgorithmError( - "The provided gradient instance does not contain an estimator primitive." - ) from exc - else: - estimator = Estimator() - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - - if qgt is None: - qgt = LinCombQGT(estimator) - - super().__init__(qgt, gradient) - - def evolution_gradient( - self, - hamiltonian: BaseOperator, - ansatz: QuantumCircuit, - param_values: Sequence[float], - gradient_params: Sequence[Parameter] | None = None, - ) -> np.ndarray: - """ - Calculates an evolution gradient according to the rules of this variational principle. - - Args: - hamiltonian: Operator used for Variational Quantum Time Evolution. - ansatz: Quantum state in the form of a parametrized quantum circuit. - param_values: Values of parameters to be bound. - gradient_params: List of parameters with respect to which gradients should be computed. - If ``None`` given, gradients w.r.t. all parameters will be computed. - - Returns: - An evolution gradient. - - Raises: - AlgorithmError: If a gradient job fails. - """ - - try: - estimator_job = self.gradient._estimator.run([ansatz], [hamiltonian], [param_values]) - energy = estimator_job.result().values[0] - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - - modified_hamiltonian = self._construct_modified_hamiltonian(hamiltonian, real(energy)) - - try: - evolution_grad = ( - 0.5 - * self.gradient.run( - [ansatz], - [modified_hamiltonian], - parameters=[gradient_params], - parameter_values=[param_values], - ) - .result() - .gradients[0] - ) - except Exception as exc: - raise AlgorithmError("The gradient primitive job failed!") from exc - - # The BaseEstimatorGradient class returns the gradient of the opposite sign than we expect - # here (i.e. with a minus sign), hence the correction that cancels it to recover the - # real McLachlan's principle equations that do not have a minus sign. - evolution_grad = (-1) * evolution_grad - return evolution_grad - - @staticmethod - def _construct_modified_hamiltonian(hamiltonian: BaseOperator, energy: float) -> BaseOperator: - """ - Modifies a Hamiltonian according to the rules of this variational principle. - - Args: - hamiltonian: Operator used for Variational Quantum Time Evolution. - energy: The energy correction value. - - Returns: - A modified Hamiltonian. - """ - energy_term = SparsePauliOp.from_list( - hamiltonian.to_list() + [("I" * hamiltonian.num_qubits, -energy)] - ) - return energy_term - - @staticmethod - def _validate_grad_settings(gradient): - - if gradient is not None: - if not hasattr(gradient, "_derivative_type"): - raise ValueError( - "The gradient instance provided does not support calculating imaginary part. " - "Please choose a different gradient class." - ) - if gradient._derivative_type != DerivativeType.IMAG: - warnings.warn( - "A gradient instance with a setting for calculating real part of the" - "gradient was provided. This variational principle requires the" - "imaginary part. The setting to imaginary was changed automatically." - ) - gradient._derivative_type = DerivativeType.IMAG diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/real_variational_principle.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/real_variational_principle.py deleted file mode 100644 index a93c50675e3e..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/real_variational_principle.py +++ /dev/null @@ -1,22 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Class for a Real Variational Principle.""" - -from abc import ABC - -from .variational_principle import VariationalPrinciple - - -class RealVariationalPrinciple(VariationalPrinciple, ABC): - """Class for a Real Variational Principle. The real variant - means that we consider real time dynamics.""" diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/variational_principle.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/variational_principle.py deleted file mode 100644 index be16849155c4..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/variational_principle.py +++ /dev/null @@ -1,98 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Class for a Variational Principle.""" -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Sequence - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ....exceptions import AlgorithmError -from ....gradients import BaseEstimatorGradient, BaseQGT, DerivativeType - - -class VariationalPrinciple(ABC): - """A Variational Principle class. It determines the time propagation of parameters in a - quantum state provided as a parametrized quantum circuit (ansatz). - - Attributes: - qgt (BaseQGT): Instance of a class used to compute the GQT. - gradient (BaseEstimatorGradient): Instance of a class used to compute the - state gradient. - """ - - def __init__( - self, - qgt: BaseQGT, - gradient: BaseEstimatorGradient, - ) -> None: - """ - Args: - qgt: Instance of a class used to compute the GQT. - gradient: Instance of a class used to compute the state gradient. - """ - self.qgt = qgt - self.gradient = gradient - - def metric_tensor( - self, ansatz: QuantumCircuit, param_values: Sequence[float] - ) -> Sequence[float]: - """ - Calculates a metric tensor according to the rules of this variational principle. - - Args: - ansatz: Quantum state in the form of a parametrized quantum circuit. - param_values: Values of parameters to be bound. - - Returns: - Metric tensor. - - Raises: - AlgorithmError: If a QFI job fails. - """ - - self.qgt.derivative_type = DerivativeType.REAL - try: - metric_tensor = self.qgt.run([ansatz], [param_values], [None]).result().qgts[0] - except Exception as exc: - - raise AlgorithmError("The QFI primitive job failed!") from exc - return metric_tensor - - @abstractmethod - def evolution_gradient( - self, - hamiltonian: BaseOperator, - ansatz: QuantumCircuit, - param_values: Sequence[float], - gradient_params: Sequence[Parameter] | None = None, - ) -> np.ndarray: - """ - Calculates an evolution gradient according to the rules of this variational principle. - - Args: - hamiltonian: Operator used for Variational Quantum Time Evolution. - ansatz: Quantum state in the form of a parametrized quantum circuit. - param_values: Values of parameters to be bound. - gradient_params: List of parameters with respect to which gradients should be computed. - If ``None`` given, gradients w.r.t. all parameters will be computed. - - Returns: - An evolution gradient. - """ - pass diff --git a/qiskit/algorithms/utils/__init__.py b/qiskit/algorithms/utils/__init__.py deleted file mode 100644 index 2b49396270c7..000000000000 --- a/qiskit/algorithms/utils/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Common Qiskit algorithms utility functions.""" - -from .validate_initial_point import validate_initial_point -from .validate_bounds import validate_bounds - -__all__ = [ - "validate_initial_point", - "validate_bounds", -] diff --git a/qiskit/algorithms/utils/set_batching.py b/qiskit/algorithms/utils/set_batching.py deleted file mode 100644 index 225f50a6fed8..000000000000 --- a/qiskit/algorithms/utils/set_batching.py +++ /dev/null @@ -1,27 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Set default batch sizes for the optimizers.""" - -from qiskit.algorithms.optimizers import Optimizer, SPSA - - -def _set_default_batchsize(optimizer: Optimizer) -> bool: - """Set the default batchsize, if None is set and return whether it was updated or not.""" - if isinstance(optimizer, SPSA): - updated = optimizer._max_evals_grouped is None - if updated: - optimizer.set_max_evals_grouped(50) - else: # we only set a batchsize for SPSA - updated = False - - return updated diff --git a/qiskit/algorithms/utils/validate_bounds.py b/qiskit/algorithms/utils/validate_bounds.py deleted file mode 100644 index 747e68f78a52..000000000000 --- a/qiskit/algorithms/utils/validate_bounds.py +++ /dev/null @@ -1,44 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Validate parameter bounds.""" - -from __future__ import annotations - -from qiskit.circuit import QuantumCircuit - - -def validate_bounds(circuit: QuantumCircuit) -> list[tuple[float | None, float | None]]: - """ - Validate the bounds provided by a quantum circuit against its number of parameters. - If no bounds are obtained, return ``None`` for all lower and upper bounds. - - Args: - circuit: A parameterized quantum circuit. - - Returns: - A list of tuples (lower_bound, upper_bound)). - - Raises: - ValueError: If the number of bounds does not the match the number of circuit parameters. - """ - if hasattr(circuit, "parameter_bounds") and circuit.parameter_bounds is not None: - bounds = circuit.parameter_bounds - if len(bounds) != circuit.num_parameters: - raise ValueError( - f"The number of bounds ({len(bounds)}) does not match the number of " - f"parameters in the circuit ({circuit.num_parameters})." - ) - else: - bounds = [(None, None)] * circuit.num_parameters - - return bounds diff --git a/qiskit/algorithms/utils/validate_initial_point.py b/qiskit/algorithms/utils/validate_initial_point.py deleted file mode 100644 index 56a7654a16e4..000000000000 --- a/qiskit/algorithms/utils/validate_initial_point.py +++ /dev/null @@ -1,72 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Validate an initial point.""" - -from __future__ import annotations - -import warnings -from collections.abc import Sequence - -import numpy as np - -from qiskit.circuit import QuantumCircuit -from qiskit.utils import algorithm_globals - - -def validate_initial_point( - point: Sequence[float] | None, circuit: QuantumCircuit -) -> Sequence[float]: - r""" - Validate a choice of initial point against a choice of circuit. If no point is provided, a - random point will be generated within certain parameter bounds. It will first look to the - circuit for these bounds. If the circuit does not specify bounds, bounds of :math:`-2\pi`, - :math:`2\pi` will be used. - - Args: - point: An initial point. - circuit: A parameterized quantum circuit. - - Returns: - A validated initial point. - - Raises: - ValueError: If the dimension of the initial point does not match the number of circuit - parameters. - """ - expected_size = circuit.num_parameters - - if point is None: - # get bounds if circuit has them set, otherwise use [-2pi, 2pi] for each parameter - bounds = getattr(circuit, "parameter_bounds", None) - if bounds is None: - bounds = [(-2 * np.pi, 2 * np.pi)] * expected_size - - # replace all Nones by [-2pi, 2pi] - lower_bounds = [] - upper_bounds = [] - for lower, upper in bounds: - lower_bounds.append(lower if lower is not None else -2 * np.pi) - upper_bounds.append(upper if upper is not None else 2 * np.pi) - - # sample from within bounds - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - point = algorithm_globals.random.uniform(lower_bounds, upper_bounds) - - elif len(point) != expected_size: - raise ValueError( - f"The dimension of the initial point ({len(point)}) does not match the " - f"number of parameters in the circuit ({expected_size})." - ) - - return point diff --git a/qiskit/algorithms/variational_algorithm.py b/qiskit/algorithms/variational_algorithm.py deleted file mode 100644 index 1b8b2ec6a164..000000000000 --- a/qiskit/algorithms/variational_algorithm.py +++ /dev/null @@ -1,137 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2021. -# -# 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. - -"""The Variational Algorithm Base Class. - -This class can be used an interface for working with Variation Algorithms, such as VQE, -QAOA, or QSVM, and also provides helper utilities for implementing new variational algorithms. -Writing a new variational algorithm is a simple as extending this class, implementing a cost -function for the new algorithm to pass to the optimizer, and running :meth:`find_minimum` method -of this class to carry out the optimization. Alternatively, all of the functions below can be -overridden to opt-out of this infrastructure but still meet the interface requirements. - -.. note:: - - This component has some function that is normally random. If you want to reproduce behavior - then you should set the random number generator seed in the algorithm_globals - (``qiskit.utils.algorithm_globals.random_seed = seed``). -""" - -from __future__ import annotations -from abc import ABC, abstractmethod -import numpy as np - -from qiskit.circuit import QuantumCircuit - -from .algorithm_result import AlgorithmResult -from .optimizers import OptimizerResult - - -class VariationalAlgorithm(ABC): - """The Variational Algorithm Base Class.""" - - @property - @abstractmethod - def initial_point(self) -> np.ndarray | None: - """Returns initial point.""" - pass - - @initial_point.setter - @abstractmethod - def initial_point(self, initial_point: np.ndarray | None) -> None: - """Sets initial point.""" - pass - - -class VariationalResult(AlgorithmResult): - """Variation Algorithm Result.""" - - def __init__(self) -> None: - super().__init__() - self._optimizer_evals: int | None = None - self._optimizer_time: float | None = None - self._optimal_value: float | None = None - self._optimal_point: np.ndarray | None = None - self._optimal_parameters: dict | None = None - self._optimizer_result: OptimizerResult | None = None - self._optimal_circuit: QuantumCircuit | None = None - - @property - def optimizer_evals(self) -> int | None: - """Returns number of optimizer evaluations""" - return self._optimizer_evals - - @optimizer_evals.setter - def optimizer_evals(self, value: int) -> None: - """Sets number of optimizer evaluations""" - self._optimizer_evals = value - - @property - def optimizer_time(self) -> float | None: - """Returns time taken for optimization""" - return self._optimizer_time - - @optimizer_time.setter - def optimizer_time(self, value: float) -> None: - """Sets time taken for optimization""" - self._optimizer_time = value - - @property - def optimal_value(self) -> float | None: - """Returns optimal value""" - return self._optimal_value - - @optimal_value.setter - def optimal_value(self, value: int) -> None: - """Sets optimal value""" - self._optimal_value = value - - @property - def optimal_point(self) -> np.ndarray | None: - """Returns optimal point""" - return self._optimal_point - - @optimal_point.setter - def optimal_point(self, value: np.ndarray) -> None: - """Sets optimal point""" - self._optimal_point = value - - @property - def optimal_parameters(self) -> dict | None: - """Returns the optimal parameters in a dictionary""" - return self._optimal_parameters - - @optimal_parameters.setter - def optimal_parameters(self, value: dict) -> None: - """Sets optimal parameters""" - self._optimal_parameters = value - - @property - def optimizer_result(self) -> OptimizerResult | None: - """Returns the optimizer result""" - return self._optimizer_result - - @optimizer_result.setter - def optimizer_result(self, value: OptimizerResult) -> None: - """Sets optimizer result""" - self._optimizer_result = value - - @property - def optimal_circuit(self) -> QuantumCircuit: - """The optimal circuits. Along with the optimal parameters, - these can be used to retrieve the minimum eigenstate. - """ - return self._optimal_circuit - - @optimal_circuit.setter - def optimal_circuit(self, optimal_circuit: QuantumCircuit) -> None: - self._optimal_circuit = optimal_circuit diff --git a/qiskit/circuit/barrier.py b/qiskit/circuit/barrier.py index 4bb37558bc20..54e6b2c06191 100644 --- a/qiskit/circuit/barrier.py +++ b/qiskit/circuit/barrier.py @@ -47,8 +47,5 @@ def inverse(self): """Special case. Return self.""" return Barrier(self.num_qubits) - def broadcast_arguments(self, qargs, cargs): - yield [qarg for sublist in qargs for qarg in sublist], [] - def c_if(self, classical, val): raise QiskitError("Barriers are compiler directives and cannot be conditional.") diff --git a/qiskit/circuit/bit.py b/qiskit/circuit/bit.py index c186755eff76..d51e82c84625 100644 --- a/qiskit/circuit/bit.py +++ b/qiskit/circuit/bit.py @@ -24,7 +24,7 @@ class Bit: .. note:: This class should not be instantiated directly. This is just a superclass - for :class:`~.Clbit` and :class:`~.Qubit`. + for :class:`~.Clbit` and :class:`~.circuit.Qubit`. """ @@ -63,6 +63,7 @@ def __init__(self, register=None, index=None): @deprecate_func( is_property=True, since="0.17", + package_name="qiskit-terra", additional_msg=( "Instead, use :meth:`~qiskit.circuit.quantumcircuit.QuantumCircuit.find_bit` to find " "all the containing registers within a circuit and the index of the bit within the " @@ -85,6 +86,7 @@ def register(self): # pylint: disable=bad-docstring-quotes @deprecate_func( is_property=True, since="0.17", + package_name="qiskit-terra", additional_msg=( "Instead, use :meth:`~qiskit.circuit.quantumcircuit.QuantumCircuit.find_bit` to find " "all the containing registers within a circuit and the index of the bit within the " diff --git a/qiskit/circuit/classicalregister.py b/qiskit/circuit/classicalregister.py index 644705ffee34..9ae39055039c 100644 --- a/qiskit/circuit/classicalregister.py +++ b/qiskit/circuit/classicalregister.py @@ -64,6 +64,7 @@ class ClassicalRegister(Register): "provided, because the premise is wrong." ), since="0.23.0", + package_name="qiskit-terra", ) def qasm(self): """Return OPENQASM string for this register.""" diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 6b5ff71cf187..84e3f344bf07 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -215,7 +215,7 @@ def __eq__(self, other): return True def __repr__(self) -> str: - """Generates a representation of the Intruction object instance + """Generates a representation of the Instruction object instance Returns: str: A representation of the Instruction instance with the name, number of qubits, classical bits and params( if any ) @@ -523,6 +523,7 @@ def _qasmif(self, string): "provided, because the premise is wrong." ), since="0.25.0", + package_name="qiskit-terra", ) def qasm(self): """Return a default OpenQASM string for the instruction. diff --git a/qiskit/circuit/library/blueprintcircuit.py b/qiskit/circuit/library/blueprintcircuit.py index 415ae8ecd117..082249e54b04 100644 --- a/qiskit/circuit/library/blueprintcircuit.py +++ b/qiskit/circuit/library/blueprintcircuit.py @@ -119,10 +119,10 @@ def qasm(self, formatted=False, filename=None, encoding=None): self._build() return super().qasm(formatted, filename, encoding) - def append(self, instruction, qargs=None, cargs=None): + def _append(self, instruction, _qargs=None, _cargs=None): if not self._is_built: self._build() - return super().append(instruction, qargs, cargs) + return super()._append(instruction, _qargs, _cargs) def compose(self, other, qubits=None, clbits=None, front=False, inplace=False, wrap=False): if not self._is_built: diff --git a/qiskit/circuit/library/generalized_gates/mcmt.py b/qiskit/circuit/library/generalized_gates/mcmt.py index f72424ccefb4..1a529b8859e1 100644 --- a/qiskit/circuit/library/generalized_gates/mcmt.py +++ b/qiskit/circuit/library/generalized_gates/mcmt.py @@ -17,7 +17,7 @@ from collections.abc import Callable from qiskit import circuit -from qiskit.circuit import ControlledGate, Gate, Qubit, QuantumRegister, QuantumCircuit +from qiskit.circuit import ControlledGate, Gate, QuantumRegister, QuantumCircuit from qiskit.exceptions import QiskitError # pylint: disable=cyclic-import @@ -51,7 +51,7 @@ class MCMT(QuantumCircuit): def __init__( self, - gate: Gate | Callable[[QuantumCircuit, Qubit, Qubit], circuit.Instruction], + gate: Gate | Callable[[QuantumCircuit, circuit.Qubit, circuit.Qubit], circuit.Instruction], num_ctrl_qubits: int, num_target_qubits: int, ) -> None: @@ -214,8 +214,8 @@ def num_ancilla_qubits(self): def _ccx_v_chain_rule( self, - control_qubits: QuantumRegister | list[Qubit], - ancilla_qubits: QuantumRegister | list[Qubit], + control_qubits: QuantumRegister | list[circuit.Qubit], + ancilla_qubits: QuantumRegister | list[circuit.Qubit], reverse: bool = False, ) -> None: """Get the rule for the CCX V-chain. diff --git a/qiskit/circuit/library/hamiltonian_gate.py b/qiskit/circuit/library/hamiltonian_gate.py index eb7e511102bb..d59faf35b0a5 100644 --- a/qiskit/circuit/library/hamiltonian_gate.py +++ b/qiskit/circuit/library/hamiltonian_gate.py @@ -130,6 +130,7 @@ def _define(self): @deprecate_func( since="0.25.0", + package_name="qiskit-terra", ) def qasm(self): """Raise an error, as QASM is not defined for the HamiltonianGate.""" diff --git a/qiskit/circuit/library/overlap.py b/qiskit/circuit/library/overlap.py index 15c39435441d..ed86d8abb9a2 100644 --- a/qiskit/circuit/library/overlap.py +++ b/qiskit/circuit/library/overlap.py @@ -15,6 +15,7 @@ from qiskit.circuit import QuantumCircuit, Gate from qiskit.circuit.parametervector import ParameterVector from qiskit.circuit.exceptions import CircuitError +from qiskit.circuit import Barrier class UnitaryOverlap(QuantumCircuit): @@ -101,7 +102,7 @@ def _check_unitary(circuit): """Check a circuit is unitary by checking if all operations are of type ``Gate``.""" for instruction in circuit.data: - if not isinstance(instruction.operation, Gate): + if not isinstance(instruction.operation, (Gate, Barrier)): raise CircuitError( ( "One or more instructions cannot be converted to" diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 8fcf53f4cfcb..29329914ed1e 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -590,7 +590,7 @@ def _define(self): self.definition = qc - @deprecate_func(since="0.25.0") + @deprecate_func(since="0.25.0", package_name="qiskit-terra") def qasm(self): # Gross hack to override the Qiskit name with the name this gate has in Terra's version of # 'qelib1.inc'. In general, the larger exporter mechanism should know about this to do the @@ -640,7 +640,7 @@ def __init__( _singleton_lookup_key = stdlib_singleton_key(num_ctrl_qubits=3) - # seems like open controls not hapening? + # seems like open controls not happening? def _define(self): """ gate c3x a,b,c,d diff --git a/qiskit/circuit/measure.py b/qiskit/circuit/measure.py index 1da5923e953f..548aaecd81a5 100644 --- a/qiskit/circuit/measure.py +++ b/qiskit/circuit/measure.py @@ -14,17 +14,19 @@ Quantum measurement in the computational basis. """ -from qiskit.circuit.instruction import Instruction +from qiskit.circuit.singleton import SingletonInstruction, stdlib_singleton_key from qiskit.circuit.exceptions import CircuitError -class Measure(Instruction): +class Measure(SingletonInstruction): """Quantum measurement in the computational basis.""" def __init__(self, label=None, *, duration=None, unit="dt"): """Create new measurement instruction.""" super().__init__("measure", 1, 1, [], label=label, duration=duration, unit=unit) + _singleton_lookup_key = stdlib_singleton_key() + def broadcast_arguments(self, qargs, cargs): qarg = qargs[0] carg = cargs[0] diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index fea533f2e072..bc89e9740e2f 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -59,8 +59,6 @@ from .bit import Bit from .quantumcircuitdata import QuantumCircuitData, CircuitInstruction from .delay import Delay -from .measure import Measure -from .reset import Reset if typing.TYPE_CHECKING: import qiskit # pylint: disable=cyclic-import @@ -381,8 +379,8 @@ def data(self, data_input: Iterable): elements must either be instances of :class:`.CircuitInstruction` (preferred), or a 3-tuple of ``(instruction, qargs, cargs)`` (legacy). In the legacy format, ``instruction`` must be an :class:`~.circuit.Instruction`, while ``qargs`` and - ``cargs`` must be iterables of :class:`.Qubit` or :class:`.Clbit` specifiers - (similar to the allowed forms in calls to :meth:`append`). + ``cargs`` must be iterables of :class:`~.circuit.Qubit` or :class:`.Clbit` + specifiers (similar to the allowed forms in calls to :meth:`append`). """ # If data_input is QuantumCircuitData(self), clearing self._data # below will also empty data_input, so make a shallow copy first. @@ -1188,7 +1186,7 @@ def _resolve_classical_resource(self, specifier): if isinstance(specifier, ClassicalRegister): # This is linear complexity for something that should be constant, but QuantumCircuit # does not currently keep a hashmap of registers, and requires non-trivial changes to - # how it exposes its registers publically before such a map can be safely stored so it + # how it exposes its registers publicly before such a map can be safely stored so it # doesn't miss updates. (Jake, 2021-11-10). if specifier not in self.cregs: raise CircuitError(f"Register {specifier} is not present in this circuit.") @@ -1230,7 +1228,7 @@ def append( Args: instruction: :class:`~.circuit.Instruction` instance to append, or a :class:`.CircuitInstruction` with all its context. - qargs: specifiers of the :class:`.Qubit`\\ s to attach instruction to. + qargs: specifiers of the :class:`~.circuit.Qubit`\\ s to attach instruction to. cargs: specifiers of the :class:`.Clbit`\\ s to attach instruction to. Returns: @@ -2180,6 +2178,8 @@ def reset(self, qubit: QubitSpecifier) -> InstructionSet: Returns: qiskit.circuit.InstructionSet: handle to the added instruction. """ + from .reset import Reset + return self.append(Reset(), [qubit], []) def measure(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet: @@ -2255,6 +2255,8 @@ def measure(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet circuit.measure(qreg[1], creg[1]) """ + from .measure import Measure + return self.append(Measure(), [qubit], [cbit]) def measure_active(self, inplace: bool = True) -> Optional["QuantumCircuit"]: @@ -2802,10 +2804,7 @@ def _unroll_param_dict( out[parameter] = value return out - @deprecate_func( - additional_msg=("Use assign_parameters() instead"), - since="0.45.0", - ) + @deprecate_func(additional_msg=("Use assign_parameters() instead"), since="0.45.0") def bind_parameters( self, values: Union[Mapping[Parameter, float], Sequence[float]] ) -> "QuantumCircuit": diff --git a/qiskit/circuit/quantumregister.py b/qiskit/circuit/quantumregister.py index de87f73a79a3..67fe26b1b224 100644 --- a/qiskit/circuit/quantumregister.py +++ b/qiskit/circuit/quantumregister.py @@ -65,6 +65,7 @@ class QuantumRegister(Register): "provided, because the premise is wrong." ), since="0.23.0", + package_name="qiskit-terra", ) def qasm(self): """Return OPENQASM string for this register.""" diff --git a/qiskit/circuit/reset.py b/qiskit/circuit/reset.py index 3243afad4010..183004dbf877 100644 --- a/qiskit/circuit/reset.py +++ b/qiskit/circuit/reset.py @@ -14,16 +14,18 @@ Qubit reset to computational zero. """ -from qiskit.circuit.instruction import Instruction +from qiskit.circuit.singleton import SingletonInstruction, stdlib_singleton_key -class Reset(Instruction): +class Reset(SingletonInstruction): """Qubit reset.""" def __init__(self, label=None, *, duration=None, unit="dt"): """Create new reset instruction.""" super().__init__("reset", 1, 0, [], label=label, duration=duration, unit=unit) + _singleton_lookup_key = stdlib_singleton_key() + def broadcast_arguments(self, qargs, cargs): for qarg in qargs[0]: yield [qarg], [] diff --git a/qiskit/circuit/singleton.py b/qiskit/circuit/singleton.py index 26cf5163bf0b..bd689b6be103 100644 --- a/qiskit/circuit/singleton.py +++ b/qiskit/circuit/singleton.py @@ -156,7 +156,7 @@ def _singleton_lookup_key(n=1, label=None): there will be two singleton instances instantiated. One corresponds to ``n=1`` and ``label=None``, and the other to ``n=2`` and ``label="two"``. Whenever ``MySingleton`` is constructed with -arguments consistent with one of those two cases, the relavent singleton will be returned. For +arguments consistent with one of those two cases, the relevant singleton will be returned. For example:: assert MySingleton() is MySingleton(1, label=None) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 22dfd35be3c2..a2b3971b5944 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -28,7 +28,7 @@ from qiskit.pulse import Schedule, InstructionScheduleMap from qiskit.transpiler import Layout, CouplingMap, PropertySet from qiskit.transpiler.basepasses import BasePass -from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.exceptions import TranspilerError, CircuitTooWideForTarget from qiskit.transpiler.instruction_durations import InstructionDurations, InstructionDurationsType from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager @@ -455,7 +455,7 @@ def _check_circuits_coupling_map(circuits, cmap, backend): # If coupling_map is not None or num_qubits == 1 num_qubits = len(circuit.qubits) if max_qubits is not None and (num_qubits > max_qubits): - raise TranspilerError( + raise CircuitTooWideForTarget( f"Number of qubits ({num_qubits}) in {circuit.name} " f"is greater than maximum ({max_qubits}) in the coupling_map" ) diff --git a/qiskit/converters/circuit_to_dag.py b/qiskit/converters/circuit_to_dag.py index 24f0ddd7a855..e2612b43d3e6 100644 --- a/qiskit/converters/circuit_to_dag.py +++ b/qiskit/converters/circuit_to_dag.py @@ -28,8 +28,8 @@ def circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_ord :class:`~.DAGCircuit` will be shared instances and modifications to operations in the :class:`~.DAGCircuit` will be reflected in the :class:`~.QuantumCircuit` (and vice versa). - qubit_order (Iterable[Qubit] or None): the order that the qubits should be indexed in the - output DAG. Defaults to the same order as in the circuit. + qubit_order (Iterable[~qiskit.circuit.Qubit] or None): the order that the qubits should be + indexed in the output DAG. Defaults to the same order as in the circuit. clbit_order (Iterable[Clbit] or None): the order that the clbits should be indexed in the output DAG. Defaults to the same order as in the circuit. diff --git a/qiskit/dagcircuit/collect_blocks.py b/qiskit/dagcircuit/collect_blocks.py index cf9adf16fe85..b15cfee71087 100644 --- a/qiskit/dagcircuit/collect_blocks.py +++ b/qiskit/dagcircuit/collect_blocks.py @@ -346,7 +346,7 @@ def collapse_to_operation(self, blocks, collapse_fn): # Additionally, find the set of classical registers used in conditions over full registers # (in such a case, we need to add that register to the block circuit, not just its clbits). - cur_clregs = [] + cur_clregs = set() for node in block: cur_qubits.update(node.qargs) @@ -355,7 +355,7 @@ def collapse_to_operation(self, blocks, collapse_fn): if cond is not None: cur_clbits.update(condition_resources(cond).clbits) if isinstance(cond[0], ClassicalRegister): - cur_clregs.append(cond[0]) + cur_clregs.add(cond[0]) # For reproducibility, order these qubits/clbits compatibly with the global order. sorted_qubits = sorted(cur_qubits, key=lambda x: global_index_map[x]) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 4233144c86fe..126f19f1bb5d 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -23,7 +23,6 @@ from collections import OrderedDict, defaultdict, deque, namedtuple import copy import math -import warnings from typing import Dict, Generator, Any, List import numpy as np @@ -424,10 +423,10 @@ def remove_qubits(self, *qubits): also be removed. Args: - qubits (List[Qubit]): The bits to remove. + qubits (List[~qiskit.circuit.Qubit]): The bits to remove. Raises: - DAGCircuitError: a qubit is not a :obj:`.Qubit`, is not in the circuit, + DAGCircuitError: a qubit is not a :obj:`~.circuit.Qubit`, is not in the circuit, or is not idle. """ if any(not isinstance(qubit, Qubit) for qubit in qubits): @@ -643,11 +642,11 @@ def apply_operation_back(self, op, qargs=(), cargs=(), *, check=True): Args: op (qiskit.circuit.Operation): the operation associated with the DAG node - qargs (tuple[Qubit]): qubits that op will be applied to + qargs (tuple[~qiskit.circuit.Qubit]): qubits that op will be applied to cargs (tuple[Clbit]): cbits that op will be applied to check (bool): If ``True`` (default), this function will enforce that the :class:`.DAGCircuit` data-structure invariants are maintained (all ``qargs`` are - :class:`.Qubit`\\ s, all are in the DAG, etc). If ``False``, the caller *must* + :class:`~.circuit.Qubit`\\ s, all are in the DAG, etc). If ``False``, the caller *must* uphold these invariants itself, but the cost of several checks will be skipped. This is most useful when building a new DAG from a source of known-good nodes. Returns: @@ -657,16 +656,8 @@ def apply_operation_back(self, op, qargs=(), cargs=(), *, check=True): DAGCircuitError: if a leaf node is connected to multiple outputs """ - if qargs is None: - _warn_none_args() - qargs = () - else: - qargs = tuple(qargs) - if cargs is None: - _warn_none_args() - cargs = () - else: - cargs = tuple(cargs) + qargs = tuple(qargs) + cargs = tuple(cargs) if self._operation_may_have_bits(op): # This is the slow path; most of the time, this won't happen. @@ -697,11 +688,11 @@ def apply_operation_front(self, op, qargs=(), cargs=(), *, check=True): Args: op (qiskit.circuit.Operation): the operation associated with the DAG node - qargs (tuple[Qubit]): qubits that op will be applied to + qargs (tuple[~qiskit.circuit.Qubit]): qubits that op will be applied to cargs (tuple[Clbit]): cbits that op will be applied to check (bool): If ``True`` (default), this function will enforce that the :class:`.DAGCircuit` data-structure invariants are maintained (all ``qargs`` are - :class:`.Qubit`\\ s, all are in the DAG, etc). If ``False``, the caller *must* + :class:`~.circuit.Qubit`\\ s, all are in the DAG, etc). If ``False``, the caller *must* uphold these invariants itself, but the cost of several checks will be skipped. This is most useful when building a new DAG from a source of known-good nodes. Returns: @@ -710,16 +701,8 @@ def apply_operation_front(self, op, qargs=(), cargs=(), *, check=True): Raises: DAGCircuitError: if initial nodes connected to multiple out edges """ - if qargs is None: - _warn_none_args() - qargs = () - else: - qargs = tuple(qargs) - if cargs is None: - _warn_none_args() - cargs = () - else: - cargs = tuple(cargs) + qargs = tuple(qargs) + cargs = tuple(cargs) if self._operation_may_have_bits(op): # This is the slow path; most of the time, this won't happen. @@ -755,7 +738,7 @@ def compose(self, other, qubits=None, clbits=None, front=False, inplace=True): Args: other (DAGCircuit): circuit to compose with self - qubits (list[Qubit|int]): qubits of self to compose onto. + qubits (list[~qiskit.circuit.Qubit|int]): qubits of self to compose onto. clbits (list[Clbit|int]): clbits of self to compose onto. front (bool): If True, front composition will be performed (not implemented yet) inplace (bool): If True, modify the object. Otherwise return composed circuit. @@ -821,7 +804,7 @@ def compose(self, other, qubits=None, clbits=None, front=False, inplace=True): for gate, cals in other.calibrations.items(): dag._calibrations[gate].update(cals) - # Ensure that the error raised here is a `DAGCircuitError` for backwards compatiblity. + # Ensure that the error raised here is a `DAGCircuitError` for backwards compatibility. def _reject_new_register(reg): raise DAGCircuitError(f"No register with '{reg.bits}' to map this expression onto.") @@ -1204,7 +1187,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None, propagate_condit the operation within ``node`` is propagated to each node in the ``input_dag``. If ``False``, then the ``input_dag`` is assumed to faithfully implement suitable conditional logic already. This is ignored for :class:`.ControlFlowOp`\\ s (i.e. - treated as if it is ``False``); replacements of those must already fulfil the same + treated as if it is ``False``); replacements of those must already fulfill the same conditional logic or this function would be close to useless for them. Returns: @@ -2042,10 +2025,10 @@ def quantum_causal_cone(self, qubit): classical bit wires are ignored for the purposes of building the causal cone. Args: - qubit (Qubit): The output qubit for which we want to find the causal cone. + qubit (~qiskit.circuit.Qubit): The output qubit for which we want to find the causal cone. Returns: - Set[Qubit]: The set of qubits whose interactions affect ``qubit``. + Set[~qiskit.circuit.Qubit]: The set of qubits whose interactions affect ``qubit``. """ # Retrieve the output node from the qubit output_node = self.output_map.get(qubit, None) @@ -2116,12 +2099,3 @@ def draw(self, scale=0.7, filename=None, style="color"): from qiskit.visualization.dag_visualization import dag_drawer return dag_drawer(dag=self, scale=scale, filename=filename, style=style) - - -def _warn_none_args(): - warnings.warn( - "Passing 'None' as the qubits or clbits of an operation to 'DAGCircuit' methods" - " is deprecated since Qiskit 0.45 and will be removed in Qiskit 1.0. Instead, pass '()'.", - DeprecationWarning, - stacklevel=3, - ) diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 8e4904c2b8b2..a1e24b4e0b8d 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -377,7 +377,7 @@ def _create_op_node(self, operation, qargs, cargs): Args: operation (qiskit.circuit.Operation): operation - qargs (list[Qubit]): list of qubits on which the operation acts + qargs (list[~qiskit.circuit.Qubit]): list of qubits on which the operation acts cargs (list[Clbit]): list of classical wires to attach to Returns: @@ -421,7 +421,7 @@ def add_op_node(self, operation, qargs, cargs): Args: operation (qiskit.circuit.Operation): operation as a quantum gate - qargs (list[Qubit]): list of qubits on which the operation acts + qargs (list[~qiskit.circuit.Qubit]): list of qubits on which the operation acts cargs (list[Clbit]): list of classical wires to attach to """ new_node = self._create_op_node(operation, qargs, cargs) @@ -561,7 +561,7 @@ def replace_block_with_op(self, node_block, op, wire_pos_map, cycle_check=True): node block to be replaced op (qiskit.circuit.Operation): The operation to replace the block with - wire_pos_map (Dict[Qubit, int]): The dictionary mapping the qarg to + wire_pos_map (Dict[~qiskit.circuit.Qubit, int]): The dictionary mapping the qarg to the position. This is necessary to reconstruct the qarg order over multiple gates in the combined single op node. cycle_check (bool): When set to True this method will check that diff --git a/qiskit/opflow/converters/abelian_grouper.py b/qiskit/opflow/converters/abelian_grouper.py index fa1d1842a8ed..b267fc699411 100644 --- a/qiskit/opflow/converters/abelian_grouper.py +++ b/qiskit/opflow/converters/abelian_grouper.py @@ -44,6 +44,7 @@ class AbelianGrouper(ConverterBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, traverse: bool = True) -> None: diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index e8e5938617e7..04116c93b31f 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -54,6 +54,7 @@ class CircuitSampler(ConverterBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/converters/converter_base.py b/qiskit/opflow/converters/converter_base.py index 231f3f2daad7..e1ed07f2e1c4 100644 --- a/qiskit/opflow/converters/converter_base.py +++ b/qiskit/opflow/converters/converter_base.py @@ -32,6 +32,7 @@ class ConverterBase(ABC): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/converters/dict_to_circuit_sum.py b/qiskit/opflow/converters/dict_to_circuit_sum.py index b52f16f185c6..04facc6fe506 100644 --- a/qiskit/opflow/converters/dict_to_circuit_sum.py +++ b/qiskit/opflow/converters/dict_to_circuit_sum.py @@ -31,6 +31,7 @@ class DictToCircuitSum(ConverterBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/converters/pauli_basis_change.py b/qiskit/opflow/converters/pauli_basis_change.py index 6a39f2ef73cc..0d2213a761dc 100644 --- a/qiskit/opflow/converters/pauli_basis_change.py +++ b/qiskit/opflow/converters/pauli_basis_change.py @@ -58,6 +58,7 @@ class PauliBasisChange(ConverterBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/converters/two_qubit_reduction.py b/qiskit/opflow/converters/two_qubit_reduction.py index 10f596f10494..aa34d2cef4ca 100644 --- a/qiskit/opflow/converters/two_qubit_reduction.py +++ b/qiskit/opflow/converters/two_qubit_reduction.py @@ -38,6 +38,7 @@ class TwoQubitReduction(ConverterBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, num_particles: Union[int, List[int], Tuple[int, int]]): diff --git a/qiskit/opflow/evolutions/evolution_base.py b/qiskit/opflow/evolutions/evolution_base.py index 1123c36c34a7..4c168078e0de 100644 --- a/qiskit/opflow/evolutions/evolution_base.py +++ b/qiskit/opflow/evolutions/evolution_base.py @@ -32,6 +32,7 @@ class EvolutionBase(ConverterBase, ABC): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/evolutions/evolution_factory.py b/qiskit/opflow/evolutions/evolution_factory.py index fffad684e816..1f9a64e0ad7f 100644 --- a/qiskit/opflow/evolutions/evolution_factory.py +++ b/qiskit/opflow/evolutions/evolution_factory.py @@ -27,6 +27,7 @@ class EvolutionFactory: @staticmethod @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def build(operator: OperatorBase = None) -> EvolutionBase: diff --git a/qiskit/opflow/evolutions/evolved_op.py b/qiskit/opflow/evolutions/evolved_op.py index 03771aa52fa8..cf3a8ef53ae8 100644 --- a/qiskit/opflow/evolutions/evolved_op.py +++ b/qiskit/opflow/evolutions/evolved_op.py @@ -42,6 +42,7 @@ class EvolvedOp(PrimitiveOp): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/evolutions/matrix_evolution.py b/qiskit/opflow/evolutions/matrix_evolution.py index 8c18a6a57728..7ecdfa79ee98 100644 --- a/qiskit/opflow/evolutions/matrix_evolution.py +++ b/qiskit/opflow/evolutions/matrix_evolution.py @@ -33,6 +33,7 @@ class MatrixEvolution(EvolutionBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/evolutions/pauli_trotter_evolution.py b/qiskit/opflow/evolutions/pauli_trotter_evolution.py index 414ffd5777ca..7ed93d04770c 100644 --- a/qiskit/opflow/evolutions/pauli_trotter_evolution.py +++ b/qiskit/opflow/evolutions/pauli_trotter_evolution.py @@ -53,6 +53,7 @@ class PauliTrotterEvolution(EvolutionBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/evolutions/trotterizations/qdrift.py b/qiskit/opflow/evolutions/trotterizations/qdrift.py index 8a31fde01c93..ca54d2592f4b 100644 --- a/qiskit/opflow/evolutions/trotterizations/qdrift.py +++ b/qiskit/opflow/evolutions/trotterizations/qdrift.py @@ -40,6 +40,7 @@ class QDrift(TrotterizationBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, reps: int = 1) -> None: diff --git a/qiskit/opflow/evolutions/trotterizations/suzuki.py b/qiskit/opflow/evolutions/trotterizations/suzuki.py index cfe59acac5a8..884034f12b35 100644 --- a/qiskit/opflow/evolutions/trotterizations/suzuki.py +++ b/qiskit/opflow/evolutions/trotterizations/suzuki.py @@ -37,6 +37,7 @@ class Suzuki(TrotterizationBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, reps: int = 1, order: int = 2) -> None: diff --git a/qiskit/opflow/evolutions/trotterizations/trotter.py b/qiskit/opflow/evolutions/trotterizations/trotter.py index eb1c48d7e27d..406598f845bb 100644 --- a/qiskit/opflow/evolutions/trotterizations/trotter.py +++ b/qiskit/opflow/evolutions/trotterizations/trotter.py @@ -24,6 +24,7 @@ class Trotter(Suzuki): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, reps: int = 1) -> None: diff --git a/qiskit/opflow/evolutions/trotterizations/trotterization_base.py b/qiskit/opflow/evolutions/trotterizations/trotterization_base.py index 222d338dfdce..9866209ecbab 100644 --- a/qiskit/opflow/evolutions/trotterizations/trotterization_base.py +++ b/qiskit/opflow/evolutions/trotterizations/trotterization_base.py @@ -28,6 +28,7 @@ class TrotterizationBase(EvolutionBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, reps: int = 1) -> None: diff --git a/qiskit/opflow/evolutions/trotterizations/trotterization_factory.py b/qiskit/opflow/evolutions/trotterizations/trotterization_factory.py index f8d119140502..e82ccd3b94be 100644 --- a/qiskit/opflow/evolutions/trotterizations/trotterization_factory.py +++ b/qiskit/opflow/evolutions/trotterizations/trotterization_factory.py @@ -25,6 +25,7 @@ class TrotterizationFactory: @staticmethod @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def build(mode: str = "trotter", reps: int = 1) -> TrotterizationBase: diff --git a/qiskit/opflow/exceptions.py b/qiskit/opflow/exceptions.py index 27bc0f6cc14d..de0b526cc79e 100644 --- a/qiskit/opflow/exceptions.py +++ b/qiskit/opflow/exceptions.py @@ -21,6 +21,7 @@ class OpflowError(QiskitError): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, *message): diff --git a/qiskit/opflow/expectations/aer_pauli_expectation.py b/qiskit/opflow/expectations/aer_pauli_expectation.py index b68054281457..6eaa77d3d644 100644 --- a/qiskit/opflow/expectations/aer_pauli_expectation.py +++ b/qiskit/opflow/expectations/aer_pauli_expectation.py @@ -41,6 +41,7 @@ class AerPauliExpectation(ExpectationBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/expectations/cvar_expectation.py b/qiskit/opflow/expectations/cvar_expectation.py index 9a6711c498ae..8de1a2897c87 100644 --- a/qiskit/opflow/expectations/cvar_expectation.py +++ b/qiskit/opflow/expectations/cvar_expectation.py @@ -57,6 +57,7 @@ class CVaRExpectation(ExpectationBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, alpha: float, expectation: Optional[ExpectationBase] = None) -> None: diff --git a/qiskit/opflow/expectations/expectation_base.py b/qiskit/opflow/expectations/expectation_base.py index 593c43b83833..330e739f1439 100644 --- a/qiskit/opflow/expectations/expectation_base.py +++ b/qiskit/opflow/expectations/expectation_base.py @@ -40,6 +40,7 @@ class ExpectationBase(ConverterBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/expectations/expectation_factory.py b/qiskit/opflow/expectations/expectation_factory.py index 58a72ed6daca..68bf55143b63 100644 --- a/qiskit/opflow/expectations/expectation_factory.py +++ b/qiskit/opflow/expectations/expectation_factory.py @@ -38,6 +38,7 @@ class ExpectationFactory: @staticmethod @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def build( diff --git a/qiskit/opflow/expectations/matrix_expectation.py b/qiskit/opflow/expectations/matrix_expectation.py index 5265eff6e59d..53c1c8912152 100644 --- a/qiskit/opflow/expectations/matrix_expectation.py +++ b/qiskit/opflow/expectations/matrix_expectation.py @@ -27,6 +27,7 @@ class MatrixExpectation(ExpectationBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/expectations/pauli_expectation.py b/qiskit/opflow/expectations/pauli_expectation.py index 7c8d9697db16..bc297afccce6 100644 --- a/qiskit/opflow/expectations/pauli_expectation.py +++ b/qiskit/opflow/expectations/pauli_expectation.py @@ -44,6 +44,7 @@ class PauliExpectation(ExpectationBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, group_paulis: bool = True) -> None: diff --git a/qiskit/opflow/gradients/circuit_gradients/circuit_gradient.py b/qiskit/opflow/gradients/circuit_gradients/circuit_gradient.py index 284cd7da7eb7..52821d7ef1f9 100644 --- a/qiskit/opflow/gradients/circuit_gradients/circuit_gradient.py +++ b/qiskit/opflow/gradients/circuit_gradients/circuit_gradient.py @@ -38,6 +38,7 @@ class CircuitGradient(ConverterBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py b/qiskit/opflow/gradients/circuit_gradients/lin_comb.py index 1e06512e763e..cd658837331c 100644 --- a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py +++ b/qiskit/opflow/gradients/circuit_gradients/lin_comb.py @@ -103,6 +103,7 @@ class LinComb(CircuitGradient): # pylint: disable=signature-differs, arguments-differ @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, aux_meas_op: OperatorBase = Z): diff --git a/qiskit/opflow/gradients/circuit_gradients/param_shift.py b/qiskit/opflow/gradients/circuit_gradients/param_shift.py index 5329cc4c14c0..dcbb58052aa0 100644 --- a/qiskit/opflow/gradients/circuit_gradients/param_shift.py +++ b/qiskit/opflow/gradients/circuit_gradients/param_shift.py @@ -49,6 +49,7 @@ class ParamShift(CircuitGradient): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, analytic: bool = True, epsilon: float = 1e-6): diff --git a/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py b/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py index 9a11d619e6c7..034492aca523 100644 --- a/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py +++ b/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py @@ -38,6 +38,7 @@ class CircuitQFI(ConverterBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py b/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py index 71f4eea3d8c2..91793e03ac1b 100644 --- a/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py +++ b/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py @@ -38,6 +38,7 @@ class LinCombFull(CircuitQFI): # pylint: disable=signature-differs, arguments-differ @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py b/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py index 86b0bd1094cf..8e6e41dbd2f0 100644 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py +++ b/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py @@ -41,6 +41,7 @@ class OverlapBlockDiag(CircuitQFI): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py b/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py index 3f630fe304b9..b579c1930713 100644 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py +++ b/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py @@ -40,6 +40,7 @@ class OverlapDiag(CircuitQFI): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/gradients/derivative_base.py b/qiskit/opflow/gradients/derivative_base.py index 9ffbc6f0cdf0..4ba0ffed2418 100644 --- a/qiskit/opflow/gradients/derivative_base.py +++ b/qiskit/opflow/gradients/derivative_base.py @@ -49,6 +49,7 @@ class DerivativeBase(ConverterBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: @@ -136,7 +137,9 @@ def gradient_fn(p_values): @staticmethod @deprecate_func( - since="0.18.0", additional_msg="Instead, use the ParameterExpression.gradient method." + since="0.18.0", + package_name="qiskit-terra", + additional_msg="Instead, use the ParameterExpression.gradient method.", ) def parameter_expression_grad( param_expr: ParameterExpression, param: ParameterExpression diff --git a/qiskit/opflow/gradients/gradient.py b/qiskit/opflow/gradients/gradient.py index f9762da93e41..045d4695621e 100644 --- a/qiskit/opflow/gradients/gradient.py +++ b/qiskit/opflow/gradients/gradient.py @@ -38,6 +38,7 @@ class Gradient(GradientBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, grad_method: Union[str, CircuitGradient] = "param_shift", **kwargs): diff --git a/qiskit/opflow/gradients/gradient_base.py b/qiskit/opflow/gradients/gradient_base.py index f8da278acc93..03b813dd5c09 100644 --- a/qiskit/opflow/gradients/gradient_base.py +++ b/qiskit/opflow/gradients/gradient_base.py @@ -27,6 +27,7 @@ class GradientBase(DerivativeBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, grad_method: Union[str, CircuitGradient] = "param_shift", **kwargs): diff --git a/qiskit/opflow/gradients/hessian.py b/qiskit/opflow/gradients/hessian.py index 3ec39f5674ca..68dc44863a44 100644 --- a/qiskit/opflow/gradients/hessian.py +++ b/qiskit/opflow/gradients/hessian.py @@ -41,6 +41,7 @@ class Hessian(HessianBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, hess_method: Union[str, CircuitGradient] = "param_shift", **kwargs): diff --git a/qiskit/opflow/gradients/hessian_base.py b/qiskit/opflow/gradients/hessian_base.py index 2230ec28d824..77f81ca1d6e0 100644 --- a/qiskit/opflow/gradients/hessian_base.py +++ b/qiskit/opflow/gradients/hessian_base.py @@ -24,6 +24,7 @@ class HessianBase(DerivativeBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, hess_method: Union[str, CircuitGradient] = "param_shift", **kwargs): diff --git a/qiskit/opflow/gradients/natural_gradient.py b/qiskit/opflow/gradients/natural_gradient.py index 39c6cb8f4b73..6a2edb5a108b 100644 --- a/qiskit/opflow/gradients/natural_gradient.py +++ b/qiskit/opflow/gradients/natural_gradient.py @@ -53,6 +53,7 @@ class NaturalGradient(GradientBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/gradients/qfi.py b/qiskit/opflow/gradients/qfi.py index b16cd3b3ecc5..ca90bc74165a 100644 --- a/qiskit/opflow/gradients/qfi.py +++ b/qiskit/opflow/gradients/qfi.py @@ -38,6 +38,7 @@ class QFI(QFIBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, qfi_method: Union[str, CircuitQFI] = "lin_comb_full"): diff --git a/qiskit/opflow/gradients/qfi_base.py b/qiskit/opflow/gradients/qfi_base.py index b09f9170dbad..b36606f3a440 100644 --- a/qiskit/opflow/gradients/qfi_base.py +++ b/qiskit/opflow/gradients/qfi_base.py @@ -32,6 +32,7 @@ class QFIBase(DerivativeBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, qfi_method: Union[str, CircuitQFI] = "lin_comb_full"): diff --git a/qiskit/opflow/list_ops/composed_op.py b/qiskit/opflow/list_ops/composed_op.py index d47a81fbd796..6f569e718ad9 100644 --- a/qiskit/opflow/list_ops/composed_op.py +++ b/qiskit/opflow/list_ops/composed_op.py @@ -36,6 +36,7 @@ class ComposedOp(ListOp): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/list_ops/list_op.py b/qiskit/opflow/list_ops/list_op.py index abf2d2326d0c..1047cc68fb92 100644 --- a/qiskit/opflow/list_ops/list_op.py +++ b/qiskit/opflow/list_ops/list_op.py @@ -55,6 +55,7 @@ class ListOp(OperatorBase): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/list_ops/summed_op.py b/qiskit/opflow/list_ops/summed_op.py index 52f7faa15083..625445e24318 100644 --- a/qiskit/opflow/list_ops/summed_op.py +++ b/qiskit/opflow/list_ops/summed_op.py @@ -33,6 +33,7 @@ class SummedOp(ListOp): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/list_ops/tensored_op.py b/qiskit/opflow/list_ops/tensored_op.py index e3eb30cfaa41..f3eff0a57f9d 100644 --- a/qiskit/opflow/list_ops/tensored_op.py +++ b/qiskit/opflow/list_ops/tensored_op.py @@ -34,6 +34,7 @@ class TensoredOp(ListOp): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/mixins/star_algebra.py b/qiskit/opflow/mixins/star_algebra.py index 642086d5ba04..57994a4cdd88 100644 --- a/qiskit/opflow/mixins/star_algebra.py +++ b/qiskit/opflow/mixins/star_algebra.py @@ -42,6 +42,7 @@ class StarAlgebraMixin(MultiplyMixin, ABC): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/mixins/tensor.py b/qiskit/opflow/mixins/tensor.py index 3535db439f09..a138bda6009f 100644 --- a/qiskit/opflow/mixins/tensor.py +++ b/qiskit/opflow/mixins/tensor.py @@ -30,6 +30,7 @@ class TensorMixin(ABC): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/operator_base.py b/qiskit/opflow/operator_base.py index e79741a5dac3..818e0f3d960e 100644 --- a/qiskit/opflow/operator_base.py +++ b/qiskit/opflow/operator_base.py @@ -48,6 +48,7 @@ class OperatorBase(StarAlgebraMixin, TensorMixin, ABC): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self) -> None: diff --git a/qiskit/opflow/operator_globals.py b/qiskit/opflow/operator_globals.py index ac0c624c7287..73e2303b3bfb 100644 --- a/qiskit/opflow/operator_globals.py +++ b/qiskit/opflow/operator_globals.py @@ -37,6 +37,7 @@ @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def make_immutable(obj): diff --git a/qiskit/opflow/primitive_ops/circuit_op.py b/qiskit/opflow/primitive_ops/circuit_op.py index 1a6104a4cac3..c063ffd63cf4 100644 --- a/qiskit/opflow/primitive_ops/circuit_op.py +++ b/qiskit/opflow/primitive_ops/circuit_op.py @@ -33,6 +33,7 @@ class CircuitOp(PrimitiveOp): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/primitive_ops/matrix_op.py b/qiskit/opflow/primitive_ops/matrix_op.py index 5afe0ac54570..c32ef4334268 100644 --- a/qiskit/opflow/primitive_ops/matrix_op.py +++ b/qiskit/opflow/primitive_ops/matrix_op.py @@ -38,6 +38,7 @@ class MatrixOp(PrimitiveOp): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/primitive_ops/pauli_op.py b/qiskit/opflow/primitive_ops/pauli_op.py index 623608754671..51ec601297b7 100644 --- a/qiskit/opflow/primitive_ops/pauli_op.py +++ b/qiskit/opflow/primitive_ops/pauli_op.py @@ -37,6 +37,7 @@ class PauliOp(PrimitiveOp): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__(self, primitive: Pauli, coeff: Union[complex, ParameterExpression] = 1.0) -> None: diff --git a/qiskit/opflow/primitive_ops/pauli_sum_op.py b/qiskit/opflow/primitive_ops/pauli_sum_op.py index b1bb9b7242a9..37815effb61a 100644 --- a/qiskit/opflow/primitive_ops/pauli_sum_op.py +++ b/qiskit/opflow/primitive_ops/pauli_sum_op.py @@ -36,6 +36,7 @@ class PauliSumOp(PrimitiveOp): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/primitive_ops/primitive_op.py b/qiskit/opflow/primitive_ops/primitive_op.py index de3ef06c3add..eb31a1fa461b 100644 --- a/qiskit/opflow/primitive_ops/primitive_op.py +++ b/qiskit/opflow/primitive_ops/primitive_op.py @@ -95,6 +95,7 @@ def __new__( @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py b/qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py index 9411afc71f26..f603a9fbb364 100644 --- a/qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py +++ b/qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py @@ -37,6 +37,7 @@ class TaperedPauliSumOp(PauliSumOp): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( @@ -90,6 +91,7 @@ class Z2Symmetries: @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/state_fns/circuit_state_fn.py b/qiskit/opflow/state_fns/circuit_state_fn.py index 3f23f89a1cee..6a4aa3a2b5e3 100644 --- a/qiskit/opflow/state_fns/circuit_state_fn.py +++ b/qiskit/opflow/state_fns/circuit_state_fn.py @@ -46,6 +46,7 @@ class CircuitStateFn(StateFn): # TODO allow normalization somehow? @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/state_fns/dict_state_fn.py b/qiskit/opflow/state_fns/dict_state_fn.py index 940de844748a..a1fb52acb486 100644 --- a/qiskit/opflow/state_fns/dict_state_fn.py +++ b/qiskit/opflow/state_fns/dict_state_fn.py @@ -41,6 +41,7 @@ class DictStateFn(StateFn): # TODO allow normalization somehow? @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/state_fns/operator_state_fn.py b/qiskit/opflow/state_fns/operator_state_fn.py index a9cac679deef..08f1b3c766b0 100644 --- a/qiskit/opflow/state_fns/operator_state_fn.py +++ b/qiskit/opflow/state_fns/operator_state_fn.py @@ -39,6 +39,7 @@ class OperatorStateFn(StateFn): # TODO allow normalization somehow? @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/state_fns/state_fn.py b/qiskit/opflow/state_fns/state_fn.py index f24592bdaf55..6f6575db4105 100644 --- a/qiskit/opflow/state_fns/state_fn.py +++ b/qiskit/opflow/state_fns/state_fn.py @@ -115,6 +115,7 @@ def __new__( # TODO allow normalization somehow? @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/state_fns/vector_state_fn.py b/qiskit/opflow/state_fns/vector_state_fn.py index 711d505612ca..067070a4f05c 100644 --- a/qiskit/opflow/state_fns/vector_state_fn.py +++ b/qiskit/opflow/state_fns/vector_state_fn.py @@ -39,6 +39,7 @@ class VectorStateFn(StateFn): # TODO allow normalization somehow? @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def __init__( diff --git a/qiskit/opflow/utils.py b/qiskit/opflow/utils.py index 0979fc0bc3e7..e766d4f57e32 100644 --- a/qiskit/opflow/utils.py +++ b/qiskit/opflow/utils.py @@ -18,6 +18,7 @@ @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def commutator(op_a: OperatorBase, op_b: OperatorBase) -> OperatorBase: @@ -39,6 +40,7 @@ def commutator(op_a: OperatorBase, op_b: OperatorBase) -> OperatorBase: @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def anti_commutator(op_a: OperatorBase, op_b: OperatorBase) -> OperatorBase: @@ -60,6 +62,7 @@ def anti_commutator(op_a: OperatorBase, op_b: OperatorBase) -> OperatorBase: @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", ) def double_commutator( diff --git a/qiskit/passmanager/base_tasks.py b/qiskit/passmanager/base_tasks.py index 84a944d78042..64ebafa225da 100644 --- a/qiskit/passmanager/base_tasks.py +++ b/qiskit/passmanager/base_tasks.py @@ -19,7 +19,7 @@ from collections.abc import Iterable, Callable, Generator from typing import Any -from .compilation_status import RunState, PassManagerState +from .compilation_status import RunState, PassManagerState, PropertySet logger = logging.getLogger(__name__) @@ -62,6 +62,7 @@ class GenericPass(Task, ABC): """ def __init__(self): + self.property_set = PropertySet() self.requires: Iterable[Task] = [] def name(self) -> str: @@ -77,6 +78,7 @@ def execute( # Overriding this method is not safe. # Pass subclass must keep current implementation. # Especially, task execution may break when method signature is modified. + self.property_set = state.property_set if self.requires: # pylint: disable=cyclic-import diff --git a/qiskit/passmanager/flow_controllers.py b/qiskit/passmanager/flow_controllers.py index bf831b94b35c..eb06202e8673 100644 --- a/qiskit/passmanager/flow_controllers.py +++ b/qiskit/passmanager/flow_controllers.py @@ -133,6 +133,8 @@ def iter_tasks(self, state: PassManagerState) -> Generator[Task, PassManagerStat state = yield task if not self.do_while(state.property_set): return + # Remove stored tasks from the completed task collection for next loop + state.workflow_status.completed_passes.difference_update(self.tasks) raise PassManagerError("Maximum iteration reached. max_iteration=%i" % max_iteration) diff --git a/qiskit/passmanager/passmanager.py b/qiskit/passmanager/passmanager.py index fb9c8b523ba6..74d5feb91088 100644 --- a/qiskit/passmanager/passmanager.py +++ b/qiskit/passmanager/passmanager.py @@ -15,7 +15,7 @@ import logging from abc import ABC, abstractmethod -from collections.abc import Callable, Sequence, Iterable +from collections.abc import Callable, Iterable from itertools import chain from typing import Any @@ -169,14 +169,16 @@ def _passmanager_backend( def run( self, - in_programs: Any, + in_programs: Any | list[Any], callback: Callable = None, **kwargs, ) -> Any: - """Run all the passes on the specified ``circuits``. + """Run all the passes on the specified ``in_programs``. Args: in_programs: Input programs to transform via all the registered passes. + A single input object cannot be a Python builtin list object. + A list object is considered as multiple input objects to optimize. callback: A callback function that will be called after each pass execution. The function will be called with 4 keyword arguments:: @@ -212,7 +214,7 @@ def callback_func(**kwargs): return in_programs is_list = True - if not isinstance(in_programs, Sequence): + if not isinstance(in_programs, list): in_programs = [in_programs] is_list = False diff --git a/qiskit/primitives/backend_estimator.py b/qiskit/primitives/backend_estimator.py index 2ffe5bb7a989..8f62e4797c2f 100644 --- a/qiskit/primitives/backend_estimator.py +++ b/qiskit/primitives/backend_estimator.py @@ -98,8 +98,7 @@ class BackendEstimator(BaseEstimator[PrimitiveJob[EstimatorResult]]): (or :class:`~.BackendV1`) object in the :class:`~.BaseEstimator` API. It facilitates using backends that do not provide a native :class:`~.BaseEstimator` implementation in places that work with - :class:`~.BaseEstimator`, such as algorithms in :mod:`qiskit.algorithms` - including :class:`~.qiskit.algorithms.minimum_eigensolvers.VQE`. However, + :class:`~.BaseEstimator`. However, if you're using a provider that has a native implementation of :class:`~.BaseEstimator`, it is a better choice to leverage that native implementation as it will likely include additional optimizations and be @@ -129,6 +128,9 @@ def __init__( will be directly executed when this object is called. """ super().__init__(options=options) + self._circuits = [] + self._parameters = [] + self._observables = [] self._abelian_grouping = abelian_grouping @@ -198,25 +200,18 @@ def _transpile(self): # 1. transpile a common circuit if self._skip_transpilation: transpiled_circuit = common_circuit.copy() - perm_pattern = list(range(common_circuit.num_qubits)) + final_index_layout = list(range(common_circuit.num_qubits)) else: transpiled_circuit = transpile( common_circuit, self.backend, **self.transpile_options.__dict__ ) if transpiled_circuit.layout is not None: - layout = transpiled_circuit.layout - virtual_bit_map = layout.initial_layout.get_virtual_bits() - perm_pattern = [virtual_bit_map[v] for v in common_circuit.qubits] - if layout.final_layout is not None: - final_mapping = dict( - enumerate(layout.final_layout.get_virtual_bits().values()) - ) - perm_pattern = [final_mapping[i] for i in perm_pattern] + final_index_layout = transpiled_circuit.layout.final_index_layout() else: - perm_pattern = list(range(transpiled_circuit.num_qubits)) + final_index_layout = list(range(transpiled_circuit.num_qubits)) # 2. transpile diff circuits - passmanager = _passmanager_for_measurement_circuits(perm_pattern, self.backend) + passmanager = _passmanager_for_measurement_circuits(final_index_layout, self.backend) diff_circuits = passmanager.run(diff_circuits) # 3. combine transpiled_circuits = [] diff --git a/qiskit/primitives/backend_sampler.py b/qiskit/primitives/backend_sampler.py index 6510b981b68a..140a3091f34a 100644 --- a/qiskit/primitives/backend_sampler.py +++ b/qiskit/primitives/backend_sampler.py @@ -38,8 +38,7 @@ class BackendSampler(BaseSampler[PrimitiveJob[SamplerResult]]): any measurement mitigation, it just computes the probability distribution from the counts. It facilitates using backends that do not provide a native :class:`~.BaseSampler` implementation in places that work with - :class:`~.BaseSampler`, such as algorithms in :mod:`qiskit.algorithms` - including :class:`~.qiskit.algorithms.minimum_eigensolvers.SamplingVQE`. + :class:`~.BaseSampler`. However, if you're using a provider that has a native implementation of :class:`~.BaseSampler`, it is a better choice to leverage that native implementation as it will likely include additional optimizations and be @@ -69,6 +68,8 @@ def __init__( """ super().__init__(options=options) + self._circuits = [] + self._parameters = [] self._backend = backend self._transpile_options = Options() self._bound_pass_manager = bound_pass_manager diff --git a/qiskit/primitives/base/base_estimator.py b/qiskit/primitives/base/base_estimator.py index 1dec010d7b12..b789863a812d 100644 --- a/qiskit/primitives/base/base_estimator.py +++ b/qiskit/primitives/base/base_estimator.py @@ -80,20 +80,22 @@ from __future__ import annotations +import warnings from abc import abstractmethod from collections.abc import Sequence from copy import copy from typing import Generic, TypeVar import typing +from qiskit.utils.deprecation import deprecate_func from qiskit.circuit import QuantumCircuit from qiskit.circuit.parametertable import ParameterView from qiskit.providers import JobV1 as Job from qiskit.quantum_info.operators import SparsePauliOp from qiskit.quantum_info.operators.base_operator import BaseOperator -from ..utils import init_observable from .base_primitive import BasePrimitive +from . import validation if typing.TYPE_CHECKING: from qiskit.opflow import PauliSumOp @@ -121,11 +123,29 @@ def __init__( Args: options: Default options. """ - self._circuits = [] - self._observables = [] - self._parameters = [] super().__init__(options) + def __getattr__(self, name: str) -> any: + # Work around to enable deprecation of the init attributes in BaseEstimator incase + # existing subclasses depend on them (which some do) + dep_defaults = { + "_circuits": [], + "_observables": [], + "_parameters": [], + } + if name not in dep_defaults: + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") + + warnings.warn( + f"The init attribute `{name}` in BaseEstimator is deprecated as of Qiskit 0.46." + " To continue to use this attribute in a subclass and avoid this warning the" + " subclass should initialize it itself.", + DeprecationWarning, + stacklevel=2, + ) + setattr(self, name, dep_defaults[name]) + return getattr(self, name) + def run( self, circuits: Sequence[QuantumCircuit] | QuantumCircuit, @@ -169,18 +189,11 @@ def run( TypeError: Invalid argument type given. ValueError: Invalid argument values given. """ - # Singular validation - circuits = self._validate_circuits(circuits) - observables = self._validate_observables(observables) - parameter_values = self._validate_parameter_values( - parameter_values, - default=[()] * len(circuits), + # Validation + circuits, observables, parameter_values = validation._validate_estimator_args( + circuits, observables, parameter_values ) - # Cross-validation - self._cross_validate_circuits_parameter_values(circuits, parameter_values) - self._cross_validate_circuits_observables(circuits, observables) - # Options run_opts = copy(self.options) run_opts.update_options(**run_options) @@ -200,36 +213,24 @@ def _run( parameter_values: tuple[tuple[float, ...], ...], **run_options, ) -> T: - raise NotImplementedError("The subclass of BaseEstimator must implment `_run` method.") + raise NotImplementedError("The subclass of BaseEstimator must implement `_run` method.") @staticmethod + @deprecate_func(since="0.46.0") def _validate_observables( observables: Sequence[BaseOperator | PauliSumOp | str] | BaseOperator | PauliSumOp | str, ) -> tuple[SparsePauliOp, ...]: - if isinstance(observables, str) or not isinstance(observables, Sequence): - observables = (observables,) - if len(observables) == 0: - raise ValueError("No observables were provided.") - return tuple(init_observable(obs) for obs in observables) + return validation._validate_observables(observables) @staticmethod + @deprecate_func(since="0.46.0") def _cross_validate_circuits_observables( circuits: tuple[QuantumCircuit, ...], observables: tuple[BaseOperator | PauliSumOp, ...] ) -> None: - if len(circuits) != len(observables): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of observables ({len(observables)})." - ) - for i, (circuit, observable) in enumerate(zip(circuits, observables)): - if circuit.num_qubits != observable.num_qubits: - raise ValueError( - f"The number of qubits of the {i}-th circuit ({circuit.num_qubits}) does " - f"not match the number of qubits of the {i}-th observable " - f"({observable.num_qubits})." - ) + return validation._cross_validate_circuits_observables(circuits, observables) @property + @deprecate_func(since="0.46.0", is_property=True) def circuits(self) -> tuple[QuantumCircuit, ...]: """Quantum circuits that represents quantum states. @@ -239,6 +240,7 @@ def circuits(self) -> tuple[QuantumCircuit, ...]: return tuple(self._circuits) @property + @deprecate_func(since="0.46.0", is_property=True) def observables(self) -> tuple[SparsePauliOp, ...]: """Observables to be estimated. @@ -248,6 +250,7 @@ def observables(self) -> tuple[SparsePauliOp, ...]: return tuple(self._observables) @property + @deprecate_func(since="0.46.0", is_property=True) def parameters(self) -> tuple[ParameterView, ...]: """Parameters of the quantum circuits. diff --git a/qiskit/primitives/base/base_primitive.py b/qiskit/primitives/base/base_primitive.py index a2f8dacdb828..c161ca8094fa 100644 --- a/qiskit/primitives/base/base_primitive.py +++ b/qiskit/primitives/base/base_primitive.py @@ -17,10 +17,11 @@ from abc import ABC from collections.abc import Sequence -import numpy as np - from qiskit.circuit import QuantumCircuit from qiskit.providers import Options +from qiskit.utils.deprecation import deprecate_func + +from . import validation class BasePrimitive(ABC): @@ -49,83 +50,25 @@ def set_options(self, **fields): self._run_options.update_options(**fields) @staticmethod + @deprecate_func(since="0.46.0") def _validate_circuits( circuits: Sequence[QuantumCircuit] | QuantumCircuit, ) -> tuple[QuantumCircuit, ...]: - if isinstance(circuits, QuantumCircuit): - circuits = (circuits,) - elif not isinstance(circuits, Sequence) or not all( - isinstance(cir, QuantumCircuit) for cir in circuits - ): - raise TypeError("Invalid circuits, expected Sequence[QuantumCircuit].") - elif not isinstance(circuits, tuple): - circuits = tuple(circuits) - if len(circuits) == 0: - raise ValueError("No circuits were provided.") - return circuits + return validation._validate_circuits(circuits) @staticmethod + @deprecate_func(since="0.46.0") def _validate_parameter_values( parameter_values: Sequence[Sequence[float]] | Sequence[float] | float | None, default: Sequence[Sequence[float]] | Sequence[float] | None = None, ) -> tuple[tuple[float, ...], ...]: - # Allow optional (if default) - if parameter_values is None: - if default is None: - raise ValueError("No default `parameter_values`, optional input disallowed.") - parameter_values = default - - # Support numpy ndarray - if isinstance(parameter_values, np.ndarray): - parameter_values = parameter_values.tolist() - elif isinstance(parameter_values, Sequence): - parameter_values = tuple( - vector.tolist() if isinstance(vector, np.ndarray) else vector - for vector in parameter_values - ) - - # Allow single value - if _isreal(parameter_values): - parameter_values = ((parameter_values,),) - elif isinstance(parameter_values, Sequence) and not any( - isinstance(vector, Sequence) for vector in parameter_values - ): - parameter_values = (parameter_values,) - - # Validation - if ( - not isinstance(parameter_values, Sequence) - or not all(isinstance(vector, Sequence) for vector in parameter_values) - or not all(all(_isreal(value) for value in vector) for vector in parameter_values) - ): - raise TypeError("Invalid parameter values, expected Sequence[Sequence[float]].") - - return tuple(tuple(float(value) for value in vector) for vector in parameter_values) + return validation._validate_parameter_values(parameter_values, default=default) @staticmethod + @deprecate_func(since="0.46.0") def _cross_validate_circuits_parameter_values( circuits: tuple[QuantumCircuit, ...], parameter_values: tuple[tuple[float, ...], ...] ) -> None: - if len(circuits) != len(parameter_values): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of parameter value sets ({len(parameter_values)})." - ) - for i, (circuit, vector) in enumerate(zip(circuits, parameter_values)): - if len(vector) != circuit.num_parameters: - raise ValueError( - f"The number of values ({len(vector)}) does not match " - f"the number of parameters ({circuit.num_parameters}) for the {i}-th circuit." - ) - - -def _isint(obj: Sequence[Sequence[float]] | Sequence[float] | float) -> bool: - """Check if object is int.""" - int_types = (int, np.integer) - return isinstance(obj, int_types) and not isinstance(obj, bool) - - -def _isreal(obj: Sequence[Sequence[float]] | Sequence[float] | float) -> bool: - """Check if object is a real number: int or float except ``±Inf`` and ``NaN``.""" - float_types = (float, np.floating) - return _isint(obj) or isinstance(obj, float_types) and float("-Inf") < obj < float("Inf") + return validation._cross_validate_circuits_parameter_values( + circuits, parameter_values=parameter_values + ) diff --git a/qiskit/primitives/base/base_sampler.py b/qiskit/primitives/base/base_sampler.py index fb6cf557ca59..d21487261091 100644 --- a/qiskit/primitives/base/base_sampler.py +++ b/qiskit/primitives/base/base_sampler.py @@ -75,16 +75,19 @@ from __future__ import annotations +import warnings from abc import abstractmethod from collections.abc import Sequence from copy import copy from typing import Generic, TypeVar -from qiskit.circuit import ControlFlowOp, Measure, QuantumCircuit +from qiskit.utils.deprecation import deprecate_func +from qiskit.circuit import QuantumCircuit from qiskit.circuit.parametertable import ParameterView from qiskit.providers import JobV1 as Job from .base_primitive import BasePrimitive +from . import validation T = TypeVar("T", bound=Job) @@ -106,10 +109,28 @@ def __init__( Args: options: Default options. """ - self._circuits = [] - self._parameters = [] super().__init__(options) + def __getattr__(self, name: str) -> any: + # Work around to enable deprecation of the init attributes in BaseSampler incase + # existing subclasses depend on them (which some do) + dep_defaults = { + "_circuits": [], + "_parameters": [], + } + if name not in dep_defaults: + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") + + warnings.warn( + f"The init attribute `{name}` in BaseSampler is deprecated as of Qiskit 0.46." + " To continue to use this attribute in a subclass and avoid this warning the" + " subclass should initialize it itself.", + DeprecationWarning, + stacklevel=2, + ) + setattr(self, name, dep_defaults[name]) + return getattr(self, name) + def run( self, circuits: QuantumCircuit | Sequence[QuantumCircuit], @@ -130,15 +151,8 @@ def run( Raises: ValueError: Invalid arguments are given. """ - # Singular validation - circuits = self._validate_circuits(circuits) - parameter_values = self._validate_parameter_values( - parameter_values, - default=[()] * len(circuits), - ) - - # Cross-validation - self._cross_validate_circuits_parameter_values(circuits, parameter_values) + # Validation + circuits, parameter_values = validation._validate_sampler_args(circuits, parameter_values) # Options run_opts = copy(self.options) @@ -157,29 +171,18 @@ def _run( parameter_values: tuple[tuple[float, ...], ...], **run_options, ) -> T: - raise NotImplementedError("The subclass of BaseSampler must implment `_run` method.") + raise NotImplementedError("The subclass of BaseSampler must implement `_run` method.") @classmethod + @deprecate_func(since="0.46.0") def _validate_circuits( cls, circuits: Sequence[QuantumCircuit] | QuantumCircuit, ) -> tuple[QuantumCircuit, ...]: - circuits = super()._validate_circuits(circuits) - for i, circuit in enumerate(circuits): - if circuit.num_clbits == 0: - raise ValueError( - f"The {i}-th circuit does not have any classical bit. " - "Sampler requires classical bits, plus measurements " - "on the desired qubits." - ) - if not _has_measure(circuit): - raise ValueError( - f"The {i}-th circuit does not have Measure instruction. " - "Without measurements, the circuit cannot be sampled from." - ) - return circuits + return validation._validate_circuits(circuits, requires_measure=True) @property + @deprecate_func(since="0.46.0", is_property=True) def circuits(self) -> tuple[QuantumCircuit, ...]: """Quantum circuits to be sampled. @@ -189,6 +192,7 @@ def circuits(self) -> tuple[QuantumCircuit, ...]: return tuple(self._circuits) @property + @deprecate_func(since="0.46.0", is_property=True) def parameters(self) -> tuple[ParameterView, ...]: """Parameters of quantum circuits. @@ -196,14 +200,3 @@ def parameters(self) -> tuple[ParameterView, ...]: List of the parameters in each quantum circuit. """ return tuple(self._parameters) - - -def _has_measure(circuit: QuantumCircuit) -> bool: - for instruction in reversed(circuit): - if isinstance(instruction.operation, Measure): - return True - elif isinstance(instruction.operation, ControlFlowOp): - for block in instruction.operation.blocks: - if _has_measure(block): - return True - return False diff --git a/qiskit/primitives/base/validation.py b/qiskit/primitives/base/validation.py new file mode 100644 index 000000000000..7e1bccf2f89c --- /dev/null +++ b/qiskit/primitives/base/validation.py @@ -0,0 +1,231 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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. + +"""Primitive validation methods. + +Note that these are not intended to be part of the public API of base primitives +but are here for backwards compatibility with deprecated functions. +""" + +from __future__ import annotations + +from collections.abc import Sequence +import typing +import numpy as np + +from qiskit.circuit import QuantumCircuit, ControlFlowOp, Measure +from qiskit.quantum_info.operators import SparsePauliOp +from qiskit.quantum_info.operators.base_operator import BaseOperator + +from ..utils import init_observable + +if typing.TYPE_CHECKING: + from qiskit.opflow import PauliSumOp + + +def _validate_estimator_args( + circuits: Sequence[QuantumCircuit] | QuantumCircuit, + observables: Sequence[BaseOperator | PauliSumOp | str] | BaseOperator | PauliSumOp | str, + parameter_values: Sequence[Sequence[float]] | Sequence[float] | float | None = None, +) -> tuple[tuple[QuantumCircuit], tuple[BaseOperator], tuple[tuple[float]]]: + """Validate run arguments for a reference Estimator. + + Args: + circuits: one or more circuit objects. + observables: one or more observable objects. + parameter_values: concrete parameters to be bound. + + Returns: + The formatted arguments ``(circuits, observables, parameter_values)``. + + Raises: + TypeError: If input arguments are invalid types. + ValueError: if input arguments are invalid values. + """ + # Singular validation + circuits = _validate_circuits(circuits) + observables = _validate_observables(observables) + parameter_values = _validate_parameter_values( + parameter_values, + default=[()] * len(circuits), + ) + + # Cross-validation + _cross_validate_circuits_parameter_values(circuits, parameter_values) + _cross_validate_circuits_observables(circuits, observables) + + return circuits, observables, parameter_values + + +def _validate_sampler_args( + circuits: Sequence[QuantumCircuit] | QuantumCircuit, + parameter_values: Sequence[Sequence[float]] | Sequence[float] | float | None = None, +) -> tuple[tuple[QuantumCircuit], tuple[BaseOperator], tuple[tuple[float]]]: + """Validate run arguments for a reference Sampler. + + Args: + circuits: one or more circuit objects. + parameter_values: concrete parameters to be bound. + + Returns: + The formatted arguments ``(circuits, parameter_values)``. + + Raises: + TypeError: If input arguments are invalid types. + ValueError: if input arguments are invalid values. + """ + # Singular validation + circuits = _validate_circuits(circuits, requires_measure=True) + parameter_values = _validate_parameter_values( + parameter_values, + default=[()] * len(circuits), + ) + + # Cross-validation + _cross_validate_circuits_parameter_values(circuits, parameter_values) + + return circuits, parameter_values + + +def _validate_circuits( + circuits: Sequence[QuantumCircuit] | QuantumCircuit, + requires_measure: bool = False, +) -> tuple[QuantumCircuit, ...]: + if isinstance(circuits, QuantumCircuit): + circuits = (circuits,) + elif not isinstance(circuits, Sequence) or not all( + isinstance(cir, QuantumCircuit) for cir in circuits + ): + raise TypeError("Invalid circuits, expected Sequence[QuantumCircuit].") + elif not isinstance(circuits, tuple): + circuits = tuple(circuits) + if len(circuits) == 0: + raise ValueError("No circuits were provided.") + + if requires_measure: + for i, circuit in enumerate(circuits): + if circuit.num_clbits == 0: + raise ValueError( + f"The {i}-th circuit does not have any classical bit. " + "Sampler requires classical bits, plus measurements " + "on the desired qubits." + ) + if not _has_measure(circuit): + raise ValueError( + f"The {i}-th circuit does not have Measure instruction. " + "Without measurements, the circuit cannot be sampled from." + ) + return circuits + + +def _validate_parameter_values( + parameter_values: Sequence[Sequence[float]] | Sequence[float] | float | None, + default: Sequence[Sequence[float]] | Sequence[float] | None = None, +) -> tuple[tuple[float, ...], ...]: + # Allow optional (if default) + if parameter_values is None: + if default is None: + raise ValueError("No default `parameter_values`, optional input disallowed.") + parameter_values = default + + # Support numpy ndarray + if isinstance(parameter_values, np.ndarray): + parameter_values = parameter_values.tolist() + elif isinstance(parameter_values, Sequence): + parameter_values = tuple( + vector.tolist() if isinstance(vector, np.ndarray) else vector + for vector in parameter_values + ) + + # Allow single value + if _isreal(parameter_values): + parameter_values = ((parameter_values,),) + elif isinstance(parameter_values, Sequence) and not any( + isinstance(vector, Sequence) for vector in parameter_values + ): + parameter_values = (parameter_values,) + + # Validation + if ( + not isinstance(parameter_values, Sequence) + or not all(isinstance(vector, Sequence) for vector in parameter_values) + or not all(all(_isreal(value) for value in vector) for vector in parameter_values) + ): + raise TypeError("Invalid parameter values, expected Sequence[Sequence[float]].") + + return tuple(tuple(float(value) for value in vector) for vector in parameter_values) + + +def _validate_observables( + observables: Sequence[BaseOperator | PauliSumOp | str] | BaseOperator | PauliSumOp | str, +) -> tuple[SparsePauliOp, ...]: + if isinstance(observables, str) or not isinstance(observables, Sequence): + observables = (observables,) + if len(observables) == 0: + raise ValueError("No observables were provided.") + return tuple(init_observable(obs) for obs in observables) + + +def _cross_validate_circuits_parameter_values( + circuits: tuple[QuantumCircuit, ...], parameter_values: tuple[tuple[float, ...], ...] +) -> None: + if len(circuits) != len(parameter_values): + raise ValueError( + f"The number of circuits ({len(circuits)}) does not match " + f"the number of parameter value sets ({len(parameter_values)})." + ) + for i, (circuit, vector) in enumerate(zip(circuits, parameter_values)): + if len(vector) != circuit.num_parameters: + raise ValueError( + f"The number of values ({len(vector)}) does not match " + f"the number of parameters ({circuit.num_parameters}) for the {i}-th circuit." + ) + + +def _cross_validate_circuits_observables( + circuits: tuple[QuantumCircuit, ...], observables: tuple[BaseOperator | PauliSumOp, ...] +) -> None: + if len(circuits) != len(observables): + raise ValueError( + f"The number of circuits ({len(circuits)}) does not match " + f"the number of observables ({len(observables)})." + ) + for i, (circuit, observable) in enumerate(zip(circuits, observables)): + if circuit.num_qubits != observable.num_qubits: + raise ValueError( + f"The number of qubits of the {i}-th circuit ({circuit.num_qubits}) does " + f"not match the number of qubits of the {i}-th observable " + f"({observable.num_qubits})." + ) + + +def _isint(obj: Sequence[Sequence[float]] | Sequence[float] | float) -> bool: + """Check if object is int.""" + int_types = (int, np.integer) + return isinstance(obj, int_types) and not isinstance(obj, bool) + + +def _isreal(obj: Sequence[Sequence[float]] | Sequence[float] | float) -> bool: + """Check if object is a real number: int or float except ``±Inf`` and ``NaN``.""" + float_types = (float, np.floating) + return _isint(obj) or isinstance(obj, float_types) and float("-Inf") < obj < float("Inf") + + +def _has_measure(circuit: QuantumCircuit) -> bool: + for instruction in reversed(circuit): + if isinstance(instruction.operation, Measure): + return True + elif isinstance(instruction.operation, ControlFlowOp): + for block in instruction.operation.blocks: + if _has_measure(block): + return True + return False diff --git a/qiskit/primitives/estimator.py b/qiskit/primitives/estimator.py index 52e519e56c29..69f36e1723f1 100644 --- a/qiskit/primitives/estimator.py +++ b/qiskit/primitives/estimator.py @@ -64,6 +64,9 @@ def __init__(self, *, options: dict | None = None): QiskitError: if some classical bits are not used for measurements. """ super().__init__(options=options) + self._circuits = [] + self._parameters = [] + self._observables = [] self._circuit_ids = {} self._observable_ids = {} diff --git a/qiskit/primitives/sampler.py b/qiskit/primitives/sampler.py index c96f7b886c1c..9ffe42165d96 100644 --- a/qiskit/primitives/sampler.py +++ b/qiskit/primitives/sampler.py @@ -61,6 +61,8 @@ def __init__(self, *, options: dict | None = None): QiskitError: if some classical bits are not used for measurements. """ super().__init__(options=options) + self._circuits = [] + self._parameters = [] self._qargs_list = [] self._circuit_ids = {} diff --git a/qiskit/primitives/utils.py b/qiskit/primitives/utils.py index 611fbe86662c..c2f915c8f0da 100644 --- a/qiskit/primitives/utils.py +++ b/qiskit/primitives/utils.py @@ -14,6 +14,7 @@ """ from __future__ import annotations +import warnings import sys import typing from collections.abc import Iterable @@ -23,7 +24,7 @@ from qiskit.circuit import Instruction, ParameterExpression, QuantumCircuit from qiskit.circuit.bit import Bit from qiskit.circuit.library.data_preparation import Initialize -from qiskit.quantum_info import SparsePauliOp, Statevector +from qiskit.quantum_info import SparsePauliOp, Statevector, PauliList from qiskit.quantum_info.operators.base_operator import BaseOperator from qiskit.quantum_info.operators.symplectic.base_pauli import BasePauli @@ -82,6 +83,15 @@ def init_observable(observable: BaseOperator | PauliSumOp | str) -> SparsePauliO elif isinstance(observable, BaseOperator) and not isinstance(observable, BasePauli): return SparsePauliOp.from_operator(observable) else: + if isinstance(observable, PauliList): + warnings.warn( + "Implicit conversion from a PauliList to a SparsePauliOp with coeffs=1 in" + " estimator observable arguments is deprecated as of Qiskit 0.46 and will be" + " in Qiskit 1.0. You should explicitly convert to a SparsePauli op using" + " SparsePauliOp(pauli_list) to avoid this warning.", + DeprecationWarning, + stacklevel=2, + ) return SparsePauliOp(observable) diff --git a/qiskit/providers/__init__.py b/qiskit/providers/__init__.py index d7ec4b21b9fe..d7a52193dd15 100644 --- a/qiskit/providers/__init__.py +++ b/qiskit/providers/__init__.py @@ -144,8 +144,8 @@ backend. It also provides the :meth:`~qiskit.providers.BackendV2.run` method which can run the :class:`~qiskit.circuit.QuantumCircuit` objects and/or :class:`~qiskit.pulse.Schedule` objects. This enables users and other Qiskit -APIs, such as :func:`~qiskit.execute_function.execute` and higher level algorithms in -:mod:`qiskit.algorithms`, to get results from executing circuits on devices in a standard +APIs, such as :func:`~qiskit.execute_function.execute` to get results from +executing circuits on devices in a standard fashion regardless of how the backend is implemented. At a high level the basic steps for writing a provider are: @@ -635,7 +635,7 @@ def status(self): provider-specific :class:`~.Sampler` implementation that leverages the ``M3Mitigation`` class internally to run the circuits and return quasi-probabilities directly from mthree in the result. Doing this would -enable algorithms from :mod:`qiskit.algorithms` to get the best results with +enable algorithms to get the best results with mitigation applied directly from your backends. You can refer to the documentation in :mod:`qiskit.primitives` on how to write custom implementations. Also the built-in implementations: :class:`~.Sampler`, @@ -750,8 +750,6 @@ def status(self): to wrap a :class:`~.BackendV1` object with a :class:`~.BackendV2` interface. """ -import pkgutil - # Providers interface from qiskit.providers.provider import Provider from qiskit.providers.provider import ProviderV1 @@ -773,8 +771,3 @@ def status(self): BackendConfigurationError, ) from qiskit.providers.jobstatus import JobStatus - - -# Support for the deprecated extending this namespace. -# Remove this after 0.46.0 release -__path__ = pkgutil.extend_path(__path__, __name__) diff --git a/qiskit/pulse/__init__.py b/qiskit/pulse/__init__.py index 34ef5c072020..c946fafa80da 100644 --- a/qiskit/pulse/__init__.py +++ b/qiskit/pulse/__init__.py @@ -40,6 +40,8 @@ .. automodule:: qiskit.pulse.schedule .. automodule:: qiskit.pulse.transforms .. automodule:: qiskit.pulse.builder +.. automodule:: qiskit.pulse.model + .. currentmodule:: qiskit.pulse @@ -168,3 +170,16 @@ ) from qiskit.pulse.library.samplers.decorators import functional_pulse from qiskit.pulse.schedule import Schedule, ScheduleBlock + +from qiskit.pulse.model import ( + PulseTarget, + Port, + LogicalElement, + Qubit, + Coupler, + Frame, + GenericFrame, + QubitFrame, + MeasurementFrame, + MixedFrame, +) diff --git a/qiskit/pulse/instructions/call.py b/qiskit/pulse/instructions/call.py index 49c9a6fe42c0..d38e1355881e 100644 --- a/qiskit/pulse/instructions/call.py +++ b/qiskit/pulse/instructions/call.py @@ -33,6 +33,7 @@ class Call(instruction.Instruction): @deprecate_func( since="0.25.0", + package_name="qiskit-terra", additional_msg="Instead, use the pulse builder function " "qiskit.pulse.builder.call(subroutine) within an active building context.", ) diff --git a/qiskit/pulse/instructions/instruction.py b/qiskit/pulse/instructions/instruction.py index 3a1764f817f6..7c6af5788e8b 100644 --- a/qiskit/pulse/instructions/instruction.py +++ b/qiskit/pulse/instructions/instruction.py @@ -230,6 +230,7 @@ def is_parameterized(self) -> bool: "``qiskit.visualization.pulse_drawer``." ), since="0.23.0", + package_name="qiskit-terra", ) @_optionals.HAS_MATPLOTLIB.require_in_call def draw( diff --git a/qiskit/pulse/library/discrete.py b/qiskit/pulse/library/discrete.py index 046944471270..15e79fd14458 100644 --- a/qiskit/pulse/library/discrete.py +++ b/qiskit/pulse/library/discrete.py @@ -28,14 +28,14 @@ @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including constant() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including constant() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.Constant(...).get_waveform(). " - " Note that complex value support for the `amp` parameter is pending deprecation" - " in the SymbolicPulse library. It is therefore recommended to use two float values" + " Note that complex value support for the `amp` parameter is deprecated" + " in the SymbolicPulse library. Use two float values" " for (`amp`, `angle`) instead of complex `amp`", - pending=True, + pending=False, ) def constant(duration: int, amp: complex, name: Optional[str] = None) -> Waveform: r"""Generates constant-sampled :class:`~qiskit.pulse.library.Waveform`. @@ -58,11 +58,11 @@ def constant(duration: int, amp: complex, name: Optional[str] = None) -> Wavefor @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including zero() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including zero() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.Constant(amp=0,...).get_waveform().", - pending=True, + pending=False, ) def zero(duration: int, name: Optional[str] = None) -> Waveform: """Generates zero-sampled :class:`~qiskit.pulse.library.Waveform`. @@ -84,13 +84,13 @@ def zero(duration: int, name: Optional[str] = None) -> Waveform: @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including square() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including square() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.Square(...).get_waveform()." " Note that pulse.Square() does not support complex values for `amp`," " and that the phase is defined differently. See documentation.", - pending=True, + pending=False, ) def square( duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None @@ -125,15 +125,15 @@ def square( @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including sawtooth() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including sawtooth() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.Sawtooth(...).get_waveform()." " Note that pulse.Sawtooth() does not support complex values for `amp`." " Instead, use two float values for (`amp`, `angle`)." " Also note that the phase is defined differently, such that 2*pi phase" " shifts by a full cycle.", - pending=True, + pending=False, ) def sawtooth( duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None @@ -182,13 +182,13 @@ def sawtooth( @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including triangle() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including triangle() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.Triangle(...).get_waveform()." " Note that pulse.Triangle() does not support complex values for `amp`." " Instead, use two float values for (`amp`, `angle`).", - pending=True, + pending=False, ) def triangle( duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None @@ -237,13 +237,13 @@ def triangle( @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including cos() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including cos() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.Cos(...).get_waveform()." " Note that pulse.Cos() does not support complex values for `amp`." " Instead, use two float values for (`amp`, `angle`).", - pending=True, + pending=False, ) def cos( duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None @@ -275,13 +275,13 @@ def cos( @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including sin() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including sin() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.Sin(...).get_waveform()." " Note that pulse.Sin() does not support complex values for `amp`." " Instead, use two float values for (`amp`, `angle`).", - pending=True, + pending=False, ) def sin( duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None @@ -313,14 +313,14 @@ def sin( @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including gaussian() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including gaussian() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.Gaussian(...).get_waveform()." - " Note that complex value support for the `amp` parameter is pending deprecation" + " Note that complex value support for the `amp` parameter is deprecated" " in the SymbolicPulse library. It is therefore recommended to use two float values" " for (`amp`, `angle`) instead of complex `amp`", - pending=True, + pending=False, ) def gaussian( duration: int, amp: complex, sigma: float, name: Optional[str] = None, zero_ends: bool = True @@ -367,13 +367,13 @@ def gaussian( @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including gaussian_deriv() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including gaussian_deriv() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.GaussianDeriv(...).get_waveform()." " Note that pulse.GaussianDeriv() does not support complex values for `amp`." " Instead, use two float values for (`amp`, `angle`).", - pending=True, + pending=False, ) def gaussian_deriv( duration: int, amp: complex, sigma: float, name: Optional[str] = None @@ -404,13 +404,13 @@ def gaussian_deriv( @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including sech() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including sech() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.Sech(...).get_waveform()." " Note that pulse.Sech() does not support complex values for `amp`." " Instead, use two float values for (`amp`, `angle`).", - pending=True, + pending=False, ) def sech( duration: int, amp: complex, sigma: float, name: str = None, zero_ends: bool = True @@ -455,13 +455,13 @@ def sech( @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including sech_deriv() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including sech_deriv() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.SechDeriv(...).get_waveform()." " Note that pulse.SechDeriv() does not support complex values for `amp`." " Instead, use two float values for (`amp`, `angle`).", - pending=True, + pending=False, ) def sech_deriv(duration: int, amp: complex, sigma: float, name: str = None) -> Waveform: r"""Generates unnormalized sech derivative :class:`~qiskit.pulse.library.Waveform`. @@ -489,14 +489,14 @@ def sech_deriv(duration: int, amp: complex, sigma: float, name: str = None) -> W @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including gaussian_square() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including gaussian_square() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.GaussianSquare(...).get_waveform()." - " Note that complex value support for the `amp` parameter is pending deprecation" + " Note that complex value support for the `amp` parameter is deprecated" " in the SymbolicPulse library. It is therefore recommended to use two float values" " for (`amp`, `angle`) instead of complex `amp`", - pending=True, + pending=False, ) def gaussian_square( duration: int, @@ -565,14 +565,14 @@ def gaussian_square( @deprecate_func( - since="0.25.0", - additional_msg="The discrete pulses library, including drag() is pending deprecation." + since="0.46.0", + additional_msg="The discrete pulses library, including drag() is deprecated." " Instead, use the SymbolicPulse library to create the waveform with" " pulse.Drag(...).get_waveform()." - " Note that complex value support for the `amp` parameter is pending deprecation" + " Note that complex value support for the `amp` parameter is deprecated" " in the SymbolicPulse library. It is therefore recommended to use two float values" " for (`amp`, `angle`) instead of complex `amp`", - pending=True, + pending=False, ) def drag( duration: int, diff --git a/qiskit/pulse/library/parametric_pulses.py b/qiskit/pulse/library/parametric_pulses.py index 9251c1ded9a9..da54c101ffa9 100644 --- a/qiskit/pulse/library/parametric_pulses.py +++ b/qiskit/pulse/library/parametric_pulses.py @@ -69,6 +69,7 @@ class ParametricPulse(Pulse): "qiskit.pulse.library.symbolic_pulses for details." ), since="0.22", + package_name="qiskit-terra", pending=True, ) def __init__( @@ -138,6 +139,7 @@ class Gaussian(ParametricPulse): "QPY serialization support." ), since="0.22", + package_name="qiskit-terra", pending=True, ) def __init__( @@ -250,6 +252,7 @@ class GaussianSquare(ParametricPulse): "QPY serialization support." ), since="0.22", + package_name="qiskit-terra", pending=True, ) def __init__( @@ -420,6 +423,7 @@ class Drag(ParametricPulse): "QPY serialization support." ), since="0.22", + package_name="qiskit-terra", pending=True, ) def __init__( @@ -543,6 +547,7 @@ class Constant(ParametricPulse): "QPY serialization support." ), since="0.22", + package_name="qiskit-terra", pending=True, ) def __init__( diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py index 8c90777eae01..9bc18c9c7f13 100644 --- a/qiskit/pulse/library/symbolic_pulses.py +++ b/qiskit/pulse/library/symbolic_pulses.py @@ -265,6 +265,17 @@ class SymbolicPulse(Pulse): which greatly reduces memory footprint during the program generation. + .. _symbolic_pulse_validation: + + .. rubric:: Pulse validation + + When a symbolic pulse is instantiated, the method :meth:`.validate_parameters` is called, + and performs validation of the pulse. The validation process involves testing the constraint + functions and the maximal amplitude of the pulse (see below). While the validation process + will improve code stability, it will reduce performance and might create + compatibility issues (particularly with JAX). Therefore, it is possible to disable the + validation by setting the class attribute :attr:`.disable_validation` to ``True``. + .. _symbolic_pulse_constraints: .. rubric:: Constraint functions @@ -390,6 +401,8 @@ def Sawtooth(duration, amp, freq, name): "_valid_amp_conditions", ) + disable_validation = False + # Lambdify caches keyed on sympy expressions. Returns the corresponding callable. _envelope_lam = LambdifiedExpression("_envelope") _constraints_lam = LambdifiedExpression("_constraints") @@ -440,6 +453,8 @@ def __init__( self._envelope = envelope self._constraints = constraints self._valid_amp_conditions = valid_amp_conditions + if not self.__class__.disable_validation: + self.validate_parameters() def __getattr__(self, item): # Get pulse parameters with attribute-like access. @@ -596,6 +611,7 @@ class ScalableSymbolicPulse(SymbolicPulse): "Instead, use a float for ``amp`` (for the magnitude) and a float for ``angle``" ), since="0.25.0", + package_name="qiskit-terra", pending=False, predicate=lambda amp: isinstance(amp, complex), ) @@ -774,7 +790,7 @@ def __new__( consts_expr = _sigma > 0 valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type=cls.alias, duration=duration, amp=amp, @@ -786,9 +802,6 @@ def __new__( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance class GaussianSquare(metaclass=_PulseType): @@ -902,7 +915,7 @@ def __new__( consts_expr = sym.And(_sigma > 0, _width >= 0, _duration >= _width) valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type=cls.alias, duration=duration, amp=amp, @@ -914,9 +927,6 @@ def __new__( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance def GaussianSquareDrag( @@ -1051,7 +1061,7 @@ def GaussianSquareDrag( consts_expr = sym.And(_sigma > 0, _width >= 0, _duration >= _width) valid_amp_conditions_expr = sym.And(sym.Abs(_amp) <= 1.0, sym.Abs(_beta) < _sigma) - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type="GaussianSquareDrag", duration=duration, amp=amp, @@ -1063,9 +1073,6 @@ def GaussianSquareDrag( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance def gaussian_square_echo( @@ -1142,6 +1149,7 @@ def gaussian_square_echo( name: Display name for this pulse envelope. limit_amplitude: If ``True``, then limit the amplitude of the waveform to 1. The default is ``True`` and the amplitude is constrained to 1. + Returns: ScalableSymbolicPulse instance. Raises: @@ -1254,7 +1262,7 @@ def gaussian_square_echo( # Check validity of amplitudes valid_amp_conditions_expr = sym.And(sym.Abs(_amp) + sym.Abs(_active_amp) <= 1.0) - instance = SymbolicPulse( + return SymbolicPulse( pulse_type="gaussian_square_echo", duration=duration, parameters=parameters, @@ -1264,9 +1272,6 @@ def gaussian_square_echo( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance def GaussianDeriv( @@ -1318,7 +1323,7 @@ def GaussianDeriv( consts_expr = _sigma > 0 valid_amp_conditions_expr = sym.Abs(_amp / _sigma) <= sym.exp(1 / 2) - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type="GaussianDeriv", duration=duration, amp=amp, @@ -1330,9 +1335,6 @@ def GaussianDeriv( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance class Drag(metaclass=_PulseType): @@ -1418,7 +1420,7 @@ def __new__( consts_expr = _sigma > 0 valid_amp_conditions_expr = sym.And(sym.Abs(_amp) <= 1.0, sym.Abs(_beta) < _sigma) - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type="Drag", duration=duration, amp=amp, @@ -1430,9 +1432,6 @@ def __new__( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance class Constant(metaclass=_PulseType): @@ -1486,7 +1485,7 @@ def __new__( valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type="Constant", duration=duration, amp=amp, @@ -1496,9 +1495,6 @@ def __new__( envelope=envelope_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance def Sin( @@ -1551,7 +1547,7 @@ def Sin( # This might fail for waves shorter than a single cycle valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type="Sin", duration=duration, amp=amp, @@ -1563,9 +1559,6 @@ def Sin( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance def Cos( @@ -1618,7 +1611,7 @@ def Cos( # This might fail for waves shorter than a single cycle valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type="Cos", duration=duration, amp=amp, @@ -1630,9 +1623,6 @@ def Cos( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance def Sawtooth( @@ -1689,7 +1679,7 @@ def Sawtooth( # This might fail for waves shorter than a single cycle valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type="Sawtooth", duration=duration, amp=amp, @@ -1701,9 +1691,6 @@ def Sawtooth( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance def Triangle( @@ -1760,7 +1747,7 @@ def Triangle( # This might fail for waves shorter than a single cycle valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type="Triangle", duration=duration, amp=amp, @@ -1772,9 +1759,6 @@ def Triangle( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance def Square( @@ -1833,7 +1817,7 @@ def Square( # This might fail for waves shorter than a single cycle valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type="Square", duration=duration, amp=amp, @@ -1845,9 +1829,6 @@ def Square( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance def Sech( @@ -1912,7 +1893,7 @@ def Sech( valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type="Sech", duration=duration, amp=amp, @@ -1924,9 +1905,6 @@ def Sech( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance def SechDeriv( @@ -1978,7 +1956,7 @@ def SechDeriv( valid_amp_conditions_expr = sym.Abs(_amp) / _sigma <= 2.0 - instance = ScalableSymbolicPulse( + return ScalableSymbolicPulse( pulse_type="SechDeriv", duration=duration, amp=amp, @@ -1990,6 +1968,3 @@ def SechDeriv( constraints=consts_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - instance.validate_parameters() - - return instance diff --git a/qiskit/pulse/model/__init__.py b/qiskit/pulse/model/__init__.py new file mode 100644 index 000000000000..df12fa082e6a --- /dev/null +++ b/qiskit/pulse/model/__init__.py @@ -0,0 +1,102 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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. + +r""" +========================================================== +Pulse Targets & Frames (:mod:`qiskit.pulse.model`) +========================================================== + +Pulse is meant to be agnostic to the underlying hardware implementation, while still allowing +low-level control. Qiskit Pulse's pulse targets and frames create a flexible framework +to define where pulse instructions are applied, and what would be their carrier frequency and phase +(because typically AC pulses are used). Each :class:`.PulseTarget` represents a separate component +in the quantum computing system on which instructions could be applied. On the other hand, +each :class:`.Frame` represents a frequency and phase duo for the carrier of the pulse. + +While :class:`.PulseTarget` includes a :class`.Port` variant allowing for direct control over +hardware ports, an abstraction layer is provided by :class:`.LogicalElement`. +The abstraction allows to write pulse level programs with less knowledge of the hardware, and in +a level which is more similar to the circuit level programing. i.e., instead of specifying +ports, one can use Qubits, Couplers, etc. + +This logical and virtual representation allows the user to write template pulse +programs without worrying about the exact details of the hardware implementation +(are the pulses to be played via the same port? Which NCO is used?), while still +allowing for effective utilization of the quantum hardware. The burden of mapping +the different combinations of :class:`.LogicalElement` and :class:`.Frame` +to hardware aware objects is left to the Pulse Compiler. + +.. _pulse_targets: + +PulseTarget +================ +:class:`.PulseTarget` includes :class:`.Port` who's objects are identified by a unique string identifier +defined by the control system, and :class:`.LogicalElement` who's objects are identified by their type +and index. Currently, the most prominent example of a :class:`.LogicalElement` is the +:class:`~.pulse.Qubit`. + +.. autosummary:: + :toctree: ../stubs/ + + Port + Qubit + Coupler + + +.. _frames: + +Frame +============= +:class:`.Frame` s are identified by their type and unique identifier. A :class:`.GenericFrame` is used to +specify custom frequency +and phase duos, while :class:`.QubitFrame` and :class:`.MeasurementFrame` are used to indicate that +backend defaults are to be used (for the qubit's driving frequency and measurement frequency +respectively). + +.. autosummary:: + :toctree: ../stubs/ + + GenericFrame + QubitFrame + MeasurementFrame + + +.. _mixed_frames: + +MixedFrame +============= +The combination of a :class:`.LogicalElement` and :class:`.Frame` is dubbed a :class:`.MixedFrame`. + +.. autosummary:: + :toctree: ../stubs/ + + MixedFrame +""" + +from .pulse_target import ( + PulseTarget, + Port, + LogicalElement, + Qubit, + Coupler, +) + +from .frames import ( + Frame, + GenericFrame, + QubitFrame, + MeasurementFrame, +) + +from .mixed_frames import ( + MixedFrame, +) diff --git a/qiskit/pulse/model/frames.py b/qiskit/pulse/model/frames.py new file mode 100644 index 000000000000..ae63fe3c7948 --- /dev/null +++ b/qiskit/pulse/model/frames.py @@ -0,0 +1,162 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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. + +""" +Frames +""" + +from abc import ABC + +import numpy as np + +from qiskit.pulse.exceptions import PulseError + + +class Frame(ABC): + """Base class for pulse module frame. + + Because pulses used in Quantum hardware are typically AC pulses, the carrier frequency and phase + must be defined. The :class:`Frame` is the object which identifies the frequency and phase for + the carrier. + and each pulse and most other instructions are associated with a frame. The different types of frames + dictate how the frequency and phase duo are defined. + + The default initial phase for every frame is 0. + """ + + def __init__(self, identifier): + """Create ``Frame``. + + Args: + identifier: A unique identifier used to hash the Frame. + """ + self._hash = hash((type(self), identifier)) + + def __eq__(self, other: "Frame") -> bool: + """Return True iff self and other are equal, specifically, iff they have the same type and hash. + + Args: + other: The frame to compare to this one. + + Returns: + True iff equal. + """ + return type(self) is type(other) and self._hash == other._hash + + def __hash__(self) -> int: + return self._hash + + +class GenericFrame(Frame): + """Pulse module GenericFrame. + + The :class:`GenericFrame` is used for custom user defined frames, which are not associated with any + backend defaults. It is especially useful when the frame doesn't correspond to any frame of + the typical qubit model, like qudit control for example. Because no backend defaults exist for + these frames, during compilation an initial frequency and phase will need to be provided. + + :class:`GenericFrame` objects are identified by their unique name. + """ + + def __init__(self, name: str): + """Create ``GenericFrame``. + + Args: + name: A unique identifier used to identify the frame. + """ + self._name = name + super().__init__(name) + + @property + def name(self) -> str: + """Return the name of the frame.""" + return self._name + + def __repr__(self) -> str: + return f"GenericFrame({self._name})" + + +class QubitFrame(Frame): + """A frame associated with the driving of a qubit. + + :class:`QubitFrame` is a frame associated with the driving of a specific qubit. + The initial frequency of + the frame will be taken as the default driving frequency provided by the backend + during compilation. + """ + + def __init__(self, index: int): + """Create ``QubitFrame``. + + Args: + index: The index of the qubit represented by the frame. + """ + self._validate_index(index) + self._index = index + super().__init__("QubitFrame" + str(index)) + + @property + def index(self) -> int: + """Return the qubit index of the qubit frame.""" + return self._index + + def _validate_index(self, index) -> None: + """Raise a ``PulseError`` if the qubit index is invalid. Namely, check if the index is a + non-negative integer. + + Raises: + PulseError: If ``identifier`` (index) is a negative integer. + """ + if not isinstance(index, (int, np.integer)) or index < 0: + raise PulseError("Qubit index must be a non-negative integer") + + def __repr__(self) -> str: + return f"QubitFrame({self._index})" + + +class MeasurementFrame(Frame): + """A frame associated with the measurement of a qubit. + + ``MeasurementFrame`` is a frame associated with the readout of a specific qubit, + which requires a stimulus tone driven at frequency off resonant to qubit drive. + + If not set otherwise, the initial frequency of the frame will be taken as the default + measurement frequency provided by the backend during compilation. + """ + + def __init__(self, index: int): + """Create ``MeasurementFrame``. + + Args: + index: The index of the qubit represented by the frame. + """ + self._validate_index(index) + self._index = index + super().__init__("MeasurementFrame" + str(index)) + + @property + def index(self) -> int: + """Return the qubit index of the measurement frame.""" + return self._index + + def _validate_index(self, index) -> None: + """Raise a ``PulseError`` if the qubit index is invalid. Namely, check if the index is a + non-negative integer. + + Raises: + PulseError: If ``index`` is a negative integer. + """ + if not isinstance(index, (int, np.integer)) or index < 0: + raise PulseError("Qubit index must be a non-negative integer") + + def __repr__(self) -> str: + return f"MeasurementFrame({self._index})" diff --git a/qiskit/pulse/model/mixed_frames.py b/qiskit/pulse/model/mixed_frames.py new file mode 100644 index 000000000000..50ff13c47a44 --- /dev/null +++ b/qiskit/pulse/model/mixed_frames.py @@ -0,0 +1,78 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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. + +""" +Mixed Frames +""" + +from .frames import Frame +from .pulse_target import PulseTarget + + +class MixedFrame: + """Representation of a :class:`PulseTarget` and :class:`Frame` combination. + + Most instructions need to be associated with both a :class:`PulseTarget` and a + :class:`Frame`. The combination + of the two is called a mixed frame and is represented by a :class:`MixedFrame` object. + + In most cases the :class:`MixedFrame` is used more by the compiler, and a pulse program + can be written without :class:`MixedFrame` s, by setting :class:`PulseTarget` and + :class:`Frame` independently. However, in some cases using :class:`MixedFrame` s can + better convey the meaning of the code, and change the compilation process. One example + is the use of the shift/set frequency/phase instructions which are not broadcasted to other + :class:`MixedFrame` s if applied on a specific :class:`MixedFrame` (unlike the behavior + of :class:`Frame`). User can also use a subclass of :class:`MixedFrame` for a particular + combination of logical elements and frames as if a syntactic sugar. This might + increase the readability of a user pulse program. As an example consider the cross + resonance architecture, in which a pulse is played on a target qubit frame and applied + to a control qubit logical element. + """ + + def __init__(self, pulse_target: PulseTarget, frame: Frame): + """Create ``MixedFrame``. + + Args: + pulse_target: The ``PulseTarget`` associated with the mixed frame. + frame: The frame associated with the mixed frame. + """ + self._pulse_target = pulse_target + self._frame = frame + self._hash = hash((self._pulse_target, self._frame, type(self))) + + @property + def pulse_target(self) -> PulseTarget: + """Return the target of this mixed frame.""" + return self._pulse_target + + @property + def frame(self) -> Frame: + """Return the ``Frame`` of this mixed frame.""" + return self._frame + + def __repr__(self) -> str: + return f"MixedFrame({self.pulse_target},{self.frame})" + + def __eq__(self, other: "MixedFrame") -> bool: + """Return True iff self and other are equal, specifically, iff they have the same target + and frame. + + Args: + other: The mixed frame to compare to this one. + + Returns: + True iff equal. + """ + return self._pulse_target == other._pulse_target and self._frame == other._frame + + def __hash__(self) -> int: + return self._hash diff --git a/qiskit/pulse/model/pulse_target.py b/qiskit/pulse/model/pulse_target.py new file mode 100644 index 000000000000..69894780df27 --- /dev/null +++ b/qiskit/pulse/model/pulse_target.py @@ -0,0 +1,205 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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. + +""" +PulseTarget +""" +from abc import ABC, abstractmethod +from typing import Tuple +import numpy as np + +from qiskit.pulse.exceptions import PulseError + + +class PulseTarget(ABC): + """Base class of pulse target. + + A :class:`PulseTarget` object identifies a hardware component the user can control, the typical + example being playing pulses on. Other examples include measurement related instruments. + + When playing a pulse on a quantum hardware, one typically has to define on what hardware component + the pulse will be played, and the frame (frequency and phase) of the carrier wave. + :class:`PulseTarget` addresses only the first of the two, and identifies the component which is the + target of the pulse. Every played pulse and most other instructions are associated with a + :class:`PulseTarget` on which they are performed. + + A subclass of :class:`PulseTarget` has to be hashable. + """ + + @abstractmethod + def __hash__(self) -> int: + pass + + +class Port(PulseTarget): + """A ``Port`` type ``PulseTarget``. + + A :class:`Port` is the most basic ``PulseTarget`` - simply a hardware port the user can control, + (typically for playing pulses, but not only, for example data acquisition). + + A :class:`Port` is identified by a string, which is set, and must be recognized, by the + backend. Therefore, using pulse level control with :class:`Port` requires an extensive + knowledge of the hardware. Programs with string identifiers which are not recognized by the + backend will fail to execute. + """ + + def __init__(self, name: str): + """Create ``Port``. + + Args: + name: A string identifying the port. + """ + self._name = name + self._hash = hash((name, type(self))) + + @property + def name(self) -> str: + """Return the ``name`` of this port.""" + return self._name + + def __eq__(self, other: "Port") -> bool: + """Return True iff self and other are equal, specifically, iff they have the same type + and the same ``name``. + + Args: + other: The Port to compare to this one. + + Returns: + True iff equal. + """ + return type(self) is type(other) and self._name == other._name + + def __repr__(self) -> str: + return f"Port({self._name})" + + def __hash__(self) -> int: + return self._hash + + +class LogicalElement(PulseTarget, ABC): + """Base class of logical elements. + + Class :class:`LogicalElement` provides an abstraction layer to ``PulseTarget``. The abstraction + allows to write pulse level programs with less knowledge of the hardware, and in a level which + is more similar to the circuit level programing. i.e., instead of specifying specific ports, one + can use Qubits, Couplers, etc. + + A logical element is identified by its type and index. + """ + + def __init__(self, index: Tuple[int, ...]): + """Create ``LogicalElement``. + + Args: + index: Tuple of indices of the logical element. + """ + self._validate_index(index) + self._index = index + self._hash = hash((index, type(self))) + + @property + def index(self) -> Tuple[int, ...]: + """Return the ``index`` of this logical element.""" + return self._index + + @abstractmethod + def _validate_index(self, index) -> None: + """Raise a PulseError if the logical element ``index`` is invalid. + + Raises: + PulseError: If ``index`` is not valid. + """ + pass + + def __eq__(self, other: "LogicalElement") -> bool: + """Return True iff self and other are equal, specifically, iff they have the same type + and the same ``index``. + + Args: + other: The logical element to compare to this one. + + Returns: + True iff equal. + """ + return type(self) is type(other) and self._index == other._index + + def __repr__(self) -> str: + ind_str = str(self._index) if len(self._index) > 1 else f"({self._index[0]})" + return type(self).__name__ + ind_str + + def __hash__(self) -> int: + return self._hash + + +class Qubit(LogicalElement): + """Qubit logical element. + + ``Qubit`` represents the different qubits in the system, as identified by + their (positive integer) index values. + """ + + def __init__(self, index: int): + """Qubit logical element. + + Args: + index: Qubit index (positive integer). + """ + super().__init__((index,)) + + @property + def qubit_index(self): + """Index of the Qubit""" + return self.index[0] + + def _validate_index(self, index) -> None: + """Raise a ``PulseError`` if the qubit index is invalid. Namely, check if the index is a + non-negative integer. + + Raises: + PulseError: If ``index`` is a negative integer. + """ + if not isinstance(index[0], (int, np.integer)) or index[0] < 0: + raise PulseError("Qubit index must be a non-negative integer") + + +class Coupler(LogicalElement): + """Coupler logical element. + + :class:`Coupler` represents an element which couples qubits, and can be controlled on its own. + It is identified by the tuple of indices of the coupled qubits. + """ + + def __init__(self, *qubits): + """Coupler logical element. + + The coupler ``index`` is defined as the ``tuple`` (\\*qubits). + + Args: + *qubits: any number of qubit indices coupled by the coupler. + """ + super().__init__(tuple(qubits)) + + def _validate_index(self, index) -> None: + """Raise a ``PulseError`` if the coupler ``index`` is invalid. Namely, + check if coupled qubit indices are non-negative integers, at least two indices were provided, + and that the indices don't repeat. + + Raises: + PulseError: If ``index`` is invalid. + """ + if len(index) < 2: + raise PulseError("At least two qubit indices are needed for a Coupler") + for qubit_index in index: + if not isinstance(qubit_index, (int, np.integer)) or qubit_index < 0: + raise PulseError("Both indices of coupled qubits must be non-negative integers") + if len(set(index)) != len(index): + raise PulseError("Indices of a coupler can not repeat") diff --git a/qiskit/qasm2/parse.py b/qiskit/qasm2/parse.py index 116c6b7c9aa0..fbd7f48390d8 100644 --- a/qiskit/qasm2/parse.py +++ b/qiskit/qasm2/parse.py @@ -234,15 +234,13 @@ def from_bytecode(bytecode, custom_instructions: Iterable[CustomInstruction]): qc._append(CircuitInstruction(Measure(), (qubits[qubit],), (clbits[clbit],))) elif opcode == OpCode.ConditionedMeasure: qubit, clbit, creg, value = op.operands - measure = Measure() - measure.condition = (qc.cregs[creg], value) + measure = Measure().c_if(qc.cregs[creg], value) qc._append(CircuitInstruction(measure, (qubits[qubit],), (clbits[clbit],))) elif opcode == OpCode.Reset: qc._append(CircuitInstruction(Reset(), (qubits[op.operands[0]],))) elif opcode == OpCode.ConditionedReset: qubit, creg, value = op.operands - reset = Reset() - reset.condition = (qc.cregs[creg], value) + reset = Reset().c_if(qc.cregs[creg], value) qc._append(CircuitInstruction(reset, (qubits[qubit],))) elif opcode == OpCode.Barrier: op_qubits = op.operands[0] diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py index d88a79b2faba..091d3aabc417 100644 --- a/qiskit/qobj/converters/pulse_instruction.py +++ b/qiskit/qobj/converters/pulse_instruction.py @@ -505,6 +505,7 @@ def _convert_bundled_acquire( @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_acquire(self, shift, instruction): return self._convert_instruction(instruction, shift) @@ -512,6 +513,7 @@ def convert_acquire(self, shift, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_bundled_acquires(self, shift, instructions_): return self._convert_bundled_acquire(instructions_, shift) @@ -519,6 +521,7 @@ def convert_bundled_acquires(self, shift, instructions_): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_set_frequency(self, shift, instruction): return self._convert_instruction(instruction, shift) @@ -526,6 +529,7 @@ def convert_set_frequency(self, shift, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_shift_frequency(self, shift, instruction): return self._convert_instruction(instruction, shift) @@ -533,6 +537,7 @@ def convert_shift_frequency(self, shift, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_set_phase(self, shift, instruction): return self._convert_instruction(instruction, shift) @@ -540,6 +545,7 @@ def convert_set_phase(self, shift, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_shift_phase(self, shift, instruction): return self._convert_instruction(instruction, shift) @@ -547,6 +553,7 @@ def convert_shift_phase(self, shift, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_delay(self, shift, instruction): return self._convert_instruction(instruction, shift) @@ -554,6 +561,7 @@ def convert_delay(self, shift, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_play(self, shift, instruction): return self._convert_instruction(instruction, shift) @@ -561,6 +569,7 @@ def convert_play(self, shift, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_snapshot(self, shift, instruction): return self._convert_instruction(instruction, shift) @@ -958,6 +967,7 @@ def _convert_generic( @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_acquire(self, instruction): t0 = instruction.t0 @@ -969,6 +979,7 @@ def convert_acquire(self, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_set_phase(self, instruction): t0 = instruction.t0 @@ -980,6 +991,7 @@ def convert_set_phase(self, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_shift_phase(self, instruction): t0 = instruction.t0 @@ -991,6 +1003,7 @@ def convert_shift_phase(self, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_set_frequency(self, instruction): t0 = instruction.t0 @@ -1002,6 +1015,7 @@ def convert_set_frequency(self, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_shift_frequency(self, instruction): t0 = instruction.t0 @@ -1013,6 +1027,7 @@ def convert_shift_frequency(self, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_delay(self, instruction): t0 = instruction.t0 @@ -1024,6 +1039,7 @@ def convert_delay(self, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def bind_pulse(self, pulse): if pulse.name not in self._pulse_library: @@ -1032,6 +1048,7 @@ def bind_pulse(self, pulse): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_parametric(self, instruction): t0 = instruction.t0 @@ -1043,6 +1060,7 @@ def convert_parametric(self, instruction): @deprecate_func( additional_msg="Instead, call converter instance directory.", since="0.23.0", + package_name="qiskit-terra", ) def convert_snapshot(self, instruction): t0 = instruction.t0 diff --git a/qiskit/qpy/binary_io/schedules.py b/qiskit/qpy/binary_io/schedules.py index 83a023b9527b..959b18fc3306 100644 --- a/qiskit/qpy/binary_io/schedules.py +++ b/qiskit/qpy/binary_io/schedules.py @@ -169,10 +169,10 @@ def _read_symbolic_pulse(file_obj, version): # And warn that this will change in future releases: warnings.warn( - "Complex amp support for symbolic library pulses will be deprecated. " - "Once deprecated, library pulses loaded from old QPY files (Terra version < 0.23)," + "Complex amp support for symbolic library pulses will be deprecated in Qiskit 1.0.0. " + "Once deprecated, library pulses loaded from old QPY files (Terra version < 0.23.0)," " will be converted automatically to float (amp,angle) representation.", - PendingDeprecationWarning, + DeprecationWarning, ) class_name = "ScalableSymbolicPulse" diff --git a/qiskit/qpy/interface.py b/qiskit/qpy/interface.py index 9886160dacc0..f4a77ec14598 100644 --- a/qiskit/qpy/interface.py +++ b/qiskit/qpy/interface.py @@ -231,6 +231,12 @@ def load( version = struct.unpack("!6sB", file_obj.read(7))[1] file_obj.seek(0) + if version > common.QPY_VERSION: + raise QiskitError( + f"The QPY format version being read, {version}, isn't supported by " + "this Qiskit version. Please upgrade your version of Qiskit to load this QPY payload" + ) + if version < 10: data = formats.FILE_HEADER._make( struct.unpack( diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index e84253b80b7b..5bfa7815b1f8 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -230,13 +230,18 @@ def copy(self): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="Instead, index or iterate through the Clifford.tableau attribute.", ) def __getitem__(self, key): """Return a stabilizer Pauli row""" return self.table.__getitem__(key) - @deprecate_func(since="0.24.0", additional_msg="Use Clifford.tableau property instead.") + @deprecate_func( + since="0.24.0", + package_name="qiskit-terra", + additional_msg="Use Clifford.tableau property instead.", + ) def __setitem__(self, key, value): """Set a stabilizer Pauli row""" self.tableau.__setitem__(key, self._stack_table_phase(value.array, value.phase)) diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py index 295d8a97d01c..c5b93ceaf4e7 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli.py +++ b/qiskit/quantum_info/operators/symplectic/pauli.py @@ -110,7 +110,7 @@ class initialization (``Pauli('-iXYZ')``). A ``Pauli`` object can be P = (-i)^{q + z\cdot x} Z^z \cdot X^x. - The :math:`k`th qubit corresponds to the :math:`k`th entry in the + The :math:`k`-th qubit corresponds to the :math:`k`-th entry in the :math:`z` and :math:`x` arrays .. math:: diff --git a/qiskit/quantum_info/synthesis/clifford_decompose.py b/qiskit/quantum_info/synthesis/clifford_decompose.py index 7ef88a5097a1..6a92c6eb014e 100644 --- a/qiskit/quantum_info/synthesis/clifford_decompose.py +++ b/qiskit/quantum_info/synthesis/clifford_decompose.py @@ -23,7 +23,9 @@ @deprecate_func( - additional_msg="Instead, use the function qiskit.synthesis.synth_clifford_full.", since="0.23.0" + additional_msg="Instead, use the function qiskit.synthesis.synth_clifford_full.", + since="0.23.0", + package_name="qiskit-terra", ) def decompose_clifford(clifford, method=None): """DEPRECATED: Decompose a Clifford operator into a QuantumCircuit. diff --git a/qiskit/quantum_info/synthesis/cnotdihedral_decompose.py b/qiskit/quantum_info/synthesis/cnotdihedral_decompose.py index 8e4c1a373035..c0491ffbd1e1 100644 --- a/qiskit/quantum_info/synthesis/cnotdihedral_decompose.py +++ b/qiskit/quantum_info/synthesis/cnotdihedral_decompose.py @@ -24,6 +24,7 @@ @deprecate_func( additional_msg="Instead, use the function qiskit.synthesis.synth_cnotdihedral_full.", since="0.23.0", + package_name="qiskit-terra", ) def decompose_cnotdihedral(elem): """DEPRECATED: Decompose a CNOTDihedral element into a QuantumCircuit. diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 82088ae051f1..ba5e99d3064f 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -1086,7 +1086,7 @@ def decomp3_supercontrolled(self, target): return U3r, U3l, U2r, U2l, U1r, U1l, U0r, U0l - @deprecate_arg("target", new_alias="unitary", since="0.23.0") + @deprecate_arg("target", new_alias="unitary", since="0.23.0", package_name="qiskit-terra") def __call__( self, unitary: Operator | np.ndarray, diff --git a/qiskit/test/base.py b/qiskit/test/base.py index 588ca89434a0..839e15b0191f 100644 --- a/qiskit/test/base.py +++ b/qiskit/test/base.py @@ -245,7 +245,7 @@ def setUpClass(cls): "Setting metadata to None.*", # and this one once Qiskit/qiskit-aer#1945 is merged and released. r"The method ``qiskit\.circuit\.quantumcircuit\.QuantumCircuit\.i\(\)`` is " - r"deprecated as of qiskit-terra 0\.45\.0\. It will be removed no earlier than 3 " + r"deprecated as of qiskit 0\.45\.0\. It will be removed no earlier than 3 " r"months after the release date\. Use QuantumCircuit\.id as direct replacement\.", ] diff --git a/qiskit/tools/jupyter/library.py b/qiskit/tools/jupyter/library.py index 09057db8c355..773416ad3a04 100644 --- a/qiskit/tools/jupyter/library.py +++ b/qiskit/tools/jupyter/library.py @@ -43,6 +43,7 @@ def _generate_circuit_library_visualization(circuit: QuantumCircuit): @deprecate_func( since="0.25.0", additional_msg="This is unused by Qiskit, and no replacement will be publicly provided.", + package_name="qiskit-terra", ) def circuit_data_table(circuit: QuantumCircuit) -> wid.HTML: """Create a HTML table widget for a given quantum circuit. @@ -112,6 +113,7 @@ def circuit_data_table(circuit: QuantumCircuit) -> wid.HTML: @deprecate_func( since="0.25.0", additional_msg="This is unused by Qiskit, and no replacement will be publicly provided.", + package_name="qiskit-terra", ) def properties_widget(circuit: QuantumCircuit) -> wid.VBox: """Create a HTML table widget with header for a given quantum circuit. @@ -133,6 +135,7 @@ def properties_widget(circuit: QuantumCircuit) -> wid.VBox: @deprecate_func( since="0.25.0", additional_msg="This is unused by Qiskit, and no replacement will be publicly provided.", + package_name="qiskit-terra", ) def qasm_widget(circuit: QuantumCircuit) -> wid.VBox: """Generate an OpenQASM widget with header for a quantum circuit. @@ -192,6 +195,7 @@ def qasm_widget(circuit: QuantumCircuit) -> wid.VBox: @deprecate_func( since="0.25.0", additional_msg="This is unused by Qiskit, and no replacement will be publicly provided.", + package_name="qiskit-terra", ) def circuit_diagram_widget() -> wid.Box: """Create a circuit diagram widget. @@ -218,6 +222,7 @@ def circuit_diagram_widget() -> wid.Box: @deprecate_func( since="0.25.0", additional_msg="This is unused by Qiskit, and no replacement will be publicly provided.", + package_name="qiskit-terra", ) def circuit_library_widget(circuit: QuantumCircuit) -> None: """Create a circuit library widget. diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 2a234401b013..13ae12e09097 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -511,7 +511,7 @@ and non-gate operations. The allowed instructions for a given backend can be found by querying the :class:`~.Target` for the devices: -.. code-block: +.. code-block:: from qiskit.providers.fake_provider import FakeVigoV2 backend = FakeVigoV2() @@ -585,6 +585,7 @@ It is important to highlight two special cases: 1. If A swap gate is not a native gate and must be decomposed this requires three CNOT gates: + .. code-block:: from qiskit.providers.fake_provider import FakeVigoV2 @@ -653,9 +654,9 @@ for minimizing the loss due to non-uniform noise properties across a device. Due to the importance of this stage, the preset pass managers try a few different methods to find the best layout. Typically this involves 2 steps: first, -trying to find a "perfect" layout (a layout which does not require any swap operations) and then, +trying to find a "perfect" layout (a layout which does not require any swap operations), and then, a heuristic pass that tries to find the best layout to use if a perfect layout cannot be found. -For the first stage there are 2 passes typically used for this: +There are 2 passes typically used for the first stage: - :class:`~.VF2Layout`: Models layout selection as a subgraph isomorphism problem and tries to find a subgraph of the connectivity graph that is isomorphic to the @@ -663,22 +664,22 @@ scoring heuristic is run to select the mapping which would result in the lowest average error when executing the circuit. -- :class:`~.TrivialLayout`: Map each virtual qubit to the same numbered physical qubit on the device, +- :class:`~.TrivialLayout`: Maps each virtual qubit to the same numbered physical qubit on the device, i.e. ``[0,1,2,3,4]`` -> ``[0,1,2,3,4]``. This is historical behavior used only in ``optimization_level=1`` to try to find a perfect layout. If it fails to do so, :class:`~.VF2Layout` - is tried next). + is tried next. Next, for the heuristic stage, 2 passes are used by default: - :class:`~.SabreLayout`: Selects a layout by starting from an initial random layout and then - repeatedly running a routing algorithm (by default :class:`~.SabreSwap`) both forwards and - backwards over the circuit using the permutation caused by swap insertions to adjust that + repeatedly running a routing algorithm (by default :class:`~.SabreSwap`) both forward and + backward over the circuit, using the permutation caused by swap insertions to adjust that initial random layout. For more details you can refer to the paper describing the algorithm: `arXiv:1809.02573 `__ - :class:`~.SabreLayout` is use to select a layout if a perfect layout isn't found for + :class:`~.SabreLayout` is used to select a layout if a perfect layout isn't found for optimization levels 1, 2, and 3. -- :class:`~.TrivialLayout`: Always used for the layout at optimization level 0 -- :class:`~.DenseLayout`: Find the sub-graph of the device with greatest connectivity +- :class:`~.TrivialLayout`: Always used for the layout at optimization level 0. +- :class:`~.DenseLayout`: Finds the sub-graph of the device with greatest connectivity that has the same number of qubits as the circuit. Used for optimization level 1 if there are control flow operations (such as :class:`~.IfElseOp`) present in the circuit. @@ -926,7 +927,7 @@ After the circuit has been translated to the target basis, mapped to the device, and optimized, a scheduling phase can be applied to optionally account for all the idle time in the circuit. -At a high level the scheduling can be thought of as inserting delays into the circuit to account +At a high level, the scheduling can be thought of as inserting delays into the circuit to account for idle time on the qubits between the execution of instructions. For example, if we start with a circuit such as: @@ -977,31 +978,32 @@ timeline_draw(circ) -The scheduling of a circuit involves two parts, analysis and constraint mapping followed by a +The scheduling of a circuit involves two parts: analysis and constraint mapping, followed by a padding pass. The first part requires running a scheduling analysis pass such as :class:`~.ALAPSchedulingAnalysis` or :class:`~.ASAPSchedulingAnalysis` which analyzes the circuit and records the start time of each instruction in the circuit using a scheduling algorithm ("as late as possible" for :class:`~.ALAPSchedulingAnalysis` and "as soon as possible" for -:class:`~.ASAPSchedulingAnalysis`) in the property set. Once the circuit has an initial scheduling +:class:`~.ASAPSchedulingAnalysis`) in the property set. Once the circuit has an initial scheduling, additional passes can be run to account for any timing constraints on the target backend, such as alignment constraints. This is typically done with the :class:`~.ConstrainedReschedule` pass which will adjust the scheduling set in the property set to the constraints of the target backend. Once all -the scheduling and adjustments/rescheduling are finished a padding pass, +the scheduling and adjustments/rescheduling are finished, a padding pass, such as :class:`~.PadDelay` or :class:`~.PadDynamicalDecoupling` is run to insert the instructions into the circuit, which completes the scheduling. Scheduling Analysis with control flow instructions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -When scheduling analysis passes run there are additional constraints on classical conditions -and control flow instructions in a circuit. This section covers the details of these additional +When running scheduling analysis passes on a circuit, you must keep in mind that there +are additional constraints on classical conditions and control flow instructions. This section +covers the details of these additional constraints that any scheduling pass will need to account for. -Policy of topological node ordering in scheduling -''''''''''''''''''''''''''''''''''''''''''''''''' +Topological node ordering in scheduling +'''''''''''''''''''''''''''''''''''''''' -The DAG representation of ``QuantumCircuit`` respects the node ordering also in the +The DAG representation of ``QuantumCircuit`` respects the node ordering in the classical register wires, though theoretically two conditional instructions conditioned on the same register could commute, i.e. read-access to the classical register doesn't change its state. @@ -1013,7 +1015,8 @@ qc.x(0).c_if(0, True) qc.x(1).c_if(0, True) -The scheduler SHOULD comply with above topological ordering policy of the DAG circuit. +The scheduler SHOULD comply with the above topological ordering policy of the +DAG circuit. Accordingly, the `asap`-scheduled circuit will become .. parsed-literal:: @@ -1028,18 +1031,20 @@ └─────────┘└─────────┘ Note that this scheduling might be inefficient in some cases, -because the second conditional operation can start without waiting the delay of 100 dt. -However, such optimization should be done by another pass, -otherwise scheduling may break topological ordering of the original circuit. +because the second conditional operation could start without waiting +for the 100 dt delay. +However, any additional optimization should be done in a different pass, +not to break the topological ordering of the original circuit. -Realistic control flow scheduling respecting for microarchitecture -'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +Realistic control flow scheduling (respecting microarchitecture) +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' -In the dispersive QND readout scheme, qubit is measured with microwave stimulus to qubit (Q) -followed by resonator ring-down (depopulation). This microwave signal is recorded -in the buffer memory (B) with hardware kernel, then a discriminated (D) binary value -is moved to the classical register (C). -The sequence from t0 to t1 of the measure instruction interval might be modeled as follows: +In the dispersive QND readout scheme, the qubit (Q) is measured by sending +a microwave stimulus, followed by a resonator ring-down (depopulation). This +microwave signal is recorded in the buffer memory (B) with the hardware kernel, +then a discriminated (D) binary value is moved to the classical register (C). +A sequence from t0 to t1 of the measure instruction interval could be +modeled as follows: .. parsed-literal:: @@ -1048,12 +1053,13 @@ D ░░░░░░░░░░▒▒▒▒▒▒░░░ C ░░░░░░░░░░░░░░░░▒▒░ -However, ``QuantumCircuit`` representation is not enough accurate to represent -this model. In the circuit representation, thus ``Qubit`` is occupied by the -stimulus microwave signal during the first half of the interval, -and ``Clbit`` is only occupied at the very end of the interval. +However, the :class:`.QuantumCircuit` representation is not accurate enough to represent +this model. In the circuit representation, the corresponding :class:`.pulse.Qubit` is occupied +by the stimulus microwave signal during the first half of the interval, +and the :class:`.Clbit` is only occupied at the very end of the interval. -This precise model may induce weird edge case. +The lack of precision representing the physical model may induce +edge cases in the scheduling: .. parsed-literal:: @@ -1065,12 +1071,14 @@ c: 1/╡ c_0=0x1 ╞═╩═ └─────────┘ 0 -In this example, user may intend to measure the state of ``q_1``, after ``XGate`` is -applied to the ``q_0``. This is correct interpretation from viewpoint of -the topological node ordering, i.e. x gate node come in front of the measure node. +In this example, a user may intend to measure the state of ``q_1`` after the +:class:`.XGate` is applied to ``q_0``. This is the correct interpretation from +the viewpoint of topological node ordering, i.e. The :class:`.XGate` node comes in +front of the :class:`.Measure` node. However, according to the measurement model above, the data in the register -is unchanged during the stimulus, thus two nodes are simultaneously operated. -If one `alap`-schedule this circuit, it may return following circuit. +is unchanged during the application of the stimulus, so two nodes are +simultaneously operated. +If one tries to `alap`-schedule this circuit, it may return following circuit: .. parsed-literal:: @@ -1082,9 +1090,10 @@ c: 1/══════════════════╡ c_0=0x1 ╞═╩═ └─────────┘ 0 -Note that there is no delay on ``q_1`` wire, and the measure instruction immediately -start after t=0, while the conditional gate starts after the delay. -It looks like the topological ordering between the nodes are flipped in the scheduled view. +Note that there is no delay on the ``q_1`` wire, and the measure instruction +immediately starts after t=0, while the conditional gate starts after the delay. +It looks like the topological ordering between the nodes is flipped in the +scheduled view. This behavior can be understood by considering the control flow model described above, .. parsed-literal:: @@ -1103,20 +1112,21 @@ D ░░░░░░░░░░▒▒▒▒▒▒░░░ C ░░░░░░░░░░░░░░░░▒▒░ -Since there is no qubit register (Q0, Q1) overlap, the node ordering is determined by the -shared classical register C. As you can see, the execution order is still +Since there is no qubit register overlap between Q0 and Q1, the node ordering is +determined by the shared classical register C. As you can see, the execution order is still preserved on C, i.e. read C then apply ``XGate``, finally store the measured outcome in C. -Because ``DAGOpNode`` cannot define different durations for associated registers, -the time ordering of two nodes is inverted anyways. +But because ``DAGOpNode`` cannot define different durations for the associated registers, +the time ordering of the two nodes is inverted. This behavior can be controlled by ``clbit_write_latency`` and ``conditional_latency``. -The former parameter determines the delay of the register write-access from -the beginning of the measure instruction t0, and another parameter determines -the delay of conditional gate operation from t0 which comes from the register read-access. -These information might be found in the backend configuration and then should +``clbit_write_latency`` determines the delay of the register write-access from +the beginning of the measure instruction (t0), while ``conditional_latency`` determines +the delay of conditional gate operations with respect to t0, which is determined +by the register read-access. +This information is accessible in the backend configuration and should be copied to the pass manager property set before the pass is called. -By default latencies, the `alap`-scheduled circuit of above example may become +Due to default latencies, the `alap`-scheduled circuit of above example may become .. parsed-literal:: @@ -1128,12 +1138,12 @@ c: 1/╡ c_0=0x1 ╞═╩═ └─────────┘ 0 -If the backend microarchitecture supports smart scheduling of the control flow, i.e. -it may separately schedule qubit and classical register, -insertion of the delay yields unnecessary longer total execution time. +If the backend microarchitecture supports smart scheduling of the control flow +instructions, such as separately scheduling qubits and classical registers, +the insertion of the delay yields an unnecessarily longer total execution time. .. parsed-literal:: - : Quantum Circuit, first-xgate + : Quantum Circuit, first-XGate 0 ░▒▒▒░░░░░░░░░░░░░░░ 1 ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ @@ -1145,9 +1155,9 @@ Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ C ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ (zero latency, scheduled after C0 read-access) -However this result is much more intuitive in the topological ordering view. -If finite conditional latency is provided, for example, 30 dt, the circuit -is scheduled as follows. +However, this result is much more intuitive in the topological ordering view. +If a finite conditional latency value is provided, for example, 30 dt, the circuit +is scheduled as follows: .. parsed-literal:: @@ -1241,6 +1251,8 @@ .. autoexception:: TranspilerAccessError .. autoexception:: CouplingError .. autoexception:: LayoutError +.. autoexception:: CircuitTooWideForTarget + """ # For backward compatibility @@ -1253,7 +1265,13 @@ from .passmanager import PassManager, StagedPassManager from .passmanager_config import PassManagerConfig from .propertyset import PropertySet # pylint: disable=no-name-in-module -from .exceptions import TranspilerError, TranspilerAccessError, CouplingError, LayoutError +from .exceptions import ( + TranspilerError, + TranspilerAccessError, + CouplingError, + LayoutError, + CircuitTooWideForTarget, +) from .fencedobjs import FencedDAGCircuit, FencedPropertySet from .basepasses import AnalysisPass, TransformationPass from .coupling import CouplingMap diff --git a/qiskit/transpiler/basepasses.py b/qiskit/transpiler/basepasses.py index 02593c0bb9fd..c09ee190e38b 100644 --- a/qiskit/transpiler/basepasses.py +++ b/qiskit/transpiler/basepasses.py @@ -74,7 +74,6 @@ class BasePass(GenericPass, metaclass=MetaPass): def __init__(self): super().__init__() self.preserves: Iterable[GenericPass] = [] - self.property_set = PropertySet() self._hash = hash(None) def __hash__(self): @@ -118,21 +117,6 @@ def is_analysis_pass(self): """ return isinstance(self, AnalysisPass) - def execute( - self, - passmanager_ir: PassManagerIR, - state: PassManagerState, - callback: Callable = None, - ) -> tuple[PassManagerIR, PassManagerState]: - # For backward compatibility. - # Circuit passes access self.property_set. - self.property_set = state.property_set - return super().execute( - passmanager_ir=passmanager_ir, - state=state, - callback=callback, - ) - def __call__( self, circuit: QuantumCircuit, diff --git a/qiskit/transpiler/exceptions.py b/qiskit/transpiler/exceptions.py index ef79603bfed2..5c23cb2b3914 100644 --- a/qiskit/transpiler/exceptions.py +++ b/qiskit/transpiler/exceptions.py @@ -49,3 +49,7 @@ def __init__(self, *msg): def __str__(self): """Return the message.""" return repr(self.msg) + + +class CircuitTooWideForTarget(TranspilerError): + """Error raised if the circuit is too wide for the target.""" diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py index b9ef2156a551..eff9ff786a48 100644 --- a/qiskit/transpiler/layout.py +++ b/qiskit/transpiler/layout.py @@ -21,6 +21,7 @@ from typing import List from dataclasses import dataclass +from qiskit import circuit from qiskit.circuit.quantumregister import Qubit, QuantumRegister from qiskit.transpiler.exceptions import LayoutError from qiskit.converters import isinstanceint @@ -451,11 +452,11 @@ class TranspileLayout: * :attr:`initial_layout` - This attribute is used to model the permutation caused by the :ref:`layout_stage` it contains a :class:`~.Layout` object that maps the input :class:`~.QuantumCircuit`\s - :class:`~.Qubit` objects to the position in the output + :class:`~.circuit.Qubit` objects to the position in the output :class:`.QuantumCircuit.qubits` list. * :attr:`input_qubit_mapping` - This attribute is used to retain input ordering of the original :class:`~.QuantumCircuit` object. It - maps the virtual :class:`~.Qubit` object from the original circuit + maps the virtual :class:`~.circuit.Qubit` object from the original circuit (and :attr:`initial_layout`) to its corresponding position in :attr:`.QuantumCircuit.qubits` in the original circuit. This is needed when computing the permutation of the :class:`Operator` of @@ -475,7 +476,7 @@ class TranspileLayout: """ initial_layout: Layout - input_qubit_mapping: dict[Qubit, int] + input_qubit_mapping: dict[circuit.Qubit, int] final_layout: Layout | None = None _input_qubit_count: int | None = None _output_qubit_list: List[Qubit] | None = None @@ -483,7 +484,7 @@ class TranspileLayout: def initial_virtual_layout(self, filter_ancillas: bool = False) -> Layout: """Return a :class:`.Layout` object for the initial layout. - This returns a mapping of virtual :class:`~.Qubit` objects in the input + This returns a mapping of virtual :class:`~.circuit.Qubit` objects in the input circuit to the physical qubit selected during layout. This is analogous to the :attr:`.initial_layout` attribute. @@ -492,7 +493,7 @@ def initial_virtual_layout(self, filter_ancillas: bool = False) -> Layout: will be in the returned layout. Any ancilla qubits added to the output circuit will be filtered from the returned object. Returns: - A layout object mapping the input circuit's :class:`~.Qubit` + A layout object mapping the input circuit's :class:`~.circuit.Qubit` objects to the selected physical qubits. """ if not filter_ancillas: diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index f461f335373e..8568b8eac1a2 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -117,6 +117,10 @@ ValidatePulseGates InstructionDurationCheck SetIOLatency + ALAPSchedule + ASAPSchedule + DynamicalDecoupling + AlignMeasures Circuit Analysis ================ @@ -267,6 +271,10 @@ from .scheduling import ConstrainedReschedule from .scheduling import InstructionDurationCheck from .scheduling import SetIOLatency +from .scheduling import ALAPSchedule +from .scheduling import ASAPSchedule +from .scheduling import DynamicalDecoupling +from .scheduling import AlignMeasures # additional utility passes from .utils import CheckMap diff --git a/qiskit/transpiler/passes/analysis/count_ops_longest_path.py b/qiskit/transpiler/passes/analysis/count_ops_longest_path.py index 4e06a2e6dd0a..fb3918bfed12 100644 --- a/qiskit/transpiler/passes/analysis/count_ops_longest_path.py +++ b/qiskit/transpiler/passes/analysis/count_ops_longest_path.py @@ -10,13 +10,13 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Count the operations on the longest path in a DAGcircuit.""" +"""Count the operations on the longest path in a DAGCircuit.""" from qiskit.transpiler.basepasses import AnalysisPass class CountOpsLongestPath(AnalysisPass): - """Count the operations on the longest path in a DAGcircuit. + """Count the operations on the longest path in a :class:`.DAGCircuit`. The result is saved in ``property_set['count_ops_longest_path']`` as an integer. """ diff --git a/qiskit/transpiler/passes/analysis/dag_longest_path.py b/qiskit/transpiler/passes/analysis/dag_longest_path.py index e35d86ca63ba..691910d2628f 100644 --- a/qiskit/transpiler/passes/analysis/dag_longest_path.py +++ b/qiskit/transpiler/passes/analysis/dag_longest_path.py @@ -10,13 +10,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Return the longest path in a DAGcircuit as a list of DAGNodes.""" +"""Return the longest path in a :class:`.DAGCircuit` as a list of DAGNodes.""" from qiskit.transpiler.basepasses import AnalysisPass class DAGLongestPath(AnalysisPass): - """Return the longest path in a DAGcircuit as a list of DAGOpNodes, DAGInNodes, and DAGOutNodes.""" + """Return the longest path in a :class:`.DAGCircuit` as a list of + :class:`.DAGOpNode`\\ s, :class:`.DAGInNode`\\ s, and :class:`.DAGOutNode`\\ s.""" def run(self, dag): """Run the DAGLongestPath pass on `dag`.""" diff --git a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py index ed6400dce94a..36433c71d1da 100644 --- a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py +++ b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py @@ -27,7 +27,7 @@ def __init__(self, target=None, basis_gates=None): Args: target (Target): The target object representing the compilation - target. If specified any multiqubit instructions in the + target. If specified any multi-qubit instructions in the circuit when the pass is run that are supported by the target device will be left in place. If both this and ``basis_gates`` are specified only the target will be checked. diff --git a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py index 20e96f127d05..12e6811a2f03 100644 --- a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py +++ b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py @@ -29,7 +29,7 @@ def __init__(self, equivalence_library, basis_gates=None, target=None, min_qubit equivalence_library (EquivalenceLibrary): The equivalence library which will be used by the BasisTranslator pass. (Instructions in this library will not be unrolled by this pass.) - basis_gates (Optional[list[str]]): Target basis names to unroll to, e.g. `['u3', 'cx']`. + basis_gates (Optional[list[str]]): Target basis names to unroll to, e.g. ``['u3', 'cx']``. Ignored if ``target`` is also specified. target (Optional[Target]): The :class:`~.Target` object corresponding to the compilation target. When specified, any argument specified for ``basis_gates`` is ignored. diff --git a/qiskit/transpiler/passes/basis/unroller.py b/qiskit/transpiler/passes/basis/unroller.py index 25223756c3f4..918c9324f91e 100644 --- a/qiskit/transpiler/passes/basis/unroller.py +++ b/qiskit/transpiler/passes/basis/unroller.py @@ -29,8 +29,8 @@ class Unroller(TransformationPass): @deprecate_func( since="0.45.0", - additional_msg="This has been replaced by the `BasisTranslator` pass." - "This pass will be removed in Qiskit 1.0.", + additional_msg="This has been replaced by the `BasisTranslator` pass " + "and is going to be removed in Qiskit 1.0.", ) def __init__(self, basis=None, target=None): """Unroller initializer. diff --git a/qiskit/transpiler/passes/calibration/pulse_gate.py b/qiskit/transpiler/passes/calibration/pulse_gate.py index 9bfd3c544779..eacabbe89057 100644 --- a/qiskit/transpiler/passes/calibration/pulse_gate.py +++ b/qiskit/transpiler/passes/calibration/pulse_gate.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Instruction scheduel map reference pass.""" +"""Instruction schedule map reference pass.""" from typing import List, Union @@ -57,7 +57,7 @@ def __init__( Args: inst_map: Instruction schedule map that user may override. target: The :class:`~.Target` representing the target backend, if both - ``inst_map`` and this are specified then it updates instructions + ``inst_map`` and ``target`` are specified then it updates instructions in the ``target`` with ``inst_map``. """ super().__init__() diff --git a/qiskit/transpiler/passes/layout/apply_layout.py b/qiskit/transpiler/passes/layout/apply_layout.py index b6b7282e304e..8c6ed2cfec3a 100644 --- a/qiskit/transpiler/passes/layout/apply_layout.py +++ b/qiskit/transpiler/passes/layout/apply_layout.py @@ -36,7 +36,7 @@ class ApplyLayout(TransformationPass): """ def run(self, dag): - """Run the ApplyLayout pass on `dag`. + """Run the ApplyLayout pass on ``dag``. Args: dag (DAGCircuit): DAG to map. @@ -45,7 +45,7 @@ def run(self, dag): DAGCircuit: A mapped DAG (with physical qubits). Raises: - TranspilerError: if no layout is found in `property_set` or no full physical qubits. + TranspilerError: if no layout is found in ``property_set`` or no full physical qubits. """ layout = self.property_set["layout"] if not layout: @@ -77,7 +77,7 @@ def run(self, dag): new_dag.apply_operation_back(node.op, qargs, node.cargs, check=False) else: # First build a new layout object going from: - # old virtual -> old phsyical -> new virtual -> new physical + # old virtual -> old physical -> new virtual -> new physical # to: # old virtual -> new physical full_layout = Layout() diff --git a/qiskit/transpiler/passes/layout/dense_layout.py b/qiskit/transpiler/passes/layout/dense_layout.py index 973947420680..71c69739990d 100644 --- a/qiskit/transpiler/passes/layout/dense_layout.py +++ b/qiskit/transpiler/passes/layout/dense_layout.py @@ -31,9 +31,9 @@ class DenseLayout(AnalysisPass): of the circuit (Qubit). Note: - Even though a 'layout' is not strictly a property of the DAG, + Even though a ``'layout'`` is not strictly a property of the DAG, in the transpiler architecture it is best passed around between passes - by being set in `property_set`. + by being set in ``property_set``. """ def __init__(self, coupling_map=None, backend_prop=None, target=None): diff --git a/qiskit/transpiler/passes/layout/enlarge_with_ancilla.py b/qiskit/transpiler/passes/layout/enlarge_with_ancilla.py index 025f846b054b..fb0b24016f66 100644 --- a/qiskit/transpiler/passes/layout/enlarge_with_ancilla.py +++ b/qiskit/transpiler/passes/layout/enlarge_with_ancilla.py @@ -34,7 +34,7 @@ def run(self, dag): DAGCircuit: An extended DAG. Raises: - TranspilerError: If there is not layout in the property set or not set at init time. + TranspilerError: If there is no layout in the property set or not set at init time. """ layout = self.property_set["layout"] diff --git a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py index 81ae7922806e..72d2c6f2c4d0 100644 --- a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py +++ b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py @@ -107,7 +107,7 @@ def run(self, dag): @staticmethod def validate_layout(layout_qubits, dag_qubits): """ - Checks if all the qregs in layout_qregs already exist in dag_qregs. Otherwise, raise. + Checks if all the qregs in ``layout_qregs`` already exist in ``dag_qregs``. Otherwise, raise. """ for qreg in layout_qubits: if qreg not in dag_qubits: diff --git a/qiskit/transpiler/passes/layout/layout_2q_distance.py b/qiskit/transpiler/passes/layout/layout_2q_distance.py index 6af709851702..fa1759a845f0 100644 --- a/qiskit/transpiler/passes/layout/layout_2q_distance.py +++ b/qiskit/transpiler/passes/layout/layout_2q_distance.py @@ -25,7 +25,7 @@ class Layout2qDistance(AnalysisPass): """Evaluate how good the layout selection was. - Saves in `property_set['layout_score']` (or the property name in property_name) + Saves in ``property_set['layout_score']`` (or the property name in property_name) the sum of distances for each circuit CX. The lower the number, the better the selection. Therefore, 0 is a perfect layout selection. No CX direction is considered. diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 62af8fd57a35..ca71ebee2777 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -60,7 +60,7 @@ class SabreLayout(TransformationPass): This method exploits the reversibility of quantum circuits, and tries to include global circuit information in the choice of initial_layout. - By default this pass will run both layout and routing and will transform the + By default, this pass will run both layout and routing and will transform the circuit so that the layout is applied to the input dag (meaning that the output circuit will have ancilla qubits allocated for unused qubits on the coupling map and the qubits will be reordered to match the mapped physical qubits) and then @@ -152,7 +152,7 @@ def __init__( will be raised if both are used. skip_routing (bool): If this is set ``True`` and ``routing_pass`` is not used then routing will not be applied to the output circuit. Only the layout - will be returned in the property set. This is a tradeoff to run custom + will be set in the property set. This is a tradeoff to run custom routing with multiple layout trials, as using this option will cause SabreLayout to run the routing stage internally but not use that result. @@ -279,6 +279,10 @@ def run(self, dag): } ) + # Add the existing registers to the layout + for qreg in dag.qregs.values(): + self.property_set["layout"].add_register(qreg) + # If skip_routing is set then return the layout in the property set # and throwaway the extra work we did to compute the swap map. # We also skip routing here if there is more than one connected @@ -436,7 +440,7 @@ def _compose_layouts(self, initial_layout, pass_final_layout, qregs): The routing passes internally start with a trivial layout, as the layout gets applied to the circuit prior to running them. So the - "final_layout" they report must be amended to account for the actual + ``"final_layout"`` they report must be amended to account for the actual initial_layout that was selected. """ trivial_layout = Layout.generate_trivial_layout(*qregs) diff --git a/qiskit/transpiler/passes/layout/vf2_layout.py b/qiskit/transpiler/passes/layout/vf2_layout.py index 9eb2b4d68284..4e3077eb1d4d 100644 --- a/qiskit/transpiler/passes/layout/vf2_layout.py +++ b/qiskit/transpiler/passes/layout/vf2_layout.py @@ -12,7 +12,6 @@ """VF2Layout pass to find a layout using subgraph isomorphism""" -import os from enum import Enum import itertools import logging @@ -38,7 +37,7 @@ class VF2LayoutStopReason(Enum): class VF2Layout(AnalysisPass): - """A pass for choosing a Layout of a circuit onto a Coupling graph, as a + """A pass for choosing a Layout of a circuit onto a Coupling graph, as a subgraph isomorphism problem, solved by VF2++. If a solution is found that means there is a "perfect layout" and that no @@ -53,7 +52,7 @@ class VF2Layout(AnalysisPass): * ``"nonexistent solution"``: If no perfect layout was found. * ``">2q gates in basis"``: If VF2Layout can't work with basis - By default this pass will construct a heuristic scoring map based on the + By default, this pass will construct a heuristic scoring map based on the error rates in the provided ``target`` (or ``properties`` if ``target`` is not provided). However, analysis passes can be run prior to this pass and set ``vf2_avg_error_map`` in the property set with a :class:`~.ErrorMap` @@ -139,6 +138,8 @@ def run(self, dag): self.property_set["VF2Layout_stop_reason"] = VF2LayoutStopReason.MORE_THAN_2Q return im_graph, im_graph_node_map, reverse_im_graph_node_map, free_nodes = result + scoring_edge_list = vf2_utils.build_edge_list(im_graph) + scoring_bit_list = vf2_utils.build_bit_list(im_graph, im_graph_node_map) cm_graph, cm_nodes = vf2_utils.shuffle_coupling_graph( self.coupling_map, self.seed, self.strict_direction ) @@ -172,10 +173,6 @@ def run(self, dag): chosen_layout_score = None start_time = time.time() trials = 0 - run_in_parallel = ( - os.getenv("QISKIT_IN_PARALLEL", "FALSE").upper() != "TRUE" - or os.getenv("QISKIT_FORCE_THREADS", "FALSE").upper() == "TRUE" - ) def mapping_to_layout(layout_mapping): return Layout({reverse_im_graph_node_map[k]: v for k, v in layout_mapping.items()}) @@ -204,7 +201,8 @@ def mapping_to_layout(layout_mapping): reverse_im_graph_node_map, im_graph, self.strict_direction, - run_in_parallel, + edge_list=scoring_edge_list, + bit_list=scoring_bit_list, ) # If the layout score is 0 we can't do any better and we'll just # waste time finding additional mappings that will at best match diff --git a/qiskit/transpiler/passes/layout/vf2_post_layout.py b/qiskit/transpiler/passes/layout/vf2_post_layout.py index 96ffc745b451..1f574fdeed10 100644 --- a/qiskit/transpiler/passes/layout/vf2_post_layout.py +++ b/qiskit/transpiler/passes/layout/vf2_post_layout.py @@ -12,7 +12,6 @@ """VF2PostLayout pass to find a layout after transpile using subgraph isomorphism""" -import os from enum import Enum import logging import inspect @@ -35,13 +34,14 @@ class VF2PostLayoutStopReason(Enum): """Stop reasons for VF2PostLayout pass.""" SOLUTION_FOUND = "solution found" + NO_BETTER_SOLUTION_FOUND = "no better solution found" NO_SOLUTION_FOUND = "nonexistent solution" MORE_THAN_2Q = ">2q gates in basis" def _target_match(node_a, node_b): # Node A is the set of operations in the target. Node B is the count dict - # of oeprations on the node or edge in the circuit. + # of operations on the node or edge in the circuit. if isinstance(node_a, set): return node_a.issuperset(node_b.keys()) # Node A is the count dict of operations on the node or edge in the circuit @@ -51,7 +51,7 @@ def _target_match(node_a, node_b): class VF2PostLayout(AnalysisPass): - """A pass for choosing a Layout after transpilation of a circuit onto a + """A pass for improving an existing Layout after transpilation of a circuit onto a Coupling graph, as a subgraph isomorphism problem, solved by VF2++. Unlike the :class:`~.VF2Layout` transpiler pass which is designed to find an @@ -66,17 +66,18 @@ class VF2PostLayout(AnalysisPass): If a solution is found that means there is a lower error layout available for the circuit. If a solution is found the layout will be set in the property set as - ``property_set['post_layout']``. However, if no solution is found, no + ``property_set['post_layout']``. However, if no solution or no better solution is found, no ``property_set['post_layout']`` is set. The stopping reason is set in ``property_set['VF2PostLayout_stop_reason']`` in all the cases and will be one of the values enumerated in ``VF2PostLayoutStopReason`` which has the following values: * ``"solution found"``: If a solution was found. + * ``"no better solution found"``: If the initial layout of the circuit is the best solution. * ``"nonexistent solution"``: If no solution was found. - * ``">2q gates in basis"``: If VF2PostLayout can't work with basis + * ``">2q gates in basis"``: If VF2PostLayout can't work with the basis of the circuit. - By default this pass will construct a heuristic scoring map based on the + By default, this pass will construct a heuristic scoring map based on the error rates in the provided ``target`` (or ``properties`` if ``target`` is not provided). However, analysis passes can be run prior to this pass and set ``vf2_avg_error_map`` in the property set with a :class:`~.ErrorMap` @@ -168,6 +169,8 @@ def run(self, dag): self.property_set["VF2PostLayout_stop_reason"] = VF2PostLayoutStopReason.MORE_THAN_2Q return im_graph, im_graph_node_map, reverse_im_graph_node_map, free_nodes = result + scoring_bit_list = vf2_utils.build_bit_list(im_graph, im_graph_node_map) + scoring_edge_list = vf2_utils.build_edge_list(im_graph) if self.target is not None: # If qargs is None then target is global and ideal so no @@ -179,7 +182,7 @@ def run(self, dag): else: cm_graph = PyGraph(multigraph=False) # If None is present in qargs there are globally defined ideal operations - # we should add these to all entries based on the number of qubits so we + # we should add these to all entries based on the number of qubits, so we # treat that as a valid operation even if there is no scoring for the # strict direction case global_ops = None @@ -253,15 +256,14 @@ def run(self, dag): call_limit=self.call_limit, ) chosen_layout = None - run_in_parallel = ( - os.getenv("QISKIT_IN_PARALLEL", "FALSE").upper() != "TRUE" - or os.getenv("QISKIT_FORCE_THREADS", "FALSE").upper() == "TRUE" - ) try: if self.strict_direction: initial_layout = Layout({bit: index for index, bit in enumerate(dag.qubits)}) chosen_layout_score = self._score_layout( - initial_layout, im_graph_node_map, reverse_im_graph_node_map, im_graph + initial_layout, + im_graph_node_map, + reverse_im_graph_node_map, + im_graph, ) else: initial_layout = { @@ -276,8 +278,11 @@ def run(self, dag): reverse_im_graph_node_map, im_graph, self.strict_direction, - run_in_parallel, + edge_list=scoring_edge_list, + bit_list=scoring_bit_list, ) + chosen_layout = initial_layout + stop_reason = VF2PostLayoutStopReason.NO_BETTER_SOLUTION_FOUND # Circuit not in basis so we have nothing to compare against return here except KeyError: self.property_set[ @@ -292,7 +297,6 @@ def run(self, dag): for mapping in mappings: trials += 1 logger.debug("Running trial: %s", trials) - stop_reason = VF2PostLayoutStopReason.SOLUTION_FOUND layout_mapping = {im_i: cm_nodes[cm_i] for cm_i, im_i in mapping.items()} if self.strict_direction: layout = Layout( @@ -309,7 +313,8 @@ def run(self, dag): reverse_im_graph_node_map, im_graph, self.strict_direction, - run_in_parallel, + edge_list=scoring_edge_list, + bit_list=scoring_bit_list, ) logger.debug("Trial %s has score %s", trials, layout_score) if layout_score < chosen_layout_score: @@ -325,6 +330,7 @@ def run(self, dag): ) chosen_layout = layout chosen_layout_score = layout_score + stop_reason = VF2PostLayoutStopReason.SOLUTION_FOUND if self.max_trials and trials >= self.max_trials: logger.debug("Trial %s is >= configured max trials %s", trials, self.max_trials) @@ -338,9 +344,7 @@ def run(self, dag): self.time_limit, ) break - if chosen_layout is None: - stop_reason = VF2PostLayoutStopReason.NO_SOLUTION_FOUND - else: + if stop_reason == VF2PostLayoutStopReason.SOLUTION_FOUND: chosen_layout = vf2_utils.map_free_qubits( free_nodes, chosen_layout, @@ -364,7 +368,10 @@ def run(self, dag): chosen_layout.add(bit, i) break self.property_set["post_layout"] = chosen_layout - + else: + if chosen_layout is None: + stop_reason = VF2PostLayoutStopReason.NO_SOLUTION_FOUND + # else the initial layout is optimal -> don't set post_layout, return 'no better solution' self.property_set["VF2PostLayout_stop_reason"] = stop_reason def _score_layout(self, layout, bit_map, reverse_bit_map, im_graph): diff --git a/qiskit/transpiler/passes/layout/vf2_utils.py b/qiskit/transpiler/passes/layout/vf2_utils.py index ed14df998caa..99006017482c 100644 --- a/qiskit/transpiler/passes/layout/vf2_utils.py +++ b/qiskit/transpiler/passes/layout/vf2_utils.py @@ -95,6 +95,27 @@ def _visit(dag, weight, wire_map): return im_graph, im_graph_node_map, reverse_im_graph_node_map, free_nodes +def build_edge_list(im_graph): + """Generate an edge list for scoring.""" + return vf2_layout.EdgeList( + [((edge[0], edge[1]), sum(edge[2].values())) for edge in im_graph.edge_index_map().values()] + ) + + +def build_bit_list(im_graph, bit_map): + """Generate a bit list for scoring.""" + bit_list = np.zeros(len(im_graph), dtype=np.int32) + for node_index in bit_map.values(): + try: + bit_list[node_index] = sum(im_graph[node_index].values()) + # If node_index not in im_graph that means there was a standalone + # node we will score/sort separately outside the vf2 mapping, so we + # can skip the hole + except IndexError: + pass + return bit_list + + def score_layout( avg_error_map, layout_mapping, @@ -102,7 +123,9 @@ def score_layout( _reverse_bit_map, im_graph, strict_direction=False, - run_in_parallel=True, + run_in_parallel=False, + edge_list=None, + bit_list=None, ): """Score a layout given an average error map.""" if layout_mapping: @@ -110,18 +133,10 @@ def score_layout( else: size = 0 nlayout = NLayout(layout_mapping, size + 1, size + 1) - bit_list = np.zeros(len(im_graph), dtype=np.int32) - for node_index in bit_map.values(): - try: - bit_list[node_index] = sum(im_graph[node_index].values()) - # If node_index not in im_graph that means there was a standalone - # node we will score/sort separately outside the vf2 mapping, so we - # can skip the hole - except IndexError: - pass - edge_list = { - (edge[0], edge[1]): sum(edge[2].values()) for edge in im_graph.edge_index_map().values() - } + if bit_list is None: + bit_list = build_bit_list(im_graph, bit_map) + if edge_list is None: + edge_list = build_edge_list(im_graph) return vf2_layout.score_layout( bit_list, edge_list, avg_error_map, nlayout, strict_direction, run_in_parallel ) diff --git a/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py b/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py index b3e3f27b89ef..51b39d7e961b 100644 --- a/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py +++ b/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py @@ -19,11 +19,11 @@ class CollectMultiQBlocks(AnalysisPass): """Collect sequences of uninterrupted gates acting on groups of qubits. - max_block_size specifies the maximum number of qubits that can be acted upon + ``max_block_size`` specifies the maximum number of qubits that can be acted upon by any single group of gates Traverse the DAG and find blocks of gates that act consecutively on - groups of qubits. Write the blocks to propert_set as a list of blocks + groups of qubits. Write the blocks to ``property_set`` as a list of blocks of the form:: [[g0, g1, g2], [g4, g5]] @@ -31,7 +31,7 @@ class CollectMultiQBlocks(AnalysisPass): Blocks are reported in a valid topological order. Further, the gates within each block are also reported in topological order Some gates may not be present in any block (e.g. if the number - of operands is greater than max_block_size) + of operands is greater than ``max_block_size``) A Disjoint Set Union data structure (DSU) is used to maintain blocks as gates are processed. This data structure points each qubit to a set at all diff --git a/qiskit/transpiler/passes/optimization/commutation_analysis.py b/qiskit/transpiler/passes/optimization/commutation_analysis.py index 0963a645c7b3..8c34c911a6ca 100644 --- a/qiskit/transpiler/passes/optimization/commutation_analysis.py +++ b/qiskit/transpiler/passes/optimization/commutation_analysis.py @@ -22,7 +22,7 @@ class CommutationAnalysis(AnalysisPass): """Analysis pass to find commutation relations between DAG nodes. - Property_set['commutation_set'] is a dictionary that describes + ``property_set['commutation_set']`` is a dictionary that describes the commutation relations on a given wire, all the gates on a wire are grouped into a set of gates that commute. """ @@ -35,7 +35,7 @@ def run(self, dag): """Run the CommutationAnalysis pass on `dag`. Run the pass on the DAG, and write the discovered commutation relations - into the property_set. + into the ``property_set``. """ # Initiate the commutation set self.property_set["commutation_set"] = defaultdict(list) diff --git a/qiskit/transpiler/passes/optimization/commutative_cancellation.py b/qiskit/transpiler/passes/optimization/commutative_cancellation.py index 777c4ff3dc48..b0eb6bd24137 100644 --- a/qiskit/transpiler/passes/optimization/commutative_cancellation.py +++ b/qiskit/transpiler/passes/optimization/commutative_cancellation.py @@ -50,7 +50,7 @@ def __init__(self, basis_gates=None, target=None): the set intersection between the ``basis_gates`` parameter and the gates in the dag. target (Target): The :class:`~.Target` representing the target backend, if both - ``basis_gates`` and this are specified then this argument will take + ``basis_gates`` and ``target`` are specified then this argument will take precedence and ``basis_gates`` will be ignored. """ super().__init__() diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 6f973ee3fd6f..71065113bb5a 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -55,15 +55,15 @@ def __init__( ): """ConsolidateBlocks initializer. - If `kak_basis_gate` is not `None` it will be used as the basis gate for KAK decomposition. - Otherwise, if `basis_gates` is not `None` a basis gate will be chosen from this list. - Otherwise the basis gate will be `CXGate`. + If ``kak_basis_gate`` is not ``None`` it will be used as the basis gate for KAK decomposition. + Otherwise, if ``basis_gates`` is not ``None`` a basis gate will be chosen from this list. + Otherwise, the basis gate will be :class:`.CXGate`. Args: kak_basis_gate (Gate): Basis gate for KAK decomposition. force_consolidate (bool): Force block consolidation. basis_gates (List(str)): Basis gates from which to choose a KAK gate. - approximation_degree (float): a float between [0.0, 1.0]. Lower approximates more. + approximation_degree (float): a float between $[0.0, 1.0]$. Lower approximates more. target (Target): The target object for the compilation target backend. """ super().__init__() diff --git a/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py b/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py index 387bdc3ba4e0..cfde23061020 100644 --- a/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py +++ b/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py @@ -23,8 +23,8 @@ with simultaneous two-qubit and one-qubit gates. The method ignores crosstalk between pairs of single qubit gates. -The method assumes that all qubits get measured simultaneously whether or not -they need a measurement. This assumption is based on current device properties +The method assumes that all qubits get measured simultaneously, whether +they need a measurement or not. This assumption is based on current device properties and may need to be revised for future device generations. """ @@ -89,7 +89,7 @@ def __init__( inserts the measure gates. If CrosstalkAdaptiveSchedule is made aware of those measurements, it is included in the optimization. target (Target): A target representing the target backend, if both - ``backend_prop`` and this are specified then this argument will take + ``backend_prop`` and ``target`` are specified then this argument will take precedence and ``coupling_map`` will be ignored. Raises: ImportError: if unable to import z3 solver diff --git a/qiskit/transpiler/passes/optimization/cx_cancellation.py b/qiskit/transpiler/passes/optimization/cx_cancellation.py index 6dd4a5364552..df1ffc8ebe23 100644 --- a/qiskit/transpiler/passes/optimization/cx_cancellation.py +++ b/qiskit/transpiler/passes/optimization/cx_cancellation.py @@ -10,14 +10,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Cancel back-to-back `cx` gates in dag.""" +"""Cancel back-to-back ``cx`` gates in dag.""" from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.passes.utils import control_flow class CXCancellation(TransformationPass): - """Cancel back-to-back `cx` gates in dag.""" + """Cancel back-to-back ``cx`` gates in dag.""" @control_flow.trivial_recurse def run(self, dag): diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index 5f4be27d220e..9352ce08f365 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -41,7 +41,7 @@ def __init__(self, instruction_schedule_map=None, target=None): instruction_schedule_map (InstructionScheduleMap): the mapping from circuit :class:`~.circuit.Instruction` names and arguments to :class:`.Schedule`\\ s. target (Target): The :class:`~.Target` representing the target backend, if both - ``instruction_schedule_map`` and this are specified then this argument will take + ``instruction_schedule_map`` and ``target`` are specified then this argument will take precedence and ``instruction_schedule_map`` will be ignored. """ super().__init__() diff --git a/qiskit/transpiler/passes/optimization/inverse_cancellation.py b/qiskit/transpiler/passes/optimization/inverse_cancellation.py index 35dae7d85f07..560cdcd707f4 100644 --- a/qiskit/transpiler/passes/optimization/inverse_cancellation.py +++ b/qiskit/transpiler/passes/optimization/inverse_cancellation.py @@ -58,12 +58,16 @@ def __init__(self, gates_to_cancel: List[Union[Gate, Tuple[Gate, Gate]]]): self.self_inverse_gates = [] self.inverse_gate_pairs = [] + self.self_inverse_gate_names = set() + self.inverse_gate_pairs_names = set() for gates in gates_to_cancel: if isinstance(gates, Gate): self.self_inverse_gates.append(gates) + self.self_inverse_gate_names.add(gates.name) else: self.inverse_gate_pairs.append(gates) + self.inverse_gate_pairs_names.update(x.name for x in gates) super().__init__() @@ -76,11 +80,13 @@ def run(self, dag: DAGCircuit): Returns: DAGCircuit: Transformed DAG. """ + if self.self_inverse_gates: + dag = self._run_on_self_inverse(dag) + if self.inverse_gate_pairs: + dag = self._run_on_inverse_pairs(dag) + return dag - dag = self._run_on_self_inverse(dag, self.self_inverse_gates) - return self._run_on_inverse_pairs(dag, self.inverse_gate_pairs) - - def _run_on_self_inverse(self, dag: DAGCircuit, self_inverse_gates: List[Gate]): + def _run_on_self_inverse(self, dag: DAGCircuit): """ Run self-inverse gates on `dag`. @@ -91,14 +97,27 @@ def _run_on_self_inverse(self, dag: DAGCircuit, self_inverse_gates: List[Gate]): Returns: DAGCircuit: Transformed DAG. """ + op_counts = dag.count_ops() + if not self.self_inverse_gate_names.intersection(op_counts): + return dag # Sets of gate runs by name, for instance: [{(H 0, H 0), (H 1, H 1)}, {(X 0, X 0}] - gate_runs_sets = [dag.collect_runs([gate.name]) for gate in self_inverse_gates] - for gate_runs in gate_runs_sets: + for gate in self.self_inverse_gates: + gate_name = gate.name + gate_count = op_counts.get(gate_name, 0) + if gate_count <= 1: + continue + gate_runs = dag.collect_runs([gate_name]) for gate_cancel_run in gate_runs: partitions = [] chunk = [] for i in range(len(gate_cancel_run) - 1): - chunk.append(gate_cancel_run[i]) + if gate_cancel_run[i].op == gate: + chunk.append(gate_cancel_run[i]) + else: + if chunk: + partitions.append(chunk) + chunk = [] + continue if gate_cancel_run[i].qargs != gate_cancel_run[i + 1].qargs: partitions.append(chunk) chunk = [] @@ -112,7 +131,7 @@ def _run_on_self_inverse(self, dag: DAGCircuit, self_inverse_gates: List[Gate]): dag.remove_op_node(node) return dag - def _run_on_inverse_pairs(self, dag: DAGCircuit, inverse_gate_pairs: List[Tuple[Gate, Gate]]): + def _run_on_inverse_pairs(self, dag: DAGCircuit): """ Run inverse gate pairs on `dag`. @@ -123,8 +142,16 @@ def _run_on_inverse_pairs(self, dag: DAGCircuit, inverse_gate_pairs: List[Tuple[ Returns: DAGCircuit: Transformed DAG. """ - for pair in inverse_gate_pairs: - gate_cancel_runs = dag.collect_runs([pair[0].name, pair[1].name]) + op_counts = dag.count_ops() + if not self.inverse_gate_pairs_names.intersection(op_counts): + return dag + + for pair in self.inverse_gate_pairs: + gate_0_name = pair[0].name + gate_1_name = pair[1].name + if gate_0_name not in op_counts or gate_1_name not in op_counts: + continue + gate_cancel_runs = dag.collect_runs([gate_0_name, gate_1_name]) for dag_nodes in gate_cancel_runs: i = 0 while i < len(dag_nodes) - 1: diff --git a/qiskit/transpiler/passes/optimization/normalize_rx_angle.py b/qiskit/transpiler/passes/optimization/normalize_rx_angle.py index 33b381fc2283..b6f36e07de36 100644 --- a/qiskit/transpiler/passes/optimization/normalize_rx_angle.py +++ b/qiskit/transpiler/passes/optimization/normalize_rx_angle.py @@ -57,7 +57,7 @@ def __init__(self, target=None, resolution_in_radian=0): corresponding RX gates with SX and X gates. resolution_in_radian (float): Resolution for RX rotation angle quantization. If set to zero, this pass won't modify the rotation angles in the given DAG. - (=Provides aribitary-angle RX) + (=Provides arbitrary-angle RX) """ super().__init__() self.target = target @@ -69,32 +69,24 @@ def quantize_angles(self, qubit, original_angle): that differ within a resolution provided by the user. Args: - qubit (Qubit): This will be the dict key to access the list of quantized rotation angles. + qubit (qiskit.circuit.Qubit): This will be the dict key to access the list of + quantized rotation angles. original_angle (float): Original rotation angle, before quantization. Returns: float: Quantized angle. """ - # check if there is already a calibration for a simliar angle - try: - angles = self.already_generated[qubit] # 1d ndarray of already generated angles - similar_angle = angles[ - np.isclose(angles, original_angle, atol=self.resolution_in_radian / 2) - ] - quantized_angle = ( - float(similar_angle[0]) if len(similar_angle) > 1 else float(similar_angle) - ) - except KeyError: - quantized_angle = original_angle - self.already_generated[qubit] = np.array([quantized_angle]) - except TypeError: - quantized_angle = original_angle - self.already_generated[qubit] = np.append( - self.already_generated[qubit], quantized_angle - ) - - return quantized_angle + if (angles := self.already_generated.get(qubit)) is None: + self.already_generated[qubit] = np.array([original_angle]) + return original_angle + similar_angles = angles[ + np.isclose(angles, original_angle, atol=self.resolution_in_radian / 2) + ] + if similar_angles.size == 0: + self.already_generated[qubit] = np.append(angles, original_angle) + return original_angle + return float(similar_angles[0]) def run(self, dag): """Run the NormalizeRXAngle pass on ``dag``. diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py b/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py index fea459b41b9e..450490734e46 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py @@ -46,10 +46,10 @@ class Optimize1qGatesSimpleCommutation(TransformationPass): """ - Optimizes 1Q gate strings interrupted by 2Q gates by commuting the components and re- - synthesizing the results. The commutation rules are stored in `commutation_table`. + Optimizes 1Q gate strings interrupted by 2Q gates by commuting the components and + resynthesizing the results. The commutation rules are stored in ``commutation_table``. - NOTE: In addition to those mentioned in `commutation_table`, this pass has some limitations: + NOTE: In addition to those mentioned in ``commutation_table``, this pass has some limitations: + Does not handle multiple commutations in a row without intermediate progress. + Can only commute into positions where there are pre-existing runs. + Does not exhaustively test all the different ways commuting gates can be assigned to @@ -58,7 +58,7 @@ class Optimize1qGatesSimpleCommutation(TransformationPass): barriers.) """ - # NOTE: A run from `dag.collect_1q_runs` is always nonempty, so we sometimes use an empty list + # NOTE: A run from dag.collect_1q_runs is always nonempty, so we sometimes use an empty list # to signify the absence of a run. def __init__(self, basis=None, run_to_completion=False, target=None): @@ -83,7 +83,7 @@ def _find_adjoining_run(dag, runs, run, front=True): Finds the run which abuts `run` from the front (or the rear if `front == False`), separated by a blocking node. - Returns a pair of the abutting multi-qubit gate and the run which it separates from this + Returns a pair of the abutting multiqubit gate and the run which it separates from this one. The next run can be the empty list `[]` if it is absent. """ edge_node = run[0] if front else run[-1] diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index 854fe8204551..f96ed999061d 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -59,9 +59,9 @@ class Optimize1qGatesDecomposition(TransformationPass): """Optimize chains of single-qubit gates by combining them into a single gate. - The decision to replace the original chain with a new resynthesis depends on: + The decision to replace the original chain with a new re-synthesis depends on: - whether the original chain was out of basis: replace - - whether the original chain was in basis but resynthesis is lower error: replace + - whether the original chain was in basis but re-synthesis is lower error: replace - whether the original chain contains a pulse gate: do not replace - whether the original chain amounts to identity: replace with null @@ -110,12 +110,12 @@ def _build_error_map(self): def _resynthesize_run(self, matrix, qubit=None): """ - Resynthesizes one 2x2 `matrix`, typically extracted via `dag.collect_1q_runs`. + Re-synthesizes one 2x2 `matrix`, typically extracted via `dag.collect_1q_runs`. Returns the newly synthesized circuit in the indicated basis, or None if no synthesis routine applied. - When multiple synthesis options are available, it prefers the one with lowest + When multiple synthesis options are available, it prefers the one with the lowest error when the circuit is applied to `qubit`. """ if self._target: diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py index 1dd03a738e32..3d080219274b 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py @@ -43,7 +43,7 @@ def __init__(self, basis=None, eps=1e-15, target=None): the set `{'u1','u2','u3', 'u', 'p'}`. eps (float): EPS to check against target (Target): The :class:`~.Target` representing the target backend, if both - ``basis`` and this are specified then this argument will take + ``basis`` and ``target`` are specified then this argument will take precedence and ``basis`` will be ignored. """ super().__init__() @@ -61,7 +61,7 @@ def run(self, dag): DAGCircuit: the optimized DAG. Raises: - TranspilerError: if YZY and ZYZ angles do not give same rotation matrix. + TranspilerError: if ``YZY`` and ``ZYZ`` angles do not give same rotation matrix. """ use_u = "u" in self.basis use_p = "p" in self.basis diff --git a/qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py b/qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py index d49485784026..faf0ed8de6b0 100644 --- a/qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py +++ b/qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py @@ -26,7 +26,7 @@ class ResetAfterMeasureSimplification(TransformationPass): This optimization is suitable for use on IBM Quantum systems where the reset operation is performed by a measurement followed by a conditional - x-gate. It might not be desireable on other backends if reset is implemented + x-gate. It might not be desirable on other backends if reset is implemented differently. """ diff --git a/qiskit/transpiler/passes/optimization/template_optimization.py b/qiskit/transpiler/passes/optimization/template_optimization.py index 0aeea99ab3c6..f4274d2331ec 100644 --- a/qiskit/transpiler/passes/optimization/template_optimization.py +++ b/qiskit/transpiler/passes/optimization/template_optimization.py @@ -57,16 +57,16 @@ def __init__( heuristics_backward_param (list[int]): [length, survivor] Those are the parameters for applying heuristics on the backward part of the algorithm. This part of the algorithm creates a tree of matching scenario. This tree grows exponentially. The - heuristics evaluates which scenarios have the longest match and keep only those. + heuristics evaluate which scenarios have the longest match and keep only those. The length is the interval in the tree for cutting it and survivor is the number - of scenarios that are kept. We advice to use l=3 and s=1 to have serious time + of scenarios that are kept. We advise to use l=3 and s=1 to have serious time advantage. We remind that the heuristics implies losing a part of the maximal matches. Check reference for more details. heuristics_qubits_param (list[int]): [length] The heuristics for the qubit choice make guesses from the dag dependency of the circuit in order to limit the number of qubit configurations to explore. The length is the number of successors or not predecessors that will be explored in the dag dependency of the circuit, each - qubits of the nodes are added to the set of authorized qubits. We advice to use + qubits of the nodes are added to the set of authorized qubits. We advise to use length=1. Check reference for more details. user_cost_dict (Dict[str, int]): quantum cost dictionary passed to TemplateSubstitution to configure its behavior. This will override any default values if None diff --git a/qiskit/transpiler/passes/routing/basic_swap.py b/qiskit/transpiler/passes/routing/basic_swap.py index af721ee51c7d..c73b2fb009c6 100644 --- a/qiskit/transpiler/passes/routing/basic_swap.py +++ b/qiskit/transpiler/passes/routing/basic_swap.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Map (with minimum effort) a DAGCircuit onto a `coupling_map` adding swap gates.""" +"""Map (with minimum effort) a DAGCircuit onto a ``coupling_map`` adding swap gates.""" from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError @@ -22,7 +22,7 @@ class BasicSwap(TransformationPass): - """Map (with minimum effort) a DAGCircuit onto a `coupling_map` adding swap gates. + """Map (with minimum effort) a DAGCircuit onto a ``coupling_map`` adding swap gates. The basic mapper is a minimum effort to insert swap gates to map the DAG onto a coupling map. When a cx is not in the coupling map possibilities, it inserts @@ -34,7 +34,7 @@ def __init__(self, coupling_map, fake_run=False): Args: coupling_map (Union[CouplingMap, Target]): Directed graph represented a coupling map. - fake_run (bool): if true, it only pretend to do routing, i.e., no + fake_run (bool): if true, it will only pretend to do routing, i.e., no swap is effectively added. """ super().__init__() @@ -57,7 +57,7 @@ def run(self, dag): Raises: TranspilerError: if the coupling map or the layout are not - compatible with the DAG, or if the coupling_map=None. + compatible with the DAG, or if the ``coupling_map=None``. """ if self.fake_run: return self._fake_run(dag) diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py index 60b5cf9e3ad3..65d39abf1ab7 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py @@ -33,7 +33,7 @@ class Commuting2qGateRouter(TransformationPass): The mapping to the coupling map is done using swap strategies, see :class:`.SwapStrategy`. The swap strategy should suit the problem and the coupling map. This transpiler pass should ideally be executed before the quantum circuit is enlarged with any idle ancilla - qubits. Otherwise we may swap qubits outside of the portion of the chip we want to use. + qubits. Otherwise, we may swap qubits outside the portion of the chip we want to use. Therefore, the swap strategy and its associated coupling map do not represent physical qubits. Instead, they represent an intermediate mapping that corresponds to the physical qubits once the initial layout is applied. The example below shows how to map a four @@ -111,7 +111,7 @@ def __init__( Args: swap_strategy: An instance of a :class:`.SwapStrategy` that holds the swap layers that are used, and the order in which to apply them, to map the instruction to - the hardware. If this field is not given if should be contained in the + the hardware. If this field is not given, it should be contained in the property set of the pass. This allows other passes to determine the most appropriate swap strategy at run-time. edge_coloring: An optional edge coloring of the coupling map (I.e. no two edges that diff --git a/qiskit/transpiler/passes/routing/layout_transformation.py b/qiskit/transpiler/passes/routing/layout_transformation.py index 218e6ca958d3..42bf44e13f2a 100644 --- a/qiskit/transpiler/passes/routing/layout_transformation.py +++ b/qiskit/transpiler/passes/routing/layout_transformation.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Map (with minimum effort) a DAGCircuit onto a `coupling_map` adding swap gates.""" +"""Map (with minimum effort) a DAGCircuit onto a ``coupling_map`` adding swap gates.""" from __future__ import annotations import numpy as np @@ -49,7 +49,7 @@ def __init__( to_layout (Union[Layout, str]): The final layout of qubits on physical qubits. - If the type is str, look up `property_set` when this pass runs. + If the type is str, look up ``property_set`` when this pass runs. seed (Union[int, np.random.default_rng]): Seed to use for random trials. diff --git a/qiskit/transpiler/passes/routing/lookahead_swap.py b/qiskit/transpiler/passes/routing/lookahead_swap.py index 0893736a2aca..a961162ab802 100644 --- a/qiskit/transpiler/passes/routing/lookahead_swap.py +++ b/qiskit/transpiler/passes/routing/lookahead_swap.py @@ -90,7 +90,7 @@ def __init__(self, coupling_map, search_depth=4, search_width=4, fake_run=False) coupling_map (Union[CouplingMap, Target]): CouplingMap of the target backend. search_depth (int): lookahead tree depth when ranking best SWAP options. search_width (int): lookahead tree width when ranking best SWAP options. - fake_run (bool): if true, it only pretend to do routing, i.e., no + fake_run (bool): if true, it will only pretend to do routing, i.e., no swap is effectively added. """ diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index b78bd539b99b..3f83a02237cd 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -339,9 +339,9 @@ def _apply_sabre_result( Rust run of the Sabre routing algorithm. initial_layout (NLayout): a Rust-space mapping of virtual indices (i.e. those of the qubits in ``in_dag``) to physical ones. - physical_qubits (list[Qubit]): an indexable sequence of :class:`.Qubit` objects representing - the physical qubits of the circuit. Note that disjoint-coupling handling can mean that - these are not strictly a "canonical physical register" in order. + physical_qubits (list[Qubit]): an indexable sequence of :class:`.circuit.Qubit` objects + representing the physical qubits of the circuit. Note that disjoint-coupling + handling can mean that these are not strictly a "canonical physical register" in order. circuit_to_dag_dict (Mapping[int, DAGCircuit]): a mapping of the Python object identity (as returned by :func:`id`) of a control-flow block :class:`.QuantumCircuit` to a :class:`.DAGCircuit` that represents the same thing. diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index 90001bb06cc8..52f0d569931d 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Map a DAGCircuit onto a `coupling_map` adding swap gates.""" +"""Map a DAGCircuit onto a ``coupling_map`` adding swap gates.""" import itertools import logging @@ -72,7 +72,7 @@ def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_l map. trials (int): maximum number of iterations to attempt seed (int): seed for random number generator - fake_run (bool): if true, it only pretend to do routing, i.e., no + fake_run (bool): if true, it will only pretend to do routing, i.e., no swap is effectively added. initial_layout (Layout): starting layout at beginning of pass. """ diff --git a/qiskit/transpiler/passes/scheduling/__init__.py b/qiskit/transpiler/passes/scheduling/__init__.py index 69485e88e4cf..6283faff0001 100644 --- a/qiskit/transpiler/passes/scheduling/__init__.py +++ b/qiskit/transpiler/passes/scheduling/__init__.py @@ -12,6 +12,9 @@ """Module containing circuit scheduling passes.""" +from .alap import ALAPSchedule +from .asap import ASAPSchedule +from .dynamical_decoupling import DynamicalDecoupling from .scheduling import ALAPScheduleAnalysis, ASAPScheduleAnalysis, SetIOLatency from .time_unit_conversion import TimeUnitConversion from .padding import PadDelay, PadDynamicalDecoupling @@ -21,3 +24,4 @@ from . import alignments as instruction_alignments # TODO Deprecated pass. Will be removed after deprecation period. +from .alignments import AlignMeasures diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py new file mode 100644 index 000000000000..9ee0f4988b4a --- /dev/null +++ b/qiskit/transpiler/passes/scheduling/alap.py @@ -0,0 +1,155 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""ALAP Scheduling.""" + +from qiskit.circuit import Delay, Qubit, Measure +from qiskit.dagcircuit import DAGCircuit +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.utils.deprecation import deprecate_func + +from .base_scheduler import BaseSchedulerTransform + + +class ALAPSchedule(BaseSchedulerTransform): + """ALAP Scheduling pass, which schedules the **stop** time of instructions as late as possible. + + See :class:`~qiskit.transpiler.passes.scheduling.base_scheduler.BaseSchedulerTransform` for the + detailed behavior of the control flow operation, i.e. ``c_if``. + """ + + @deprecate_func( + additional_msg=( + "Instead, use :class:`~.ALAPScheduleAnalysis`, which is an " + "analysis pass that requires a padding pass to later modify the circuit." + ), + since="0.21.0", + pending=True, + ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def run(self, dag): + """Run the ALAPSchedule pass on `dag`. + + Args: + dag (DAGCircuit): DAG to schedule. + + Returns: + DAGCircuit: A scheduled DAG. + + Raises: + TranspilerError: if the circuit is not mapped on physical qubits. + TranspilerError: if conditional bit is added to non-supported instruction. + """ + if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: + raise TranspilerError("ALAP schedule runs on physical circuits only") + + time_unit = self.property_set["time_unit"] + new_dag = DAGCircuit() + for qreg in dag.qregs.values(): + new_dag.add_qreg(qreg) + for creg in dag.cregs.values(): + new_dag.add_creg(creg) + + idle_before = {q: 0 for q in dag.qubits + dag.clbits} + for node in reversed(list(dag.topological_op_nodes())): + op_duration = self._get_node_duration(node, dag) + + # compute t0, t1: instruction interval, note that + # t0: start time of instruction + # t1: end time of instruction + + # since this is alap scheduling, node is scheduled in reversed topological ordering + # and nodes are packed from the very end of the circuit. + # the physical meaning of t0 and t1 is flipped here. + if isinstance(node.op, self.CONDITIONAL_SUPPORTED): + t0q = max(idle_before[q] for q in node.qargs) + if node.op.condition_bits: + # conditional is bit tricky due to conditional_latency + t0c = max(idle_before[c] for c in node.op.condition_bits) + # Assume following case (t0c > t0q): + # + # |t0q + # Q ░░░░░░░░░░░░░▒▒▒ + # C ░░░░░░░░▒▒▒▒▒▒▒▒ + # |t0c + # + # In this case, there is no actual clbit read before gate. + # + # |t0q' = t0c - conditional_latency + # Q ░░░░░░░░▒▒▒░░▒▒▒ + # C ░░░░░░▒▒▒▒▒▒▒▒▒▒ + # |t1c' = t0c + conditional_latency + # + # rather than naively doing + # + # |t1q' = t0c + duration + # Q ░░░░░▒▒▒░░░░░▒▒▒ + # C ░░▒▒░░░░▒▒▒▒▒▒▒▒ + # |t1c' = t0c + duration + conditional_latency + # + t0 = max(t0q, t0c - op_duration) + t1 = t0 + op_duration + for clbit in node.op.condition_bits: + idle_before[clbit] = t1 + self.conditional_latency + else: + t0 = t0q + t1 = t0 + op_duration + else: + if node.op.condition_bits: + raise TranspilerError( + f"Conditional instruction {node.op.name} is not supported in ALAP scheduler." + ) + + if isinstance(node.op, Measure): + # clbit time is always right (alap) justified + t0 = max(idle_before[bit] for bit in node.qargs + node.cargs) + t1 = t0 + op_duration + # + # |t1 = t0 + duration + # Q ░░░░░▒▒▒▒▒▒▒▒▒▒▒ + # C ░░░░░░░░░▒▒▒▒▒▒▒ + # |t0 + (duration - clbit_write_latency) + # + for clbit in node.cargs: + idle_before[clbit] = t0 + (op_duration - self.clbit_write_latency) + else: + # It happens to be directives such as barrier + t0 = max(idle_before[bit] for bit in node.qargs + node.cargs) + t1 = t0 + op_duration + + for bit in node.qargs: + delta = t0 - idle_before[bit] + if delta > 0 and self._delay_supported(dag.find_bit(bit).index): + new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [], check=False) + idle_before[bit] = t1 + + new_dag.apply_operation_front(node.op, node.qargs, node.cargs, check=False) + + circuit_duration = max(idle_before.values()) + for bit, before in idle_before.items(): + delta = circuit_duration - before + if not (delta > 0 and isinstance(bit, Qubit)): + continue + if self._delay_supported(dag.find_bit(bit).index): + new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [], check=False) + + new_dag.name = dag.name + new_dag.metadata = dag.metadata + new_dag.calibrations = dag.calibrations + + # set circuit duration and unit to indicate it is scheduled + new_dag.duration = circuit_duration + new_dag.unit = time_unit + + return new_dag diff --git a/qiskit/transpiler/passes/scheduling/alignments/__init__.py b/qiskit/transpiler/passes/scheduling/alignments/__init__.py index a25ec01bc1cf..513144937ab5 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/__init__.py +++ b/qiskit/transpiler/passes/scheduling/alignments/__init__.py @@ -78,3 +78,4 @@ from .check_durations import InstructionDurationCheck from .pulse_gate_validation import ValidatePulseGates from .reschedule import ConstrainedReschedule +from .align_measures import AlignMeasures diff --git a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py new file mode 100644 index 000000000000..668d65f6abd5 --- /dev/null +++ b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py @@ -0,0 +1,256 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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. + +"""Align measurement instructions.""" +from __future__ import annotations +import itertools +import warnings +from collections import defaultdict +from collections.abc import Iterable +from typing import Type + +from qiskit.circuit.quantumcircuit import ClbitSpecifier, QubitSpecifier + +from qiskit.circuit.delay import Delay +from qiskit.circuit.measure import Measure +from qiskit.circuit.parameterexpression import ParameterExpression +from qiskit.dagcircuit import DAGCircuit +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.utils.deprecation import deprecate_func + + +class AlignMeasures(TransformationPass): + """Measurement alignment. + + This is a control electronics aware optimization pass. + + In many quantum computing architectures gates (instructions) are implemented with + shaped analog stimulus signals. These signals are digitally stored in the + waveform memory of the control electronics and converted into analog voltage signals + by electronic components called digital to analog converters (DAC). + + In a typical hardware implementation of superconducting quantum processors, + a single qubit instruction is implemented by a + microwave signal with the duration of around several tens of ns with a per-sample + time resolution of ~0.1-10ns, as reported by ``backend.configuration().dt``. + In such systems requiring higher DAC bandwidth, control electronics often + defines a `pulse granularity`, in other words a data chunk, to allow the DAC to + perform the signal conversion in parallel to gain the bandwidth. + + Measurement alignment is required if a backend only allows triggering ``measure`` + instructions at a certain multiple value of this pulse granularity. + This value is usually provided by ``backend.configuration().timing_constraints``. + + In Qiskit SDK, the duration of delay can take arbitrary value in units of ``dt``, + thus circuits involving delays may violate the above alignment constraint (i.e. misalignment). + This pass shifts measurement instructions to a new time position to fix the misalignment, + by inserting extra delay right before the measure instructions. + The input of this pass should be scheduled :class:`~qiskit.dagcircuit.DAGCircuit`, + thus one should select one of the scheduling passes + (:class:`~qiskit.transpiler.passes.ALAPSchedule` or + :class:`~qiskit.trasnpiler.passes.ASAPSchedule`) before calling this. + + Examples: + We assume executing the following circuit on a backend with ``alignment=16``. + + .. parsed-literal:: + + ┌───┐┌────────────────┐┌─┐ + q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├ + └───┘└────────────────┘└╥┘ + c: 1/════════════════════════╩═ + 0 + + Note that delay of 100 dt induces a misalignment of 4 dt at the measurement. + This pass appends an extra 12 dt time shift to the input circuit. + + .. parsed-literal:: + + ┌───┐┌────────────────┐┌─┐ + q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├ + └───┘└────────────────┘└╥┘ + c: 1/════════════════════════╩═ + 0 + + This pass always inserts a positive delay before measurements + rather than reducing other delays. + + Notes: + The Backend may allow users to execute circuits violating the alignment constraint. + However, it may return meaningless measurement data mainly due to the phase error. + """ + + @deprecate_func( + additional_msg=( + "Instead, use :class:`~.ConstrainedReschedule`, which performs the same function " + "but also supports aligning to additional timing constraints." + ), + since="0.21.0", + pending=True, + ) + def __init__(self, alignment: int = 1): + """Create new pass. + + Args: + alignment: Integer number representing the minimum time resolution to + trigger measure instruction in units of ``dt``. This value depends on + the control electronics of your quantum processor. + """ + super().__init__() + self.alignment = alignment + + def run(self, dag: DAGCircuit): + """Run the measurement alignment pass on `dag`. + + Args: + dag (DAGCircuit): DAG to be checked. + + Returns: + DAGCircuit: DAG with consistent timing and op nodes annotated with duration. + + Raises: + TranspilerError: If circuit is not scheduled. + """ + time_unit = self.property_set["time_unit"] + + if not _check_alignment_required(dag, self.alignment, Measure): + # return input as-is to avoid unnecessary scheduling. + # because following procedure regenerate new DAGCircuit, + # we should avoid continuing if not necessary from performance viewpoint. + return dag + + # if circuit is not yet scheduled, schedule with ALAP method + if dag.duration is None: + raise TranspilerError( + f"This circuit {dag.name} may involve a delay instruction violating the " + "pulse controller alignment. To adjust instructions to " + "right timing, you should call one of scheduling passes first. " + "This is usually done by calling transpiler with scheduling_method='alap'." + ) + + # the following lines are basically copied from ASAPSchedule pass + # + # * some validations for non-scheduled nodes are dropped, since we assume scheduled input + # * pad_with_delay is called only with non-delay node to avoid consecutive delay + new_dag = dag.copy_empty_like() + + qubit_time_available: dict[QubitSpecifier, int] = defaultdict(int) # to track op start time + qubit_stop_times: dict[QubitSpecifier, int] = defaultdict( + int + ) # to track delay start time for padding + clbit_readable: dict[ClbitSpecifier, int] = defaultdict(int) + clbit_writeable: dict[ClbitSpecifier, int] = defaultdict(int) + + def pad_with_delays(qubits: Iterable[QubitSpecifier], until, unit) -> None: + """Pad idle time-slots in ``qubits`` with delays in ``unit`` until ``until``.""" + for q in qubits: + if qubit_stop_times[q] < until: + idle_duration = until - qubit_stop_times[q] + new_dag.apply_operation_back(Delay(idle_duration, unit), (q,), check=False) + + for node in dag.topological_op_nodes(): + # choose appropriate clbit available time depending on op + clbit_time_available = ( + clbit_writeable if isinstance(node.op, Measure) else clbit_readable + ) + # correction to change clbit start time to qubit start time + delta = node.op.duration if isinstance(node.op, Measure) else 0 + start_time = max( + itertools.chain( + (qubit_time_available[q] for q in node.qargs), + ( + clbit_time_available[c] - delta + for c in node.cargs + tuple(node.op.condition_bits) + ), + ) + ) + + if isinstance(node.op, Measure): + if start_time % self.alignment != 0: + start_time = ((start_time // self.alignment) + 1) * self.alignment + + if not isinstance(node.op, Delay): # exclude delays for combining consecutive delays + pad_with_delays(node.qargs, until=start_time, unit=time_unit) + new_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) + + stop_time = start_time + node.op.duration + # update time table + for q in node.qargs: + qubit_time_available[q] = stop_time + if not isinstance(node.op, Delay): + qubit_stop_times[q] = stop_time + for c in node.cargs: # measure + clbit_writeable[c] = clbit_readable[c] = stop_time + for c in node.op.condition_bits: # conditional op + clbit_writeable[c] = max(start_time, clbit_writeable[c]) + + working_qubits = qubit_time_available.keys() + circuit_duration = max(qubit_time_available[q] for q in working_qubits) + pad_with_delays(new_dag.qubits, until=circuit_duration, unit=time_unit) + + new_dag.name = dag.name + new_dag.metadata = dag.metadata + + # set circuit duration and unit to indicate it is scheduled + new_dag.duration = circuit_duration + new_dag.unit = time_unit + + return new_dag + + +def _check_alignment_required( + dag: DAGCircuit, + alignment: int, + instructions: Type | list[Type], +) -> bool: + """Check DAG nodes and return a boolean representing if instruction scheduling is necessary. + + Args: + dag: DAG circuit to check. + alignment: Instruction alignment condition. + instructions: Target instructions. + + Returns: + If instruction scheduling is necessary. + """ + if not isinstance(instructions, list): + instructions = [instructions] + + if alignment == 1: + # disable alignment if arbitrary t0 value can be used + return False + + if all(len(dag.op_nodes(inst)) == 0 for inst in instructions): + # disable alignment if target instruction is not involved + return False + + # check delay durations + for delay_node in dag.op_nodes(Delay): + duration = delay_node.op.duration + if isinstance(duration, ParameterExpression): + # duration is parametrized: + # raise user warning if backend alignment is not 1. + warnings.warn( + f"Parametrized delay with {repr(duration)} is found in circuit {dag.name}. " + f"This backend requires alignment={alignment}. " + "Please make sure all assigned values are multiple values of the alignment.", + UserWarning, + ) + else: + # duration is bound: + # check duration and trigger alignment if it violates constraint + if duration % alignment != 0: + return True + + # disable alignment if all delays are multiple values of the alignment + return False diff --git a/qiskit/transpiler/passes/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/asap.py new file mode 100644 index 000000000000..cebc32af71a8 --- /dev/null +++ b/qiskit/transpiler/passes/scheduling/asap.py @@ -0,0 +1,177 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""ASAP Scheduling.""" + +from qiskit.circuit import Delay, Qubit, Measure +from qiskit.dagcircuit import DAGCircuit +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.utils.deprecation import deprecate_func + +from .base_scheduler import BaseSchedulerTransform + + +class ASAPSchedule(BaseSchedulerTransform): + """ASAP Scheduling pass, which schedules the start time of instructions as early as possible.. + + See :class:`~qiskit.transpiler.passes.scheduling.base_scheduler.BaseSchedulerTransform` for the + detailed behavior of the control flow operation, i.e. ``c_if``. + + .. note:: + + This base class has been superseded by :class:`~.ASAPScheduleAnalysis` and + the new scheduling workflow. It will be deprecated and subsequently + removed in a future release. + """ + + @deprecate_func( + additional_msg=( + "Instead, use :class:`~.ASAPScheduleAnalysis`, which is an " + "analysis pass that requires a padding pass to later modify the circuit." + ), + since="0.21.0", + pending=True, + ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def run(self, dag): + """Run the ASAPSchedule pass on `dag`. + + Args: + dag (DAGCircuit): DAG to schedule. + + Returns: + DAGCircuit: A scheduled DAG. + + Raises: + TranspilerError: if the circuit is not mapped on physical qubits. + TranspilerError: if conditional bit is added to non-supported instruction. + """ + if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: + raise TranspilerError("ASAP schedule runs on physical circuits only") + + time_unit = self.property_set["time_unit"] + + new_dag = DAGCircuit() + for qreg in dag.qregs.values(): + new_dag.add_qreg(qreg) + for creg in dag.cregs.values(): + new_dag.add_creg(creg) + + idle_after = {q: 0 for q in dag.qubits + dag.clbits} + for node in dag.topological_op_nodes(): + op_duration = self._get_node_duration(node, dag) + + # compute t0, t1: instruction interval, note that + # t0: start time of instruction + # t1: end time of instruction + if isinstance(node.op, self.CONDITIONAL_SUPPORTED): + t0q = max(idle_after[q] for q in node.qargs) + if node.op.condition_bits: + # conditional is bit tricky due to conditional_latency + t0c = max(idle_after[bit] for bit in node.op.condition_bits) + if t0q > t0c: + # This is situation something like below + # + # |t0q + # Q ▒▒▒▒▒▒▒▒▒░░ + # C ▒▒▒░░░░░░░░ + # |t0c + # + # In this case, you can insert readout access before tq0 + # + # |t0q + # Q ▒▒▒▒▒▒▒▒▒▒▒ + # C ▒▒▒░░░▒▒░░░ + # |t0q - conditional_latency + # + t0c = max(t0q - self.conditional_latency, t0c) + t1c = t0c + self.conditional_latency + for bit in node.op.condition_bits: + # Lock clbit until state is read + idle_after[bit] = t1c + # It starts after register read access + t0 = max(t0q, t1c) + else: + t0 = t0q + t1 = t0 + op_duration + else: + if node.op.condition_bits: + raise TranspilerError( + f"Conditional instruction {node.op.name} is not supported in ASAP scheduler." + ) + + if isinstance(node.op, Measure): + # measure instruction handling is bit tricky due to clbit_write_latency + t0q = max(idle_after[q] for q in node.qargs) + t0c = max(idle_after[c] for c in node.cargs) + # Assume following case (t0c > t0q) + # + # |t0q + # Q ▒▒▒▒░░░░░░░░░░░░ + # C ▒▒▒▒▒▒▒▒░░░░░░░░ + # |t0c + # + # In this case, there is no actual clbit access until clbit_write_latency. + # The node t0 can be push backward by this amount. + # + # |t0q' = t0c - clbit_write_latency + # Q ▒▒▒▒░░▒▒▒▒▒▒▒▒▒▒ + # C ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + # |t0c' = t0c + # + # rather than naively doing + # + # |t0q' = t0c + # Q ▒▒▒▒░░░░▒▒▒▒▒▒▒▒ + # C ▒▒▒▒▒▒▒▒░░░▒▒▒▒▒ + # |t0c' = t0c + clbit_write_latency + # + t0 = max(t0q, t0c - self.clbit_write_latency) + t1 = t0 + op_duration + for clbit in node.cargs: + idle_after[clbit] = t1 + else: + # It happens to be directives such as barrier + t0 = max(idle_after[bit] for bit in node.qargs + node.cargs) + t1 = t0 + op_duration + + # Add delay to qubit wire + for bit in node.qargs: + delta = t0 - idle_after[bit] + if ( + delta > 0 + and isinstance(bit, Qubit) + and self._delay_supported(dag.find_bit(bit).index) + ): + new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) + idle_after[bit] = t1 + + new_dag.apply_operation_back(node.op, node.qargs, node.cargs) + + circuit_duration = max(idle_after.values()) + for bit, after in idle_after.items(): + delta = circuit_duration - after + if not (delta > 0 and isinstance(bit, Qubit)): + continue + if self._delay_supported(dag.find_bit(bit).index): + new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) + + new_dag.name = dag.name + new_dag.metadata = dag.metadata + new_dag.calibrations = dag.calibrations + + # set circuit duration and unit to indicate it is scheduled + new_dag.duration = circuit_duration + new_dag.unit = time_unit + return new_dag diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py new file mode 100644 index 000000000000..cf8477278372 --- /dev/null +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -0,0 +1,285 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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. + +"""Dynamical Decoupling insertion pass.""" + +import itertools + +import numpy as np +from qiskit.circuit import Gate, Delay, Reset +from qiskit.circuit.library.standard_gates import IGate, UGate, U3Gate +from qiskit.dagcircuit import DAGOpNode, DAGInNode +from qiskit.quantum_info.operators.predicates import matrix_equal +from qiskit.quantum_info.synthesis import OneQubitEulerDecomposer +from qiskit.transpiler.passes.optimization import Optimize1qGates +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.utils.deprecation import deprecate_func + + +class DynamicalDecoupling(TransformationPass): + """Dynamical decoupling insertion pass. + + This pass works on a scheduled, physical circuit. It scans the circuit for + idle periods of time (i.e. those containing delay instructions) and inserts + a DD sequence of gates in those spots. These gates amount to the identity, + so do not alter the logical action of the circuit, but have the effect of + mitigating decoherence in those idle periods. + + As a special case, the pass allows a length-1 sequence (e.g. [XGate()]). + In this case the DD insertion happens only when the gate inverse can be + absorbed into a neighboring gate in the circuit (so we would still be + replacing Delay with something that is equivalent to the identity). + This can be used, for instance, as a Hahn echo. + + This pass ensures that the inserted sequence preserves the circuit exactly + (including global phase). + + .. plot:: + :include-source: + + import numpy as np + from qiskit.circuit import QuantumCircuit + from qiskit.circuit.library import XGate + from qiskit.transpiler import PassManager, InstructionDurations + from qiskit.transpiler.passes import ALAPSchedule, DynamicalDecoupling + from qiskit.visualization import timeline_drawer + + # Because the legacy passes do not propagate the scheduling information correctly, it is + # necessary to run a no-op "re-schedule" before the output circuits can be drawn. + def draw(circuit): + from qiskit import transpile + + scheduled = transpile( + circuit, + optimization_level=0, + instruction_durations=InstructionDurations(), + scheduling_method="alap", + ) + return timeline_drawer(scheduled) + + circ = QuantumCircuit(4) + circ.h(0) + circ.cx(0, 1) + circ.cx(1, 2) + circ.cx(2, 3) + circ.measure_all() + durations = InstructionDurations( + [("h", 0, 50), ("cx", [0, 1], 700), ("reset", None, 10), + ("cx", [1, 2], 200), ("cx", [2, 3], 300), + ("x", None, 50), ("measure", None, 1000)] + ) + # balanced X-X sequence on all qubits + dd_sequence = [XGate(), XGate()] + pm = PassManager([ALAPSchedule(durations), + DynamicalDecoupling(durations, dd_sequence)]) + circ_dd = pm.run(circ) + draw(circ_dd) + + # Uhrig sequence on qubit 0 + n = 8 + dd_sequence = [XGate()] * n + def uhrig_pulse_location(k): + return np.sin(np.pi * (k + 1) / (2 * n + 2)) ** 2 + spacing = [] + for k in range(n): + spacing.append(uhrig_pulse_location(k) - sum(spacing)) + spacing.append(1 - sum(spacing)) + pm = PassManager( + [ + ALAPSchedule(durations), + DynamicalDecoupling(durations, dd_sequence, qubits=[0], spacing=spacing), + ] + ) + circ_dd = pm.run(circ) + draw(circ_dd) + """ + + @deprecate_func( + additional_msg=( + "Instead, use :class:`~.PadDynamicalDecoupling`, which performs the same " + "function but requires scheduling and alignment analysis passes to run prior to it." + ), + since="0.21.0", + pending=True, + ) + def __init__( + self, durations, dd_sequence, qubits=None, spacing=None, skip_reset_qubits=True, target=None + ): + """Dynamical decoupling initializer. + + Args: + durations (InstructionDurations): Durations of instructions to be + used in scheduling. + dd_sequence (list[Gate]): sequence of gates to apply in idle spots. + qubits (list[int]): physical qubits on which to apply DD. + If None, all qubits will undergo DD (when possible). + spacing (list[float]): a list of spacings between the DD gates. + The available slack will be divided according to this. + The list length must be one more than the length of dd_sequence, + and the elements must sum to 1. If None, a balanced spacing + will be used [d/2, d, d, ..., d, d, d/2]. + skip_reset_qubits (bool): if True, does not insert DD on idle + periods that immediately follow initialized/reset qubits (as + qubits in the ground state are less susceptile to decoherence). + target (Target): The :class:`~.Target` representing the target backend, if both + ``durations`` and this are specified then this argument will take + precedence and ``durations`` will be ignored. + """ + super().__init__() + self._durations = durations + self._dd_sequence = dd_sequence + self._qubits = qubits + self._spacing = spacing + self._skip_reset_qubits = skip_reset_qubits + self._target = target + if target is not None: + self._durations = target.durations() + for gate in dd_sequence: + if gate.name not in target.operation_names: + raise TranspilerError( + f"{gate.name} in dd_sequence is not supported in the target" + ) + + def run(self, dag): + """Run the DynamicalDecoupling pass on dag. + + Args: + dag (DAGCircuit): a scheduled DAG. + + Returns: + DAGCircuit: equivalent circuit with delays interrupted by DD, + where possible. + + Raises: + TranspilerError: if the circuit is not mapped on physical qubits. + """ + if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: + raise TranspilerError("DD runs on physical circuits only.") + + if dag.duration is None: + raise TranspilerError("DD runs after circuit is scheduled.") + + num_pulses = len(self._dd_sequence) + sequence_gphase = 0 + if num_pulses != 1: + if num_pulses % 2 != 0: + raise TranspilerError("DD sequence must contain an even number of gates (or 1).") + noop = np.eye(2) + for gate in self._dd_sequence: + noop = noop.dot(gate.to_matrix()) + if not matrix_equal(noop, IGate().to_matrix(), ignore_phase=True): + raise TranspilerError("The DD sequence does not make an identity operation.") + sequence_gphase = np.angle(noop[0][0]) + + if self._qubits is None: + self._qubits = set(range(dag.num_qubits())) + else: + self._qubits = set(self._qubits) + + if self._spacing: + if sum(self._spacing) != 1 or any(a < 0 for a in self._spacing): + raise TranspilerError( + "The spacings must be given in terms of fractions " + "of the slack period and sum to 1." + ) + else: # default to balanced spacing + mid = 1 / num_pulses + end = mid / 2 + self._spacing = [end] + [mid] * (num_pulses - 1) + [end] + + for qarg in list(self._qubits): + for gate in self._dd_sequence: + if not self.__gate_supported(gate, qarg): + self._qubits.discard(qarg) + break + + index_sequence_duration_map = {} + for physical_qubit in self._qubits: + dd_sequence_duration = 0 + for index, gate in enumerate(self._dd_sequence): + gate = gate.to_mutable() + self._dd_sequence[index] = gate + gate.duration = self._durations.get(gate, physical_qubit) + + dd_sequence_duration += gate.duration + index_sequence_duration_map[physical_qubit] = dd_sequence_duration + + new_dag = dag.copy_empty_like() + + for nd in dag.topological_op_nodes(): + if not isinstance(nd.op, Delay): + new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) + continue + + dag_qubit = nd.qargs[0] + physical_qubit = dag.find_bit(dag_qubit).index + if physical_qubit not in self._qubits: # skip unwanted qubits + new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) + continue + + pred = next(dag.predecessors(nd)) + succ = next(dag.successors(nd)) + if self._skip_reset_qubits: # discount initial delays + if isinstance(pred, DAGInNode) or isinstance(pred.op, Reset): + new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) + continue + + dd_sequence_duration = index_sequence_duration_map[physical_qubit] + slack = nd.op.duration - dd_sequence_duration + if slack <= 0: # dd doesn't fit + new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) + continue + + if num_pulses == 1: # special case of using a single gate for DD + u_inv = self._dd_sequence[0].inverse().to_matrix() + theta, phi, lam, phase = OneQubitEulerDecomposer().angles_and_phase(u_inv) + # absorb the inverse into the successor (from left in circuit) + if isinstance(succ, DAGOpNode) and isinstance(succ.op, (UGate, U3Gate)): + theta_r, phi_r, lam_r = succ.op.params + succ.op.params = Optimize1qGates.compose_u3( + theta_r, phi_r, lam_r, theta, phi, lam + ) + sequence_gphase += phase + # absorb the inverse into the predecessor (from right in circuit) + elif isinstance(pred, DAGOpNode) and isinstance(pred.op, (UGate, U3Gate)): + theta_l, phi_l, lam_l = pred.op.params + pred.op.params = Optimize1qGates.compose_u3( + theta, phi, lam, theta_l, phi_l, lam_l + ) + sequence_gphase += phase + # don't do anything if there's no single-qubit gate to absorb the inverse + else: + new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) + continue + + # insert the actual DD sequence + taus = [int(slack * a) for a in self._spacing] + unused_slack = slack - sum(taus) # unused, due to rounding to int multiples of dt + middle_index = int((len(taus) - 1) / 2) # arbitrary: redistribute to middle + taus[middle_index] += unused_slack # now we add up to original delay duration + + for tau, gate in itertools.zip_longest(taus, self._dd_sequence): + if tau > 0: + new_dag.apply_operation_back(Delay(tau), [dag_qubit], check=False) + if gate is not None: + new_dag.apply_operation_back(gate, [dag_qubit], check=False) + + new_dag.global_phase = new_dag.global_phase + sequence_gphase + + return new_dag + + def __gate_supported(self, gate: Gate, qarg: int) -> bool: + """A gate is supported on the qubit (qarg) or not.""" + if self._target is None or self._target.instruction_supported(gate.name, qargs=(qarg,)): + return True + return False diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py index 2d4114f3cfc7..97946c55df8b 100644 --- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py @@ -42,7 +42,7 @@ class PadDynamicalDecoupling(BasePadding): so do not alter the logical action of the circuit, but have the effect of mitigating decoherence in those idle periods. - As a special case, the pass allows a length-1 sequence (e.g. [XGate()]). + As a special case, the pass allows a length-1 sequence (e.g. ``[XGate()]``). In this case the DD insertion happens only when the gate inverse can be absorbed into a neighboring gate in the circuit (so we would still be replacing Delay with something that is equivalent to the identity). @@ -394,8 +394,7 @@ def _constrained_length(values): gate_length = self._dd_sequence_lengths[qubit][dd_ind] self._apply_scheduled_op(dag, idle_after, gate, qubit) idle_after += gate_length - - dag.global_phase = self._mod_2pi(dag.global_phase + sequence_gphase) + dag.global_phase = dag.global_phase + sequence_gphase @staticmethod def _resolve_params(gate: Gate) -> tuple: @@ -407,11 +406,3 @@ def _resolve_params(gate: Gate) -> tuple: else: params.append(p) return tuple(params) - - @staticmethod - def _mod_2pi(angle: float, atol: float = 0): - """Wrap angle into interval [-π,π). If within atol of the endpoint, clamp to -π""" - wrapped = (angle + np.pi) % (2 * np.pi) - np.pi - if abs(wrapped - np.pi) < atol: - wrapped = -np.pi - return wrapped diff --git a/qiskit/transpiler/passes/scheduling/padding/pad_delay.py b/qiskit/transpiler/passes/scheduling/padding/pad_delay.py index f1517900874f..886bb6a37974 100644 --- a/qiskit/transpiler/passes/scheduling/padding/pad_delay.py +++ b/qiskit/transpiler/passes/scheduling/padding/pad_delay.py @@ -57,7 +57,7 @@ def __init__(self, fill_very_end: bool = True, target: Target = None): Args: fill_very_end: Set ``True`` to fill the end of circuit with delay. target: The :class:`~.Target` representing the target backend. - If it supplied and it does not support delay instruction on a qubit, + If it is supplied and does not support delay instruction on a qubit, padding passes do not pad any idle time of the qubit. """ super().__init__(target=target) diff --git a/qiskit/transpiler/passes/scheduling/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/scheduling/asap.py index ce0e1d1948a0..fa07ae0c5f61 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/asap.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/asap.py @@ -18,7 +18,7 @@ class ASAPScheduleAnalysis(BaseScheduler): - """ASAP Scheduling pass, which schedules the start time of instructions as early as possible.. + """ASAP Scheduling pass, which schedules the start time of instructions as early as possible. See the :ref:`scheduling_stage` section in the :mod:`qiskit.transpiler` module documentation for the detailed behavior of the control flow diff --git a/qiskit/transpiler/passes/scheduling/scheduling/set_io_latency.py b/qiskit/transpiler/passes/scheduling/scheduling/set_io_latency.py index 1116123cfa51..3b0ed1692f07 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/set_io_latency.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/set_io_latency.py @@ -19,7 +19,7 @@ class SetIOLatency(AnalysisPass): """Set IOLatency information to the input circuit. The ``clbit_write_latency`` and ``conditional_latency`` are added to - the property set of pass manager. These information can be shared among the passes + the property set of pass manager. This information can be shared among the passes that perform scheduling on instructions acting on classical registers. Once these latencies are added to the property set, this information diff --git a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py index c75e22f285b8..d53c3fc4ef6a 100644 --- a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py +++ b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py @@ -25,15 +25,15 @@ class TimeUnitConversion(TransformationPass): """Choose a time unit to be used in the following time-aware passes, and make all circuit time units consistent with that. - This pass will add a .duration metadata to each op whose duration is known, + This pass will add a :attr:`.Instruction.duration` metadata to each op whose duration is known which will be used by subsequent scheduling passes for scheduling. - If dt (dt in seconds) is known to transpiler, the unit 'dt' is chosen. Otherwise, + If ``dt`` (in seconds) is known to transpiler, the unit ``'dt'`` is chosen. Otherwise, the unit to be selected depends on what units are used in delays and instruction durations: - * 's': if they are all in SI units. - * 'dt': if they are all in the unit 'dt'. - * raise error: if they are a mix of SI units and 'dt'. + * ``'s'``: if they are all in SI units. + * ``'dt'``: if they are all in the unit ``'dt'``. + * raise error: if they are a mix of SI units and ``'dt'``. """ def __init__(self, inst_durations: InstructionDurations = None, target: Target = None): @@ -42,7 +42,7 @@ def __init__(self, inst_durations: InstructionDurations = None, target: Target = Args: inst_durations (InstructionDurations): A dictionary of durations of instructions. target: The :class:`~.Target` representing the target backend, if both - ``inst_durations`` and this are specified then this argument will take + ``inst_durations`` and ``target`` are specified then this argument will take precedence and ``inst_durations`` will be ignored. diff --git a/qiskit/transpiler/passes/synthesis/linear_functions_synthesis.py b/qiskit/transpiler/passes/synthesis/linear_functions_synthesis.py index 4d31be6bff12..eb773a985ddb 100644 --- a/qiskit/transpiler/passes/synthesis/linear_functions_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/linear_functions_synthesis.py @@ -30,6 +30,7 @@ class LinearFunctionsSynthesis(HighLevelSynthesis): @deprecate_func( additional_msg="Instead, use :class:`~.HighLevelSynthesis`.", since="0.23.0", + package_name="qiskit-terra", ) def __init__(self): # This config synthesizes only linear functions using the "default" method. diff --git a/qiskit/transpiler/passes/utils/check_map.py b/qiskit/transpiler/passes/utils/check_map.py index d56a709253c9..61ddc71d131e 100644 --- a/qiskit/transpiler/passes/utils/check_map.py +++ b/qiskit/transpiler/passes/utils/check_map.py @@ -21,7 +21,7 @@ class CheckMap(AnalysisPass): """Check if a DAG circuit is already mapped to a coupling map. - Check if a DAGCircuit is mapped to `coupling_map` by checking that all + Check if a DAGCircuit is mapped to ``coupling_map`` by checking that all 2-qubit interactions are laid out to be on adjacent qubits in the global coupling map of the device, setting the property set field (either specified with ``property_set_field`` or the default ``is_swap_mapped``) to ``True`` or ``False`` accordingly. Note this does not diff --git a/qiskit/transpiler/passes/utils/dag_fixed_point.py b/qiskit/transpiler/passes/utils/dag_fixed_point.py index 2f9f486545b5..60da251b3099 100644 --- a/qiskit/transpiler/passes/utils/dag_fixed_point.py +++ b/qiskit/transpiler/passes/utils/dag_fixed_point.py @@ -21,7 +21,7 @@ class DAGFixedPoint(AnalysisPass): """Check if the DAG has reached a fixed point. A dummy analysis pass that checks if the DAG a fixed point (the DAG is not - modified anymore). The results is saved in + modified anymore). The result is saved in ``property_set['dag_fixed_point']`` as a boolean. """ diff --git a/qiskit/transpiler/passes/utils/error.py b/qiskit/transpiler/passes/utils/error.py index 445b1d21d565..f2659ec052ff 100644 --- a/qiskit/transpiler/passes/utils/error.py +++ b/qiskit/transpiler/passes/utils/error.py @@ -31,9 +31,9 @@ def __init__(self, msg=None, action="raise"): will be used. This can be either a raw string, or a callback function that accepts the current ``property_set`` and returns the desired message. action (str): the action to perform. Default: 'raise'. The options are: - * 'raise': Raises a `TranspilerError` exception with msg - * 'warn': Raises a non-fatal warning with msg - * 'log': logs in `logging.getLogger(__name__)` + * ``'raise'``: Raises a ``TranspilerError`` exception with msg + * ``'warn'``: Raises a non-fatal warning with msg + * ``'log'``: logs in ``logging.getLogger(__name__)`` Raises: TranspilerError: if action is not valid. diff --git a/qiskit/transpiler/passes/utils/fixed_point.py b/qiskit/transpiler/passes/utils/fixed_point.py index 34fa1faabe3b..fbef9d0a85ef 100644 --- a/qiskit/transpiler/passes/utils/fixed_point.py +++ b/qiskit/transpiler/passes/utils/fixed_point.py @@ -21,7 +21,7 @@ class FixedPoint(AnalysisPass): """Check if a property reached a fixed point. A dummy analysis pass that checks if a property reached a fixed point. - The results is saved in ``property_set['_fixed_point']`` + The result is saved in ``property_set['_fixed_point']`` as a boolean. """ diff --git a/qiskit/transpiler/passes/utils/minimum_point.py b/qiskit/transpiler/passes/utils/minimum_point.py index 8ccfa4a7f951..1dcef0db2181 100644 --- a/qiskit/transpiler/passes/utils/minimum_point.py +++ b/qiskit/transpiler/passes/utils/minimum_point.py @@ -44,7 +44,7 @@ class MinimumPoint(TransformationPass): Fields used by this pass in the property set are (all relative to the ``prefix`` argument): - * ``{prefix}_minimum_point_state`` - Used to track the state of the minimpoint search + * ``{prefix}_minimum_point_state`` - Used to track the state of the minimum point search * ``{prefix}_minimum_point`` - This value gets set to ``True`` when either a fixed point is reached over the ``backtrack_depth`` executions, or ``backtrack_depth`` was exceeded and an earlier minimum is restored. diff --git a/qiskit/transpiler/passes/utils/remove_final_measurements.py b/qiskit/transpiler/passes/utils/remove_final_measurements.py index 3c60adeabbba..c3179e204cd4 100644 --- a/qiskit/transpiler/passes/utils/remove_final_measurements.py +++ b/qiskit/transpiler/passes/utils/remove_final_measurements.py @@ -26,7 +26,7 @@ class RemoveFinalMeasurements(TransformationPass): Classical registers are removed iff they reference at least one bit that has become unused by the circuit as a result of the operation, and all - of their other bits are also unused. Seperately, classical bits are removed + of their other bits are also unused. Separately, classical bits are removed iff they have become unused by the circuit as a result of the operation, or they appear in a removed classical register, but do not appear in a classical register that will remain. diff --git a/qiskit/transpiler/passes/utils/unroll_forloops.py b/qiskit/transpiler/passes/utils/unroll_forloops.py index 8cde3874adcb..b8ebe853f1e0 100644 --- a/qiskit/transpiler/passes/utils/unroll_forloops.py +++ b/qiskit/transpiler/passes/utils/unroll_forloops.py @@ -22,8 +22,8 @@ class UnrollForLoops(TransformationPass): """``UnrollForLoops`` transpilation pass unrolls for-loops when possible.""" def __init__(self, max_target_depth=-1): - """Things like `for x in {0, 3, 4} {rx(x) qr[1];}` will turn into - `rx(0) qr[1]; rx(3) qr[1]; rx(4) qr[1];`. + """Things like ``for x in {0, 3, 4} {rx(x) qr[1];}`` will turn into + ``rx(0) qr[1]; rx(3) qr[1]; rx(4) qr[1];``. .. note:: The ``UnrollForLoops`` unrolls only one level of block depth. No inner loop will @@ -38,7 +38,7 @@ def __init__(self, max_target_depth=-1): @control_flow.trivial_recurse def run(self, dag): - """Run the UnrollForLoops pass on `dag`. + """Run the UnrollForLoops pass on ``dag``. Args: dag (DAGCircuit): the directed acyclic graph to run on. diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index 93eee91e540a..c9c67be05d6a 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -65,7 +65,7 @@ def _passmanager_frontend( input_program: QuantumCircuit, **kwargs, ) -> DAGCircuit: - return circuit_to_dag(input_program, copy_operations=False) + return circuit_to_dag(input_program, copy_operations=True) def _passmanager_backend( self, @@ -73,7 +73,7 @@ def _passmanager_backend( in_program: QuantumCircuit, **kwargs, ) -> QuantumCircuit: - out_program = dag_to_circuit(passmanager_ir) + out_program = dag_to_circuit(passmanager_ir, copy_operations=False) out_name = kwargs.get("output_name", None) if out_name is not None: @@ -107,6 +107,7 @@ def _passmanager_backend( since="0.25", additional_msg="'max_iteration' can be set in the constructor.", pending=True, + package_name="qiskit-terra", ) def append( self, @@ -163,6 +164,7 @@ def append( since="0.25", additional_msg="'max_iteration' can be set in the constructor.", pending=True, + package_name="qiskit-terra", ) def replace( self, diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py index bec81501aab5..ee1e407ae87d 100644 --- a/qiskit/transpiler/preset_passmanagers/__init__.py +++ b/qiskit/transpiler/preset_passmanagers/__init__.py @@ -61,6 +61,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.target import target_to_backend_properties +from qiskit.transpiler import CouplingMap from .level0 import level_0_pass_manager from .level1 import level_1_pass_manager @@ -130,7 +131,7 @@ def generate_preset_pass_manager( circuit, transpiler attaches the custom gate definition to the circuit. This enables one to flexibly override the low-level instruction implementation. - coupling_map (CouplingMap): Directed graph represented a coupling + coupling_map (CouplingMap or list): Directed graph represented a coupling map. instruction_durations (InstructionDurations): Dictionary of duration (in dt) for each instruction. @@ -213,6 +214,9 @@ def generate_preset_pass_manager( stacklevel=2, ) + if coupling_map is not None and not isinstance(coupling_map, CouplingMap): + coupling_map = CouplingMap(coupling_map) + if target is not None: if coupling_map is None: coupling_map = target.build_coupling_map() diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 3dc89850e37a..ca21bd2a3b57 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -39,7 +39,7 @@ CommutativeCancellation, Collect2qBlocks, ConsolidateBlocks, - CXCancellation, + InverseCancellation, ) from qiskit.transpiler.passes import Depth, Size, FixedPoint, MinimumPoint from qiskit.transpiler.passes.utils.gates_basis import GatesInBasis @@ -47,6 +47,23 @@ from qiskit.passmanager.flow_controllers import ConditionalController from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason +from qiskit.circuit.library.standard_gates import ( + CXGate, + ECRGate, + CZGate, + XGate, + YGate, + ZGate, + TGate, + TdgGate, + SwapGate, + SGate, + SdgGate, + HGate, + CYGate, + SXGate, + SXdgGate, +) class DefaultInitPassManager(PassManagerStagePlugin): @@ -468,7 +485,22 @@ def _opt_control(property_set): Optimize1qGatesDecomposition( basis=pass_manager_config.basis_gates, target=pass_manager_config.target ), - CXCancellation(), + InverseCancellation( + [ + CXGate(), + ECRGate(), + CZGate(), + CYGate(), + XGate(), + YGate(), + ZGate(), + HGate(), + SwapGate(), + (TGate(), TdgGate()), + (SGate(), SdgGate()), + (SXGate(), SXdgGate()), + ] + ), ] elif optimization_level == 2: # Steps for optimization level 2 diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index e9bf85283fe3..494aa7a2159e 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -581,6 +581,7 @@ def _require_alignment(property_set): @deprecate_func( additional_msg="Instead, use :func:`~qiskit.transpiler.preset_passmanagers.common.get_vf2_limits`.", since="0.25.0", + package_name="qiskit-terra", ) def get_vf2_call_limit( optimization_level: int, diff --git a/qiskit/transpiler/synthesis/aqc/aqc.py b/qiskit/transpiler/synthesis/aqc/aqc.py index e90fd5612f97..4ced39a7e4ac 100644 --- a/qiskit/transpiler/synthesis/aqc/aqc.py +++ b/qiskit/transpiler/synthesis/aqc/aqc.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2022. +# (C) Copyright IBM 2022, 2023. # # 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 @@ -10,30 +10,82 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """A generic implementation of Approximate Quantum Compiler.""" -from typing import Optional +from __future__ import annotations + +from functools import partial + +from collections.abc import Callable +from typing import Protocol import numpy as np +from scipy.optimize import OptimizeResult, minimize -from qiskit.algorithms.optimizers import L_BFGS_B, Optimizer from qiskit.quantum_info import Operator + from .approximate import ApproximateCircuit, ApproximatingObjective +class Minimizer(Protocol): + """Callable Protocol for minimizer. + + This interface is based on `SciPy's optimize module + `__. + + This protocol defines a callable taking the following parameters: + + fun + The objective function to minimize. + x0 + The initial point for the optimization. + jac + The gradient of the objective function. + bounds + Parameters bounds for the optimization. Note that these might not be supported + by all optimizers. + + and which returns a SciPy minimization result object. + """ + + def __call__( + self, + fun: Callable[[np.ndarray], float], + x0: np.ndarray, # pylint: disable=invalid-name + jac: Callable[[np.ndarray], np.ndarray] | None = None, + bounds: list[tuple[float, float]] | None = None, + ) -> OptimizeResult: + """Minimize the objective function. + + This interface is based on `SciPy's optimize module `__. + + Args: + fun: The objective function to minimize. + x0: The initial point for the optimization. + jac: The gradient of the objective function. + bounds: Parameters bounds for the optimization. Note that these might not be supported + by all optimizers. + + Returns: + The SciPy minimization result object. + """ + ... # pylint: disable=unnecessary-ellipsis + + class AQC: """ - A generic implementation of Approximate Quantum Compiler. This implementation is agnostic of + A generic implementation of the Approximate Quantum Compiler. This implementation is agnostic of the underlying implementation of the approximate circuit, objective, and optimizer. Users may pass corresponding implementations of the abstract classes: - * Optimizer is an instance of :class:`~qiskit.algorithms.optimizers.Optimizer` and used to run - the optimization process. A choice of optimizer may affect overall convergence, required time + * The *optimizer* is an implementation of the :class:`~.Minimizer` protocol, a callable used to run + the optimization process. The choice of optimizer may affect overall convergence, required time for the optimization process and achieved objective value. - * Approximate circuit represents a template which parameters we want to optimize. Currently, + * The *approximate circuit* represents a template which parameters we want to optimize. Currently, there's only one implementation based on 4-rotations CNOT unit blocks: :class:`.CNOTUnitCircuit`. See the paper for more details. - * Approximate objective is tightly coupled with the approximate circuit implementation and + * The *approximate objective* is tightly coupled with the approximate circuit implementation and provides two methods for computing objective function and gradient with respect to approximate circuit parameters. This objective is passed to the optimizer. Currently, there are two implementations based on 4-rotations CNOT unit blocks: :class:`.DefaultCNOTUnitObjective` and @@ -50,18 +102,21 @@ class AQC: def __init__( self, - optimizer: Optional[Optimizer] = None, - seed: Optional[int] = None, + optimizer: Minimizer | None = None, + seed: int | None = None, ): """ Args: optimizer: an optimizer to be used in the optimization procedure of the search for - the best approximate circuit. By default, :obj:`.L_BFGS_B` is used with max - iterations set to 1000. - seed: a seed value to be user by a random number generator. + the best approximate circuit. By default, the scipy minimizer with the + ``L-BFGS-B`` method is used with max iterations set to 1000. + seed: a seed value to be used by a random number generator. """ super().__init__() - self._optimizer = optimizer + self._optimizer = optimizer or partial( + minimize, args=(), method="L-BFGS-B", options={"maxiter": 1000} + ) + self._seed = seed def compile_unitary( @@ -69,7 +124,7 @@ def compile_unitary( target_matrix: np.ndarray, approximate_circuit: ApproximateCircuit, approximating_objective: ApproximatingObjective, - initial_point: Optional[np.ndarray] = None, + initial_point: np.ndarray | None = None, ) -> None: """ Approximately compiles a circuit represented as a unitary matrix by solving an optimization @@ -96,13 +151,11 @@ def compile_unitary( # set the matrix to approximate in the algorithm approximating_objective.target_matrix = su_matrix - optimizer = self._optimizer or L_BFGS_B(maxiter=1000) - if initial_point is None: np.random.seed(self._seed) initial_point = np.random.uniform(0, 2 * np.pi, approximating_objective.num_thetas) - opt_result = optimizer.minimize( + opt_result = self._optimizer( fun=approximating_objective.objective, x0=initial_point, jac=approximating_objective.gradient, diff --git a/qiskit/transpiler/synthesis/aqc/aqc_plugin.py b/qiskit/transpiler/synthesis/aqc/aqc_plugin.py index 0138d8e7b97a..0fa153566557 100644 --- a/qiskit/transpiler/synthesis/aqc/aqc_plugin.py +++ b/qiskit/transpiler/synthesis/aqc/aqc_plugin.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2021, 2023. # # 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 @@ -12,6 +12,7 @@ """ An AQC synthesis plugin to Qiskit's transpiler. """ +from functools import partial import numpy as np from qiskit.converters import circuit_to_dag @@ -43,8 +44,8 @@ class AQCSynthesisPlugin(UnitarySynthesisPlugin): depth of the CNOT-network, i.e. the number of layers, where each layer consists of a single CNOT-block. - optimizer (:class:`~qiskit.algorithms.optimizers.Optimizer`) - An instance of optimizer to be used in the optimization process. + optimizer (:class:`~.Minimizer`) + An implementation of the ``Minimizer`` protocol to be used in the optimization process. seed (int) A random seed. @@ -104,7 +105,7 @@ def run(self, unitary, **options): # Runtime imports to avoid the overhead of these imports for # plugin discovery and only use them if the plugin is run/used - from qiskit.algorithms.optimizers import L_BFGS_B + from scipy.optimize import minimize from qiskit.transpiler.synthesis.aqc.aqc import AQC from qiskit.transpiler.synthesis.aqc.cnot_structures import make_cnot_network from qiskit.transpiler.synthesis.aqc.cnot_unit_circuit import CNOTUnitCircuit @@ -125,7 +126,8 @@ def run(self, unitary, **options): depth=depth, ) - optimizer = config.get("optimizer", L_BFGS_B(maxiter=1000)) + default_optimizer = partial(minimize, args=(), method="L-BFGS-B", options={"maxiter": 1000}) + optimizer = config.get("optimizer", default_optimizer) seed = config.get("seed") aqc = AQC(optimizer, seed) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 8a895f0d9ba3..5171951119d9 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -173,11 +173,11 @@ class Target(Mapping): } gmap.add_instruction(CXGate(), cx_props) - Each instruction in the Target is indexed by a unique string name that uniquely + Each instruction in the ``Target`` is indexed by a unique string name that uniquely identifies that instance of an :class:`~qiskit.circuit.Instruction` object in the Target. There is a 1:1 mapping between a name and an :class:`~qiskit.circuit.Instruction` instance in the target and each name must - be unique. By default the name is the :attr:`~qiskit.circuit.Instruction.name` + be unique. By default, the name is the :attr:`~qiskit.circuit.Instruction.name` attribute of the instruction, but can be set to anything. This lets a single target have multiple instances of the same instruction class with different parameters. For example, if a backend target has two instances of an @@ -242,7 +242,12 @@ class Target(Mapping): "concurrent_measurements", ) - @deprecate_arg("aquire_alignment", new_alias="acquire_alignment", since="0.23.0") + @deprecate_arg( + "aquire_alignment", + new_alias="acquire_alignment", + since="0.23.0", + package_name="qiskit-terra", + ) def __init__( self, description=None, @@ -256,7 +261,7 @@ def __init__( concurrent_measurements=None, ): """ - Create a new Target object + Create a new ``Target`` object Args: description (str): An optional string to describe the Target. @@ -291,10 +296,11 @@ def __init__( ``None`` concurrent_measurements(list): A list of sets of qubits that must be measured together. This must be provided - as a nested list like [[0, 1], [2, 3, 4]]. + as a nested list like ``[[0, 1], [2, 3, 4]]``. + Raises: ValueError: If both ``num_qubits`` and ``qubit_properties`` are both - defined and the value of ``num_qubits`` differs from the length of - ``qubit_properties``. + defined and the value of ``num_qubits`` differs from the length of + ``qubit_properties``. """ self.num_qubits = num_qubits # A mapping of gate name -> gate instance @@ -332,7 +338,7 @@ def add_instruction(self, instruction, properties=None, name=None): """Add a new instruction to the :class:`~qiskit.transpiler.Target` As ``Target`` objects are strictly additive this is the primary method - for modifying a ``Target``. Typically you will use this to fully populate + for modifying a ``Target``. Typically, you will use this to fully populate a ``Target`` before using it in :class:`~qiskit.providers.BackendV2`. For example:: @@ -363,7 +369,7 @@ def add_instruction(self, instruction, properties=None, name=None): Args: instruction (qiskit.circuit.Instruction): The operation object to add to the map. If it's - paramerterized any value of the parameter can be set. Optionally for variable width + parameterized any value of the parameter can be set. Optionally for variable width instructions (such as control flow operations such as :class:`~.ForLoop` or :class:`~MCXGate`) you can specify the class. If the class is specified than the ``name`` argument must be specified. When a class is used the gate is treated as global @@ -374,7 +380,7 @@ def add_instruction(self, instruction, properties=None, name=None): for any instruction implementation, if there are no :class:`~qiskit.transpiler.InstructionProperties` available for the backend the value can be None. If there are no constraints on the - instruction (as in a noisless/ideal simulation) this can be set to + instruction (as in a noiseless/ideal simulation) this can be set to ``{None, None}`` which will indicate it runs on all qubits (or all available permutations of qubits for multi-qubit gates). The first ``None`` indicates it applies to all qubits and the second ``None`` @@ -386,7 +392,7 @@ def add_instruction(self, instruction, properties=None, name=None): specified the :attr:`~qiskit.circuit.Instruction.name` attribute of ``gate`` will be used. All gates in the ``Target`` need unique names. Backends can differentiate between different - parameterizations of a single gate by providing a unique name for + parameterization of a single gate by providing a unique name for each (e.g. `"rx30"`, `"rx60", ``"rx90"`` similar to the example in the documentation for the :class:`~qiskit.transpiler.Target` class). Raises: @@ -613,7 +619,7 @@ def timing_constraints(self): """Get an :class:`~qiskit.transpiler.TimingConstraints` object from the target Returns: - TimingConstraints: The timing constraints represented in the Target + TimingConstraints: The timing constraints represented in the ``Target`` """ return TimingConstraints( self.granularity, self.min_length, self.pulse_alignment, self.acquire_alignment @@ -686,16 +692,15 @@ def operation_names_for_qargs(self, qargs): """Get the operation names for a specified qargs tuple Args: - qargs (tuple): A qargs tuple of the qubits to get the gates that apply + qargs (tuple): A ``qargs`` tuple of the qubits to get the gates that apply to it. For example, ``(0,)`` will return the set of all instructions that apply to qubit 0. If set to ``None`` this will return the names for any globally defined operations in the target. Returns: - set: The set of operation names that apply to the specified - `qargs``. + set: The set of operation names that apply to the specified ``qargs``. Raises: - KeyError: If qargs is not in target + KeyError: If ``qargs`` is not in target """ if qargs is not None and any(x not in range(0, self.num_qubits) for x in qargs): raise KeyError(f"{qargs} not in target.") @@ -735,7 +740,7 @@ def instruction_supported( is supported on the backend. For example, if you wanted to check whether a :class:`~.RXGate` was supported on a specific qubit with a fixed angle. That fixed angle variant will - typically have a name different than the object's + typically have a name different from the object's :attr:`~.Instruction.name` attribute (``"rx"``) in the target. This can be used to check if any instances of the class are available in such a case. @@ -757,7 +762,7 @@ def instruction_supported( parameters = [Parameter("theta")] target.instruction_supported("rx", (0,), parameters=parameters) - will return ``True`` if an :class:`~.RXGate` is suporrted on qubit 0 + will return ``True`` if an :class:`~.RXGate` is supported on qubit 0 that will accept any parameter. If you need to check for a fixed numeric value parameter this argument is typically paired with the ``operation_class`` argument. For example:: @@ -787,7 +792,7 @@ def check_obj_params(parameters, obj): if inspect.isclass(obj): if obj != operation_class: continue - # If no qargs a operation class is supported + # If no qargs operation class is supported if qargs is None: return True # If qargs set then validate no duplicates and all indices are valid on device @@ -1016,7 +1021,7 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): Args: two_q_gate (str): An optional gate name for a two qubit gate in - the Target to generate the coupling map for. If specified the + the ``Target`` to generate the coupling map for. If specified the output coupling map will only have edges between qubits where this gate is present. filter_idle_qubits (bool): If set to ``True`` the output :class:`~.CouplingMap` @@ -1024,7 +1029,7 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): target. Note that using this argument will result in an output :class:`~.CouplingMap` object which has holes in its indices which might differ from the assumptions of the class. The typical use - case of this argument is to be paired with with + case of this argument is to be paired with :meth:`.CouplingMap.connected_components` which will handle the holes as expected. Returns: @@ -1034,7 +1039,7 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): Raises: ValueError: If a non-two qubit gate is passed in for ``two_q_gate``. - IndexError: If an Instruction not in the Target is passed in for + IndexError: If an Instruction not in the ``Target`` is passed in for ``two_q_gate``. """ if self.qargs is None: @@ -1089,13 +1094,13 @@ def get_non_global_operation_names(self, strict_direction=False): """Return the non-global operation names for the target The non-global operations are those in the target which don't apply - on all qubits (for single qubit operations) or all multiqubit qargs + on all qubits (for single qubit operations) or all multi-qubit qargs (for multi-qubit operations). Args: strict_direction (bool): If set to ``True`` the multi-qubit operations considered as non-global respect the strict - direction (or order of qubits in the qargs is signifcant). For + direction (or order of qubits in the qargs is significant). For example, if ``cx`` is defined on ``(0, 1)`` and ``ecr`` is defined over ``(1, 0)`` by default neither would be considered non-global, but if ``strict_direction`` is set ``True`` both @@ -1143,6 +1148,7 @@ def get_non_global_operation_names(self, strict_direction=False): additional_msg="Use the property ``acquire_alignment`` instead.", since="0.24.0", is_property=True, + package_name="qiskit-terra", ) def aquire_alignment(self): """Alias of deprecated name. This will be removed.""" @@ -1153,6 +1159,7 @@ def aquire_alignment(self): additional_msg="Use the property ``acquire_alignment`` instead.", since="0.24.0", is_property=True, + package_name="qiskit-terra", ) def aquire_alignment(self, new_value: int): """Alias of deprecated name. This will be removed.""" @@ -1248,7 +1255,7 @@ def from_configuration( Args: basis_gates: The list of basis gate names for the backend. For the target to be created these names must either be in the output - from :func:~.get_standard_gate_name_mapping` or present in the + from :func:`~.get_standard_gate_name_mapping` or present in the specified ``custom_name_mapping`` argument. num_qubits: The number of qubits supported on the backend. coupling_map: The coupling map representing connectivity constraints @@ -1259,7 +1266,7 @@ def from_configuration( is specified ``coupling_map`` must be specified. The ``coupling_map`` is used as the source of truth for connectivity and if ``inst_map`` is used the schedule is looked up based - on the instuctions from the pair of ``basis_gates`` and + on the instructions from the pair of ``basis_gates`` and ``coupling_map``. If you want to define a custom gate for a particular qubit or qubit pair, you can manually build :class:`.Target`. backend_properties: The :class:`~.BackendProperties` object which is @@ -1275,13 +1282,13 @@ def from_configuration( :class:`~InstructionProperties` objects for the instructions in the target. concurrent_measurements(list): A list of sets of qubits that must be measured together. This must be provided - as a nested list like [[0, 1], [2, 3, 4]]. + as a nested list like ``[[0, 1], [2, 3, 4]]``. dt: The system time resolution of input signals in seconds timing_constraints: Optional timing constraints to include in the :class:`~.Target` custom_name_mapping: An optional dictionary that maps custom gate/operation names in ``basis_gates`` to an :class:`~.Operation` object representing that - gate/operation. By default most standard gates names are mapped to the + gate/operation. By default, most standard gates names are mapped to the standard gate object from :mod:`qiskit.circuit.library` this only needs to be specified if the input ``basis_gates`` defines gates in names outside that set. @@ -1326,9 +1333,9 @@ def from_configuration( name_mapping.update(custom_name_mapping) # While BackendProperties can also contain coupling information we - # rely solely on CouplingMap to determin connectivity. This is because + # rely solely on CouplingMap to determine connectivity. This is because # in legacy transpiler usage (and implicitly in the BackendV1 data model) - # the coupling map is used to define connecitivity constraints and + # the coupling map is used to define connectivity constraints and # the properties is only used for error rate and duration population. # If coupling map is not specified we ignore the backend_properties if coupling_map is None: diff --git a/qiskit/utils/__init__.py b/qiskit/utils/__init__.py index 789b8c0da9b0..45200413ba2a 100644 --- a/qiskit/utils/__init__.py +++ b/qiskit/utils/__init__.py @@ -48,9 +48,8 @@ A QuantumInstance holds the Qiskit `backend` as well as a number of compile and runtime parameters controlling circuit compilation and execution. Quantum -:mod:`algorithms ` -are run on a device or simulator by passing a QuantumInstance setup with the desired -backend etc. +algorithms are run on a device or simulator by passing a QuantumInstance setup +with the desired backend etc. Optional Dependency Checkers (:mod:`qiskit.utils.optionals`) diff --git a/qiskit/utils/backend_utils.py b/qiskit/utils/backend_utils.py index 7f71a7c91897..5943c408fc47 100644 --- a/qiskit/utils/backend_utils.py +++ b/qiskit/utils/backend_utils.py @@ -52,6 +52,7 @@ def _get_backend_provider(backend): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def has_ibmq(): @@ -73,6 +74,7 @@ def has_ibmq(): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def has_aer(): @@ -93,6 +95,7 @@ def has_aer(): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def is_aer_provider(backend): @@ -117,6 +120,7 @@ def is_aer_provider(backend): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def is_basicaer_provider(backend): @@ -134,6 +138,7 @@ def is_basicaer_provider(backend): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def is_ibmq_provider(backend): @@ -154,6 +159,7 @@ def is_ibmq_provider(backend): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def is_aer_statevector_backend(backend): @@ -170,6 +176,7 @@ def is_aer_statevector_backend(backend): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def is_statevector_backend(backend): @@ -181,16 +188,21 @@ def is_statevector_backend(backend): Returns: bool: True is statevector """ + if backend is None: + return False + backend_interface_version = _get_backend_interface_version(backend) if has_aer(): from qiskit.providers.aer.backends import AerSimulator, StatevectorSimulator if isinstance(backend, StatevectorSimulator): return True - if isinstance(backend, AerSimulator) and "aer_simulator_statevector" in backend.name(): - return True - if backend is None: - return False - backend_interface_version = _get_backend_interface_version(backend) + if isinstance(backend, AerSimulator): + if backend_interface_version <= 1: + name = backend.name() + else: + name = backend.name + if "aer_simulator_statevector" in name: + return True if backend_interface_version <= 1: return backend.name().startswith("statevector") else: @@ -199,6 +211,7 @@ def is_statevector_backend(backend): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def is_simulator_backend(backend): @@ -218,6 +231,7 @@ def is_simulator_backend(backend): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def is_local_backend(backend): @@ -237,6 +251,7 @@ def is_local_backend(backend): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def is_aer_qasm(backend): @@ -257,6 +272,7 @@ def is_aer_qasm(backend): @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def support_backend_options(backend): diff --git a/qiskit/utils/deprecation.py b/qiskit/utils/deprecation.py index 57fecfd9a449..b37cb1d63dbb 100644 --- a/qiskit/utils/deprecation.py +++ b/qiskit/utils/deprecation.py @@ -26,7 +26,7 @@ def deprecate_func( since: str, additional_msg: str | None = None, pending: bool = False, - package_name: str = "qiskit-terra", + package_name: str = "qiskit", removal_timeline: str = "no earlier than 3 months after the release date", is_property: bool = False, ): @@ -108,7 +108,7 @@ def deprecate_arg( additional_msg: str | None = None, deprecation_description: str | None = None, pending: bool = False, - package_name: str = "qiskit-terra", + package_name: str = "qiskit", new_alias: str | None = None, predicate: Callable[[Any], bool] | None = None, removal_timeline: str = "no earlier than 3 months after the release date", @@ -433,7 +433,7 @@ def add_deprecation_to_docstring( "This is a simplification to facilitate deprecation messages being added to the " "documentation. If you have a compelling reason to need " "new lines, feel free to improve this function or open a request at " - "https://github.com/Qiskit/qiskit-terra/issues." + "https://github.com/Qiskit/qiskit/issues." ) if since is None: diff --git a/qiskit/utils/measurement_error_mitigation.py b/qiskit/utils/measurement_error_mitigation.py index 0ef09c997ec5..4702fc0b52b8 100644 --- a/qiskit/utils/measurement_error_mitigation.py +++ b/qiskit/utils/measurement_error_mitigation.py @@ -32,6 +32,7 @@ @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def get_measured_qubits( @@ -80,6 +81,7 @@ def get_measured_qubits( @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def get_measured_qubits_from_qobj(qobj: QasmQobj) -> Tuple[List[int], Dict[str, List[int]]]: @@ -125,6 +127,7 @@ def get_measured_qubits_from_qobj(qobj: QasmQobj) -> Tuple[List[int], Dict[str, @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def build_measurement_error_mitigation_circuits( @@ -204,6 +207,7 @@ def build_measurement_error_mitigation_circuits( @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def build_measurement_error_mitigation_qobj( diff --git a/qiskit/utils/mitigation/__init__.py b/qiskit/utils/mitigation/__init__.py index c79b107cdc0f..54251715424d 100644 --- a/qiskit/utils/mitigation/__init__.py +++ b/qiskit/utils/mitigation/__init__.py @@ -44,7 +44,7 @@ From these calibrations, it is possible to correct the average results of another experiment of interest. These tools are intended for use solely with the :class:`~qiskit.utils.QuantumInstance` class as part of -:mod:`qiskit.algorithms` and :mod:`qiskit.opflow`. +:mod:`qiskit.opflow`. .. autosummary:: :toctree: ../stubs/ diff --git a/qiskit/utils/mitigation/_filters.py b/qiskit/utils/mitigation/_filters.py index 5d566f625116..d29700338d4e 100644 --- a/qiskit/utils/mitigation/_filters.py +++ b/qiskit/utils/mitigation/_filters.py @@ -45,6 +45,7 @@ class MeasurementFilter: @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def __init__(self, cal_matrix: np.matrix, state_labels: list): @@ -227,6 +228,7 @@ class TensoredFilter: @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def __init__(self, cal_matrices: np.matrix, substate_labels_list: list, mit_pattern: list): diff --git a/qiskit/utils/mitigation/circuits.py b/qiskit/utils/mitigation/circuits.py index 2fdeaa6372a6..d6a6935d7c47 100644 --- a/qiskit/utils/mitigation/circuits.py +++ b/qiskit/utils/mitigation/circuits.py @@ -24,6 +24,7 @@ @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def count_keys(num_qubits: int) -> List[str]: @@ -42,6 +43,7 @@ def count_keys(num_qubits: int) -> List[str]: @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def complete_meas_cal( @@ -123,6 +125,7 @@ def complete_meas_cal( @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def tensored_meas_cal( diff --git a/qiskit/utils/mitigation/fitters.py b/qiskit/utils/mitigation/fitters.py index 65357d5dc5b5..a399b863d102 100644 --- a/qiskit/utils/mitigation/fitters.py +++ b/qiskit/utils/mitigation/fitters.py @@ -37,6 +37,7 @@ class CompleteMeasFitter: @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def __init__( @@ -221,6 +222,7 @@ class TensoredMeasFitter: @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def __init__( diff --git a/qiskit/utils/quantum_instance.py b/qiskit/utils/quantum_instance.py index 94c1bed535d4..f27050782fa5 100644 --- a/qiskit/utils/quantum_instance.py +++ b/qiskit/utils/quantum_instance.py @@ -146,6 +146,7 @@ class QuantumInstance: @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def __init__( diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index 9714234ca694..ce26c5cf5b93 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -42,6 +42,7 @@ @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def find_regs_by_name( @@ -107,6 +108,7 @@ def _safe_get_job_status(job: Job, job_id: str, max_job_retries: int, wait: floa @deprecate_func( since="0.24.0", + package_name="qiskit-terra", additional_msg="For code migration guidelines, visit https://qisk.it/qi_migration.", ) def run_circuits( diff --git a/qiskit/visualization/circuit/_utils.py b/qiskit/visualization/circuit/_utils.py index 13c554e0cc2e..252db40e51c5 100644 --- a/qiskit/visualization/circuit/_utils.py +++ b/qiskit/visualization/circuit/_utils.py @@ -197,7 +197,7 @@ def get_bit_register(circuit, bit): return bit_loc.registers[0][0] if bit_loc.registers else None -@deprecate_arg("reverse_bits", since="0.22.0") +@deprecate_arg("reverse_bits", since="0.22.0", package_name="qiskit-terra") def get_bit_reg_index(circuit, bit, reverse_bits=None): """Get the register for a bit if there is one, and the index of the bit from the top of the circuit, or the index of the bit within a register. @@ -285,7 +285,7 @@ def get_wire_label(drawer, register, index, layout=None, cregbundle=True): return wire_label -@deprecate_arg("reverse_bits", since="0.22.0") +@deprecate_arg("reverse_bits", since="0.22.0", package_name="qiskit-terra") def get_condition_label_val(condition, circuit, cregbundle, reverse_bits=None): """Get the label and value list to display a condition diff --git a/qiskit/visualization/circuit/circuit_visualization.py b/qiskit/visualization/circuit/circuit_visualization.py index 87c3f1799cea..a70ef9c2822b 100644 --- a/qiskit/visualization/circuit/circuit_visualization.py +++ b/qiskit/visualization/circuit/circuit_visualization.py @@ -33,6 +33,7 @@ from qiskit import user_config from qiskit.utils import optionals as _optionals +from qiskit.circuit import ControlFlowOp, Measure from . import latex as _latex from . import text as _text from . import matplotlib as _matplotlib @@ -248,6 +249,28 @@ def circuit_drawer( ) cregbundle = False + def check_clbit_in_inst(circuit, cregbundle): + if cregbundle is False: + return False + for inst in circuit.data: + if isinstance(inst.operation, ControlFlowOp): + for block in inst.operation.blocks: + if check_clbit_in_inst(block, cregbundle) is False: + return False + elif inst.clbits and not isinstance(inst.operation, Measure): + if cregbundle is not False: + warn( + "Cregbundle set to False since an instruction needs to refer" + " to individual classical wire", + RuntimeWarning, + 3, + ) + return False + + return True + + cregbundle = check_clbit_in_inst(circuit, cregbundle) + if output == "text": return _text_circuit_drawer( circuit, diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 33bf01f75cf9..f46561c6ed94 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -17,7 +17,6 @@ import collections import itertools import re -from warnings import warn from io import StringIO import numpy as np @@ -135,28 +134,7 @@ def __init__( self._global_phase = self._circuit.global_phase self._calibrations = self._circuit.calibrations self._expr_len = expr_len - - def check_clbit_in_inst(circuit, cregbundle): - if cregbundle is False: - return False - for inst in circuit.data: - if isinstance(inst.operation, ControlFlowOp): - for block in inst.operation.blocks: - if check_clbit_in_inst(block, cregbundle) is False: - return False - elif inst.clbits and not isinstance(inst.operation, Measure): - if cregbundle is not False: - warn( - "Cregbundle set to False since an instruction needs to refer" - " to individual classical wire", - RuntimeWarning, - 3, - ) - return False - - return True - - self._cregbundle = check_clbit_in_inst(circuit, cregbundle) + self._cregbundle = cregbundle self._lwidth1 = 1.0 self._lwidth15 = 1.5 diff --git a/qiskit/visualization/circuit/text.py b/qiskit/visualization/circuit/text.py index c1a28ea37675..8fdc29829693 100644 --- a/qiskit/visualization/circuit/text.py +++ b/qiskit/visualization/circuit/text.py @@ -735,28 +735,7 @@ def __init__( raise ValueError("Vertical compression can only be 'high', 'medium', or 'low'") self.vertical_compression = vertical_compression self._wire_map = {} - - def check_clbit_in_inst(circuit, cregbundle): - if cregbundle is False: - return False - for inst in circuit.data: - if isinstance(inst.operation, ControlFlowOp): - for block in inst.operation.blocks: - if check_clbit_in_inst(block, cregbundle) is False: - return False - elif inst.clbits and not isinstance(inst.operation, Measure): - if cregbundle is not False: - warn( - "Cregbundle set to False since an instruction needs to refer" - " to individual classical wire", - RuntimeWarning, - 3, - ) - return False - - return True - - self.cregbundle = check_clbit_in_inst(circuit, cregbundle) + self.cregbundle = cregbundle if encoding: self.encoding = encoding diff --git a/qiskit/visualization/counts_visualization.py b/qiskit/visualization/counts_visualization.py index fb7ddba13185..a0e4b64a4aa2 100644 --- a/qiskit/visualization/counts_visualization.py +++ b/qiskit/visualization/counts_visualization.py @@ -67,6 +67,7 @@ def _is_deprecated_data_format(data) -> bool: additional_msg="Instead, use ``plot_distribution()``.", predicate=_is_deprecated_data_format, pending=True, + package_name="qiskit-terra", ) def plot_histogram( data, diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index 8e3de6e1186a..961087facc54 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -18,7 +18,9 @@ from rustworkx.visualization import graphviz_draw from qiskit.dagcircuit.dagnode import DAGOpNode, DAGInNode, DAGOutNode -from qiskit.circuit import Qubit +from qiskit.circuit import Qubit, Clbit, ClassicalRegister +from qiskit.circuit.classical import expr +from qiskit.converters import dagdependency_to_circuit from qiskit.utils import optionals as _optionals from qiskit.exceptions import InvalidFileError from .exceptions import VisualizationError @@ -72,32 +74,83 @@ def dag_drawer(dag, scale=0.7, filename=None, style="color"): # the two tradeoffs ere that it will not handle subclasses and it is # slower (which doesn't matter for a visualization function) type_str = str(type(dag)) + register_bit_labels = { + bit: f"{reg.name}[{idx}]" + for reg in list(dag.qregs.values()) + list(dag.cregs.values()) + for (idx, bit) in enumerate(reg) + } if "DAGDependency" in type_str: + # pylint: disable=cyclic-import + from qiskit.visualization.circuit._utils import get_bit_reg_index + + qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} + clbit_indices = {bit: index for index, bit in enumerate(dag.clbits)} graph_attrs = {"dpi": str(100 * scale)} + dag_dep_circ = dagdependency_to_circuit(dag) def node_attr_func(node): if style == "plain": return {} if style == "color": n = {} - n["label"] = str(node.node_id) + ": " + str(node.name) - if node.name == "measure": - n["color"] = "blue" - n["style"] = "filled" - n["fillcolor"] = "lightblue" + args = [] + for count, arg in enumerate(node.qargs + node.cargs): + if count > 4: + args.append("...") + break + if isinstance(arg, Qubit): + f_str = f"q_{qubit_indices[arg]}" + elif isinstance(arg, Clbit): + f_str = f"c_{clbit_indices[arg]}" + else: + f_str = f"{arg.index}" + arg_str = register_bit_labels.get(arg, f_str) + args.append(arg_str) + + n["color"] = "black" + n["label"] = ( + str(node.node_id) + + ": " + + str(node.name) + + " (" + + str(args)[1:-1].replace("'", "") + + ")" + ) if node.name == "barrier": - n["color"] = "black" n["style"] = "filled" - n["fillcolor"] = "green" - if getattr(node.op, "_directive", False): - n["color"] = "black" + n["fillcolor"] = "grey" + elif getattr(node.op, "_directive", False): n["style"] = "filled" n["fillcolor"] = "red" - if getattr(node.op, "condition", None): - n["label"] = str(node.node_id) + ": " + str(node.name) + " (conditional)" - n["color"] = "black" + elif getattr(node.op, "condition", None): + condition = node.op.condition + if isinstance(condition, expr.Expr): + cond_txt = " (cond: [Expr]) (" + elif isinstance(condition[0], ClassicalRegister): + cond_txt = f" (cond: {condition[0].name}, {int(condition[1])}) (" + else: + register, bit_index, reg_index = get_bit_reg_index( + dag_dep_circ, condition[0] + ) + if register is not None: + cond_txt = ( + f" (cond: {register.name}[{reg_index}], {int(condition[1])}) (" + ) + else: + cond_txt = f" (cond: {bit_index}, {int(condition[1])}) (" n["style"] = "filled" - n["fillcolor"] = "lightgreen" + n["fillcolor"] = "green" + n["label"] = ( + str(node.node_id) + + ": " + + str(node.name) + + cond_txt + + str(args)[1:-1].replace("'", "") + + ")" + ) + elif node.name != "measure": # measure is unfilled + n["style"] = "filled" + n["fillcolor"] = "lightblue" return n else: raise VisualizationError("Unrecognized style %s for the dag_drawer." % style) @@ -105,12 +158,6 @@ def node_attr_func(node): edge_attr_func = None else: - register_bit_labels = { - bit: f"{reg.name}[{idx}]" - for reg in list(dag.qregs.values()) + list(dag.cregs.values()) - for (idx, bit) in enumerate(reg) - } - graph_attrs = {"dpi": str(100 * scale)} def node_attr_func(node): diff --git a/qiskit/visualization/pulse/interpolation.py b/qiskit/visualization/pulse/interpolation.py index b5d12a19cbe9..7fa757b33fe4 100644 --- a/qiskit/visualization/pulse/interpolation.py +++ b/qiskit/visualization/pulse/interpolation.py @@ -32,6 +32,7 @@ ), since="0.23.0", removal_timeline="no earlier than 6 months after the release date", + package_name="qiskit-terra", ) def interp1d( time: np.ndarray, samples: np.ndarray, nop: int, kind: str = "linear" @@ -72,6 +73,7 @@ def interp1d( ), since="0.23.0", removal_timeline="no earlier than 6 months after the release date", + package_name="qiskit-terra", ) def step_wise( time: np.ndarray, samples: np.ndarray, nop: int diff --git a/qiskit/visualization/pulse/matplotlib.py b/qiskit/visualization/pulse/matplotlib.py index 619677161ab2..7ca82af4c2e6 100644 --- a/qiskit/visualization/pulse/matplotlib.py +++ b/qiskit/visualization/pulse/matplotlib.py @@ -59,6 +59,7 @@ class EventsOutputChannels: ), since="0.23.0", removal_timeline="no earlier than 6 months after the release date", + package_name="qiskit-terra", ) def __init__(self, t0: int, tf: int): """Create new channel dataset. @@ -296,6 +297,7 @@ class WaveformDrawer: ), since="0.23.0", removal_timeline="no earlier than 6 months after the release date", + package_name="qiskit-terra", ) def __init__(self, style: PulseStyle): """Create new figure. @@ -399,6 +401,7 @@ class ScheduleDrawer: ), since="0.23.0", removal_timeline="no earlier than 6 months after the release date", + package_name="qiskit-terra", ) def __init__(self, style: SchedStyle): """Create new figure. diff --git a/qiskit/visualization/pulse/qcstyle.py b/qiskit/visualization/pulse/qcstyle.py index 5ef6ba33510f..e466774e895d 100644 --- a/qiskit/visualization/pulse/qcstyle.py +++ b/qiskit/visualization/pulse/qcstyle.py @@ -37,6 +37,7 @@ class SchedStyle: "You can choose one of ``IQXStandard``, ``IQXSimple``, ``IQXDebugging``." ), since="0.23.0", + package_name="qiskit-terra", ) def __init__( self, @@ -182,6 +183,7 @@ class PulseStyle: "You can choose one of ``IQXStandard``, ``IQXSimple``, ``IQXDebugging``." ), since="0.23.0", + package_name="qiskit-terra", ) def __init__( self, diff --git a/qiskit/visualization/state_visualization.py b/qiskit/visualization/state_visualization.py index d886666b8f2c..5eea8bfe8e33 100644 --- a/qiskit/visualization/state_visualization.py +++ b/qiskit/visualization/state_visualization.py @@ -447,6 +447,7 @@ def plot_state_city( plot_state_city(state, alpha=0.6) """ + import matplotlib.colors as mcolors from matplotlib import pyplot as plt from mpl_toolkits.mplot3d.art3d import Poly3DCollection @@ -463,8 +464,7 @@ def plot_state_city( column_names = [bin(i)[2:].zfill(num) for i in range(2**num)] row_names = [bin(i)[2:].zfill(num) for i in range(2**num)] - lx = len(datareal[0]) # Work out matrix dimensions - ly = len(datareal[:, 0]) + ly, lx = datareal.shape[:2] xpos = np.arange(0, lx, 1) # Set up a mesh of positions ypos = np.arange(0, ly, 1) xpos, ypos = np.meshgrid(xpos + 0.25, ypos + 0.25) @@ -479,22 +479,21 @@ def plot_state_city( dzi = dataimag.flatten() if color is None: - color = ["#648fff", "#648fff"] + real_color, imag_color = "#648fff", "#648fff" else: if len(color) != 2: raise ValueError("'color' must be a list of len=2.") - if color[0] is None: - color[0] = "#648fff" - if color[1] is None: - color[1] = "#648fff" + real_color = "#648fff" if color[0] is None else color[0] + imag_color = "#648fff" if color[1] is None else color[1] if ax_real is None and ax_imag is None: # set default figure size if figsize is None: - figsize = (15, 5) + figsize = (16, 8) + + fig = plt.figure(figsize=figsize, facecolor="w") + ax1 = fig.add_subplot(1, 2, 1, projection="3d", computed_zorder=False) + ax2 = fig.add_subplot(1, 2, 2, projection="3d", computed_zorder=False) - fig = plt.figure(figsize=figsize) - ax1 = fig.add_subplot(1, 2, 1, projection="3d") - ax2 = fig.add_subplot(1, 2, 2, projection="3d") elif ax_real is not None: fig = ax_real.get_figure() ax1 = ax_real @@ -504,109 +503,103 @@ def plot_state_city( ax1 = None ax2 = ax_imag - max_dzr = max(dzr) - min_dzr = min(dzr) - min_dzi = np.min(dzi) + fig.tight_layout() + + max_dzr = np.max(dzr) max_dzi = np.max(dzi) - # There seems to be a rounding error in which some zero bars are negative - dzr = np.clip(dzr, 0, None) + # Figure scaling variables since fig.tight_layout won't work + fig_width, fig_height = fig.get_size_inches() + max_plot_size = min(fig_width / 2.25, fig_height) + max_font_size = int(3 * max_plot_size) + max_zoom = 10 / (10 + np.sqrt(max_plot_size)) - if ax1 is not None: - fc1 = generate_facecolors(xpos, ypos, zpos, dx, dy, dzr, color[0]) - for idx, cur_zpos in enumerate(zpos): - if dzr[idx] > 0: - zorder = 2 - else: - zorder = 0 - b1 = ax1.bar3d( - xpos[idx], - ypos[idx], - cur_zpos, - dx[idx], - dy[idx], - dzr[idx], + for (ax, dz, col, zlabel) in ( + (ax1, dzr, real_color, "Real"), + (ax2, dzi, imag_color, "Imaginary"), + ): + + if ax is None: + continue + + max_dz = np.max(dz) + min_dz = np.min(dz) + + if isinstance(col, str) and col.startswith("#"): + col = mcolors.to_rgba_array(col) + + dzn = dz < 0 + if np.any(dzn): + fc = generate_facecolors( + xpos[dzn], ypos[dzn], zpos[dzn], dx[dzn], dy[dzn], dz[dzn], col + ) + negative_bars = ax.bar3d( + xpos[dzn], + ypos[dzn], + zpos[dzn], + dx[dzn], + dy[dzn], + dz[dzn], alpha=alpha, - zorder=zorder, + zorder=0.625, ) - b1.set_facecolors(fc1[6 * idx : 6 * idx + 6]) - - xlim, ylim = ax1.get_xlim(), ax1.get_ylim() - x = [xlim[0], xlim[1], xlim[1], xlim[0]] - y = [ylim[0], ylim[0], ylim[1], ylim[1]] - z = [0, 0, 0, 0] - verts = [list(zip(x, y, z))] - - pc1 = Poly3DCollection(verts, alpha=0.15, facecolor="k", linewidths=1, zorder=1) - - if min(dzr) < 0 < max(dzr): - ax1.add_collection3d(pc1) - ax1.set_xticks(np.arange(0.5, lx + 0.5, 1)) - ax1.set_yticks(np.arange(0.5, ly + 0.5, 1)) - if max_dzr != min_dzr: - ax1.axes.set_zlim3d(np.min(dzr), max(np.max(dzr) + 1e-9, max_dzi)) - else: - if min_dzr == 0: - ax1.axes.set_zlim3d(np.min(dzr), max(np.max(dzr) + 1e-9, np.max(dzi))) - else: - ax1.axes.set_zlim3d(auto=True) - ax1.get_autoscalez_on() - ax1.xaxis.set_ticklabels(row_names, fontsize=14, rotation=45, ha="right", va="top") - ax1.yaxis.set_ticklabels(column_names, fontsize=14, rotation=-22.5, ha="left", va="center") - ax1.set_zlabel("Re[$\\rho$]", fontsize=14) - for tick in ax1.zaxis.get_major_ticks(): - tick.label1.set_fontsize(14) - - if ax2 is not None: - fc2 = generate_facecolors(xpos, ypos, zpos, dx, dy, dzi, color[1]) - for idx, cur_zpos in enumerate(zpos): - if dzi[idx] > 0: - zorder = 2 - else: - zorder = 0 - b2 = ax2.bar3d( - xpos[idx], - ypos[idx], - cur_zpos, - dx[idx], - dy[idx], - dzi[idx], + negative_bars.set_facecolor(fc) + + if min_dz < 0 < max_dz: + xlim, ylim = [0, lx], [0, ly] + verts = [list(zip(xlim + xlim[::-1], np.repeat(ylim, 2), [0] * 4))] + plane = Poly3DCollection(verts, alpha=0.25, facecolor="k", linewidths=1) + plane.set_zorder(0.75) + ax.add_collection3d(plane) + + dzp = dz >= 0 + if np.any(dzp): + fc = generate_facecolors( + xpos[dzp], ypos[dzp], zpos[dzp], dx[dzp], dy[dzp], dz[dzp], col + ) + positive_bars = ax.bar3d( + xpos[dzp], + ypos[dzp], + zpos[dzp], + dx[dzp], + dy[dzp], + dz[dzp], alpha=alpha, - zorder=zorder, + zorder=0.875, ) - b2.set_facecolors(fc2[6 * idx : 6 * idx + 6]) - - xlim, ylim = ax2.get_xlim(), ax2.get_ylim() - x = [xlim[0], xlim[1], xlim[1], xlim[0]] - y = [ylim[0], ylim[0], ylim[1], ylim[1]] - z = [0, 0, 0, 0] - verts = [list(zip(x, y, z))] - - pc2 = Poly3DCollection(verts, alpha=0.2, facecolor="k", linewidths=1, zorder=1) - - if min(dzi) < 0 < max(dzi): - ax2.add_collection3d(pc2) - ax2.set_xticks(np.arange(0.5, lx + 0.5, 1)) - ax2.set_yticks(np.arange(0.5, ly + 0.5, 1)) - if min_dzi != max_dzi: - eps = 0 - ax2.axes.set_zlim3d(np.min(dzi), max(np.max(dzr) + 1e-9, np.max(dzi) + eps)) + positive_bars.set_facecolor(fc) + + ax.set_title(f"{zlabel} Amplitude (ρ)", fontsize=max_font_size) + + ax.set_xticks(np.arange(0.5, lx + 0.5, 1)) + ax.set_yticks(np.arange(0.5, ly + 0.5, 1)) + if max_dz != min_dz: + ax.axes.set_zlim3d(min_dz, max(max_dzr + 1e-9, max_dzi)) else: - if min_dzi == 0: - ax2.set_zticks([0]) - eps = 1e-9 - ax2.axes.set_zlim3d(np.min(dzi), max(np.max(dzr) + 1e-9, np.max(dzi) + eps)) + if min_dz == 0: + ax.axes.set_zlim3d(min_dz, max(max_dzr + 1e-9, max_dzi)) else: - ax2.axes.set_zlim3d(auto=True) + ax.axes.set_zlim3d(auto=True) + ax.get_autoscalez_on() + + ax.xaxis.set_ticklabels( + row_names, fontsize=max_font_size, rotation=45, ha="right", va="top" + ) + ax.yaxis.set_ticklabels( + column_names, fontsize=max_font_size, rotation=-22.5, ha="left", va="center" + ) + + for tick in ax.zaxis.get_major_ticks(): + tick.label1.set_fontsize(max_font_size) + tick.label1.set_horizontalalignment("left") + tick.label1.set_verticalalignment("bottom") - ax2.xaxis.set_ticklabels(row_names, fontsize=14, rotation=45, ha="right", va="top") - ax2.yaxis.set_ticklabels(column_names, fontsize=14, rotation=-22.5, ha="left", va="center") - ax2.set_zlabel("Im[$\\rho$]", fontsize=14) - for tick in ax2.zaxis.get_major_ticks(): - tick.label1.set_fontsize(14) - ax2.get_autoscalez_on() + ax.set_box_aspect(aspect=(4, 4, 4), zoom=max_zoom) + ax.set_xmargin(0) + ax.set_ymargin(0) - fig.suptitle(title, fontsize=16) + fig.suptitle(title, fontsize=max_font_size * 1.25) + fig.subplots_adjust(top=0.9, bottom=0, left=0, right=1, hspace=0, wspace=0) if ax_real is None and ax_imag is None: matplotlib_close_if_inline(fig) if filename is None: @@ -1290,6 +1283,7 @@ def state_to_latex( @deprecate_func( additional_msg="For similar functionality, see sympy's ``nsimplify`` and ``latex`` functions.", since="0.23.0", + package_name="qiskit-terra", ) def num_to_latex_ket(raw_value: complex, first_term: bool, decimals: int = 10) -> Optional[str]: """Convert a complex number to latex code suitable for a ket expression @@ -1309,6 +1303,7 @@ def num_to_latex_ket(raw_value: complex, first_term: bool, decimals: int = 10) - @deprecate_func( additional_msg="For similar functionality, see sympy's ``nsimplify`` and ``latex`` functions.", since="0.23.0", + package_name="qiskit-terra", ) def numbers_to_latex_terms(numbers: List[complex], decimals: int = 10) -> List[str]: """Convert a list of numbers to latex formatted terms diff --git a/qiskit_pkg/setup.py b/qiskit_pkg/setup.py index 6148d3d8a965..4846b2d0b9f6 100644 --- a/qiskit_pkg/setup.py +++ b/qiskit_pkg/setup.py @@ -26,11 +26,11 @@ with open(README_PATH) as readme_file: README = readme_file.read() -requirements = ["qiskit-terra==0.45.0rc1"] +requirements = ["qiskit-terra==1.0.0"] setup( name="qiskit", - version="0.45.0rc1", + version="1.0.0", description="Software for developing quantum computing programs", long_description=README, long_description_content_type="text/markdown", diff --git a/releasenotes/notes/0.20/bit-slots-17d6649872da0440.yaml b/releasenotes/notes/0.20/bit-slots-17d6649872da0440.yaml index 257ad056723a..dba8bdcfd9d5 100644 --- a/releasenotes/notes/0.20/bit-slots-17d6649872da0440.yaml +++ b/releasenotes/notes/0.20/bit-slots-17d6649872da0440.yaml @@ -1,7 +1,7 @@ --- upgrade: - | - The classes :class:`.Qubit`, :class:`.Clbit` and :class:`.AncillaQubit` now + The classes :class:`~qiskit.circuit.Qubit`, :class:`.Clbit` and :class:`.AncillaQubit` now have the ``__slots__`` attribute. This is to reduce their memory usage. As a side effect, they can no longer have arbitrary data attached as attributes to them. This is very unlikely to have any effect on downstream code other diff --git a/releasenotes/notes/0.21/change-instruction-data-scalar-81f2066ca2435933.yaml b/releasenotes/notes/0.21/change-instruction-data-scalar-81f2066ca2435933.yaml index 3b4b877580c0..5e9a3a8e041b 100644 --- a/releasenotes/notes/0.21/change-instruction-data-scalar-81f2066ca2435933.yaml +++ b/releasenotes/notes/0.21/change-instruction-data-scalar-81f2066ca2435933.yaml @@ -3,7 +3,7 @@ upgrade: - | The data type of each element in :attr:`.QuantumCircuit.data` has changed. It used to be a simple 3-tuple of an :class:`~.circuit.Instruction`, a list - of :class:`.Qubit`\ s, and a list of :class:`.Clbit`\ s, whereas it is now + of :class:`~qiskit.circuit.Qubit`\ s, and a list of :class:`.Clbit`\ s, whereas it is now an instance of :class:`.CircuitInstruction`. The attributes of this new class are :attr:`~.CircuitInstruction.operation`, diff --git a/releasenotes/notes/0.21/fix-reverse_bits-with-registerless-bits-6d17597b99640fb0.yaml b/releasenotes/notes/0.21/fix-reverse_bits-with-registerless-bits-6d17597b99640fb0.yaml index 6fa9789cfdbb..ba7c7d43af05 100644 --- a/releasenotes/notes/0.21/fix-reverse_bits-with-registerless-bits-6d17597b99640fb0.yaml +++ b/releasenotes/notes/0.21/fix-reverse_bits-with-registerless-bits-6d17597b99640fb0.yaml @@ -2,7 +2,7 @@ fixes: - | Fixed :meth:`.QuantumCircuit.reverse_bits` with circuits containing registerless - :class:`.Qubit` and :class:`.Clbit`. For example, the following will now work:: + :class:`~qiskit.circuit.Qubit` and :class:`.Clbit`. For example, the following will now work:: from qiskit.circuit import QuantumCircuit, Qubit, Clbit diff --git a/releasenotes/notes/0.22/circuit-initialize-and-prepare-single-qubit-e25dacc8f873bc01.yaml b/releasenotes/notes/0.22/circuit-initialize-and-prepare-single-qubit-e25dacc8f873bc01.yaml index 0648666c6147..2209c0e4398b 100644 --- a/releasenotes/notes/0.22/circuit-initialize-and-prepare-single-qubit-e25dacc8f873bc01.yaml +++ b/releasenotes/notes/0.22/circuit-initialize-and-prepare-single-qubit-e25dacc8f873bc01.yaml @@ -2,4 +2,4 @@ fixes: - | Fixed a bug in :meth:`.QuantumCircuit.initialize` and :meth:`.QuantumCircuit.prepare_state` - that caused them to not accept a single :class:`Qubit` as argument to initialize. + that caused them to not accept a single :class:`~qiskit.circuit.Qubit` as argument to initialize. diff --git a/releasenotes/notes/0.22/denselayout-loose-bits-3e66011432bc6232.yaml b/releasenotes/notes/0.22/denselayout-loose-bits-3e66011432bc6232.yaml index 5798e708eaf6..ab5fbdd36ccb 100644 --- a/releasenotes/notes/0.22/denselayout-loose-bits-3e66011432bc6232.yaml +++ b/releasenotes/notes/0.22/denselayout-loose-bits-3e66011432bc6232.yaml @@ -2,6 +2,6 @@ fixes: - | Fixed an issue in the :class:`~.DenseLayout` transpiler pass where any - loose :class:`~.Qubit` objects (i.e. not part of a :class:`~.QuantumRegister`) + loose :class:`~qiskit.circuit.Qubit` objects (i.e. not part of a :class:`~.QuantumRegister`) that were part of a :class:`~.QuantumCircuit` would not be included in the output :class:`~.Layout` that was generated by the pass. diff --git a/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml b/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml index 528b7facd6f7..a9fa4703d678 100644 --- a/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml +++ b/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml @@ -3,7 +3,7 @@ fixes: - | QPY deserialisation will no longer add extra :class:`.Clbit` instances to the circuit if there are both loose :class:`.Clbit`\ s in the circuit and more - :class:`.Qubit`\ s than :class:`.Clbit`\ s. + :class:`~qiskit.circuit.Qubit`\ s than :class:`.Clbit`\ s. - | QPY deserialisation will no longer add registers named `q` and `c` if the input circuit contained only loose bits. diff --git a/releasenotes/notes/0.24/fix-deprecated-bit-qpy-roundtrip-9a23a795aa677c71.yaml b/releasenotes/notes/0.24/fix-deprecated-bit-qpy-roundtrip-9a23a795aa677c71.yaml index a6f1a0db1f68..7ee1ab0e620e 100644 --- a/releasenotes/notes/0.24/fix-deprecated-bit-qpy-roundtrip-9a23a795aa677c71.yaml +++ b/releasenotes/notes/0.24/fix-deprecated-bit-qpy-roundtrip-9a23a795aa677c71.yaml @@ -1,8 +1,8 @@ --- fixes: - | - The deprecated :class:`.Qubit` and :class:`.Clbit` properties :attr:`~.Qubit.register` and - :attr:`~.Qubit.index` will now be correctly round-tripped by QPY (:mod:`qiskit.qpy`) in all + The deprecated :class:`~qiskit.circuit.Qubit` and :class:`.Clbit` properties :attr:`~.Qubit.register` and + :attr:`~qiskit.circuit.Qubit.index` will now be correctly round-tripped by QPY (:mod:`qiskit.qpy`) in all valid usages of :class:`.QuantumRegister` and :class:`.ClassicalRegister`. In earlier releases in the Terra 0.23 series, this information would be lost. In versions before 0.23.0, this information was partially reconstructed but could be incorrect or produce invalid circuits for diff --git a/releasenotes/notes/0.25/fix-bit-copy-4b2f7349683f616a.yaml b/releasenotes/notes/0.25/fix-bit-copy-4b2f7349683f616a.yaml index 41d76acdb609..8828796e744e 100644 --- a/releasenotes/notes/0.25/fix-bit-copy-4b2f7349683f616a.yaml +++ b/releasenotes/notes/0.25/fix-bit-copy-4b2f7349683f616a.yaml @@ -2,6 +2,6 @@ fixes: - | Fixed an issue with copying circuits with new-style :class:`.Clbit`\ s and - :class:`.Qubit`\ s (bits without registers) where references to these bits + :class:`~qiskit.circuit.Qubit`\ s (bits without registers) where references to these bits from the containing circuit could be broken, causing issues with serialization and circuit visualization. diff --git a/releasenotes/notes/fix-input-normalization-of-transpile-initial-layout.yaml b/releasenotes/notes/0.45/fix-input-normalization-of-transpile-initial-layout.yaml similarity index 100% rename from releasenotes/notes/fix-input-normalization-of-transpile-initial-layout.yaml rename to releasenotes/notes/0.45/fix-input-normalization-of-transpile-initial-layout.yaml diff --git a/releasenotes/notes/0.45/h_basic_aer-3fc5e6776f0de9c1.yaml b/releasenotes/notes/0.45/h_basic_aer-3fc5e6776f0de9c1.yaml index 530836dbe597..df80391c3306 100644 --- a/releasenotes/notes/0.45/h_basic_aer-3fc5e6776f0de9c1.yaml +++ b/releasenotes/notes/0.45/h_basic_aer-3fc5e6776f0de9c1.yaml @@ -1,5 +1,5 @@ --- upgrade: - | - The `QasmSimulator` python-based simulator included in :class:`qiskit.providers.basicaer` - now includes `h` (:class:`.HGate`), `p` (:class:`.PhaseGate`), and `u` (:class:`.UGate`) in its basis gate set. + The ``QasmSimulator`` python-based simulator included in :class:`qiskit.providers.basicaer` + now includes ``'h'`` (:class:`.HGate`), ``'p'`` (:class:`.PhaseGate`), and ``'u'`` (:class:`.UGate`) in its basis gate set. diff --git a/releasenotes/notes/0.45/remove-deprecated-code-in-0.19-a97ccfec62405b9a.yaml b/releasenotes/notes/0.45/remove-deprecated-code-in-0.19-a97ccfec62405b9a.yaml index eb62402ab3c4..3e04728590ad 100644 --- a/releasenotes/notes/0.45/remove-deprecated-code-in-0.19-a97ccfec62405b9a.yaml +++ b/releasenotes/notes/0.45/remove-deprecated-code-in-0.19-a97ccfec62405b9a.yaml @@ -1,10 +1,10 @@ --- upgrade: - | - The argument ``qubits`` in the method :meth:`qiskit.transpiler.instruction_durations.InstructionDurations.get`, does not accept :class:`.Qubit` (or a list of them) any more. This functionality was deprecated in Qiskit 0.33 (with Terra 0.19), released on Dec 2021. Instead, use an integer for the qubit indices. + The argument ``qubits`` in the method :meth:`qiskit.transpiler.instruction_durations.InstructionDurations.get`, does not accept :class:`qiskit.circuit.Qubit` (or a list of them) any more. This functionality was deprecated in Qiskit 0.33 (with Terra 0.19), released on Dec 2021. Instead, use an integer for the qubit indices. - | The argument ``channel`` in the method :meth:`qiskit.providers.models.backendconfiguration.PulseBackendConfiguration.control` is removed. It was deprecated in Qiskit 0.33 (with Terra 0.19), released on Dec 2021. Instead use the ``qubits`` argument. - | - The class ``qiskit.qobj.Qobj`` is removed. It was deprecated in Qiskit 0.33 (with Terra 0.19), released on Dec 2021. Instead, use :class:`qiskit.qobj.QasmQobj` or :class:`qiskit.qobj.PulseQobj`. \ No newline at end of file + The class ``qiskit.qobj.Qobj`` is removed. It was deprecated in Qiskit 0.33 (with Terra 0.19), released on Dec 2021. Instead, use :class:`qiskit.qobj.QasmQobj` or :class:`qiskit.qobj.PulseQobj`. diff --git a/releasenotes/notes/0.45/remove-deprecated-code-in-0.22-128475199e6f3cc2.yaml b/releasenotes/notes/0.45/remove-deprecated-code-in-0.22-128475199e6f3cc2.yaml index d4851ded41ab..13b3953ecd9e 100644 --- a/releasenotes/notes/0.45/remove-deprecated-code-in-0.22-128475199e6f3cc2.yaml +++ b/releasenotes/notes/0.45/remove-deprecated-code-in-0.22-128475199e6f3cc2.yaml @@ -2,11 +2,11 @@ upgrade: - | The method :meth:`qiskit.quantum_info.pauli_basis` - does not accept `pauli_list` argument any more. + does not accept ``pauli_list`` argument any more. It was deprecated in Qiskit 0.39 (with Terra 0.22), released on Oct 2022. - | The function ``random_stabilizer_table`` in the class :class:`qiskit.quantum_info.random` is removed. It was deprecated in Qiskit 0.39 (with Terra 0.22), released on Oct 2022. - Instead, use :func:``qiskit.quantum_info.random.random_pauli_list``. + Instead, use :func:`qiskit.quantum_info.random.random_pauli_list`. diff --git a/releasenotes/notes/0.45/remove-deprecated-code-in-0.22.0-139fa09ee0cc6df9.yaml b/releasenotes/notes/0.45/remove-deprecated-code-in-0.22.0-139fa09ee0cc6df9.yaml index 25869ed728b8..9a97d7edddf1 100644 --- a/releasenotes/notes/0.45/remove-deprecated-code-in-0.22.0-139fa09ee0cc6df9.yaml +++ b/releasenotes/notes/0.45/remove-deprecated-code-in-0.22.0-139fa09ee0cc6df9.yaml @@ -5,7 +5,7 @@ upgrade: - | Replaced the argument ``qobj[Qobj]`` in :meth:`qiskit.providers.aer.QasmSimulator.run()` with ``run_input[QuantumCircuit or Schedule or list]`` - Here is an example to migrate yor code:: + Here is an example to migrate your code:: # Importing necessary Qiskit libraries from qiskit import transpile, QuantumCircuit diff --git a/releasenotes/notes/0.45/remove-deprecated-in-quantum-info-0f9bd2b0c093307d.yaml b/releasenotes/notes/0.45/remove-deprecated-in-quantum-info-0f9bd2b0c093307d.yaml index a49cf58be8fb..cd77daf912b1 100644 --- a/releasenotes/notes/0.45/remove-deprecated-in-quantum-info-0f9bd2b0c093307d.yaml +++ b/releasenotes/notes/0.45/remove-deprecated-in-quantum-info-0f9bd2b0c093307d.yaml @@ -1,7 +1,7 @@ --- upgrade: - | - The classes ``qiskit.quantum_info.PauliTable`` and `qiskit.quantum_info.StabilizerTable` + The classes ``qiskit.quantum_info.PauliTable`` and ``qiskit.quantum_info.StabilizerTable`` are removed. The function ``random_pauli_table`` is also removed. They were deprecated in Qiskit 0.43 (with Terra 0.24), released in May 2023. Instead, you should use :class:`.PauliList` and :func:`.random_pauli_list`. diff --git a/releasenotes/notes/0.45/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml b/releasenotes/notes/0.45/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml index 5f609ad91b89..6c7b67336b80 100644 --- a/releasenotes/notes/0.45/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml +++ b/releasenotes/notes/0.45/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml @@ -3,42 +3,23 @@ upgrade: - | The function :func:`~qiskit.execute_function.execute` does not accept the arguments `qobj_id` and `qobj_header` any more. - Their use was deprecated in Qiskit 0.37 (with Terra 0.21), released on June 2022. + Their use was deprecated in Qiskit 0.37 (with Terra 0.21), released on June 2022. - | - The transpilation pass ``qiskit.transpiler.passes.ALAPSchedule`` is removed. It use was deprecated - in Qiskit 0.37 (with Terra 0.21), released on June 2022 and replaced by - :class:`~.transpiler.passes.ALAPScheduleAnalysis`, which is an - analysis pass. - - | - The transpilation pass ``qiskit.transpiler.passes.ASAPSchedule`` is removed. It use was deprecated - in Qiskit 0.37 (with Terra 0.21), released on June 2022. It has been superseded by - :class:`~.ASAPScheduleAnalysis` and the new scheduling workflow. - - | - The transpilation pass ``qiskit.transpiler.passes.DynamicalDecoupling`` is removed. It use was deprecated - in Qiskit 0.37 (with Terra 0.21), released on June 2022. - Instead, use :class:`~.transpiler.passes.PadDynamicalDecoupling`, which performs the same - function but requires scheduling and alignment analysis passes to run prior to it. - - | - The transpilation pass ``qiskit.transpiler.passes.AlignMeasures`` is removed. It use was deprecated - in Qiskit 0.37 (with Terra 0.21), released on June 2022. - Instead, use :class:`~.ConstrainedReschedule`, which performs the same function - and also supports aligning to additional timing constraints. - - | - The transpilation pass ``qiskit.transpiler.passes.CXDirection`` is removed. It use was deprecated + The transpilation pass ``qiskit.transpiler.passes.CXDirection`` is removed. It use was deprecated in Qiskit 0.37 (with Terra 0.21), released on June 2022. Instead, use the more generic :class:`~.GateDirection` pass. - | - The transpilation pass ``qiskit.transpiler.passes.CheckCXDirection`` is removed. It use was deprecated + The transpilation pass ``qiskit.transpiler.passes.CheckCXDirection`` is removed. It use was deprecated in Qiskit 0.37 (with Terra 0.21), released on June 2022. Instead, use the more generic :class:`~.CheckGateDirection` pass. - | The methods ``to_dict`` in the classes :class:`.pulse.transforms.AlignmentKind`, `.pulse.transforms.AlignEquispaced`, and :class:`.pulse.transforms.AlignFunc` are removed. - They were deprecated + They were deprecated in Qiskit 0.37 (with Terra 0.21), released on June 2022. - | The argument ``circuits`` in the method :meth:`qiskit.qpy.interface.dump` - is removed as its usage was deprecated + is removed as its usage was deprecated in Qiskit 0.37 (with Terra 0.21), released on June 2022. Instead, use the argument ``programs``, which behaves identically. - + diff --git a/releasenotes/notes/0.45/single-pulse-rx-cal-347aadcee7bfe60b.yaml b/releasenotes/notes/0.45/single-pulse-rx-cal-347aadcee7bfe60b.yaml index c6cc05c907cd..48b02694f64c 100644 --- a/releasenotes/notes/0.45/single-pulse-rx-cal-347aadcee7bfe60b.yaml +++ b/releasenotes/notes/0.45/single-pulse-rx-cal-347aadcee7bfe60b.yaml @@ -4,7 +4,7 @@ features: Two new transpiler passes are added to generate single-pulse RX gate calibrations on the fly. These single-pulse RX calibrations will reduce the gate time in half, as described in P.Gokhale et al, Optimized Quantum Compilation for Near-Term Algorithms with OpenPulse - (2020), `arXiv:2004.11205 `. + (2020), `arXiv:2004.11205 `__. To reduce the amount of RX calibration data that needs to be generated, :class:`~qiskit.transpiler.passes.optimization.normalize_rx_angle.NormalizeRXAngle` @@ -22,4 +22,4 @@ features: Such single-pulse calibrations reduces the RX gate time in half, compared to the conventional sequence that consists of two SX pulses. - There could be an improvement in fidelity due to this reduction in gate time. \ No newline at end of file + There could be an improvement in fidelity due to this reduction in gate time. diff --git a/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml b/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml index c6f125f7e40a..7b8313e6b080 100644 --- a/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml +++ b/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml @@ -30,23 +30,23 @@ features: The following standard library gates are now instances of :class:`~.SingletonGate`: - * :class:`~.DCXGate` - * :class:`~.ECRGate` - * :class:`~.HGate` - * :class:`~.IGate` - * :class:`~.iSwapGate` - * :class:`~.SGate` - * :class:`~.SdgGate` - * :class:`~.SwapGate` - * :class:`~.SXGate` - * :class:`~.SXdgGate` - * :class:`~.TGate` - * :class:`~.TdgGate` - * :class:`~.XGate` - * :class:`~.RCCXGate` - * :class:`~.RC3XGate` - * :class:`~.YGate` - * :class:`~.ZGate` + * :class:`~.DCXGate` + * :class:`~.ECRGate` + * :class:`~.HGate` + * :class:`~.IGate` + * :class:`~.iSwapGate` + * :class:`~.SGate` + * :class:`~.SdgGate` + * :class:`~.SwapGate` + * :class:`~.SXGate` + * :class:`~.SXdgGate` + * :class:`~.TGate` + * :class:`~.TdgGate` + * :class:`~.XGate` + * :class:`~.RCCXGate` + * :class:`~.RC3XGate` + * :class:`~.YGate` + * :class:`~.ZGate` This means that if these classes are instantiated as (e.g.) ``XGate()`` using all the constructor defaults, they will all share a single global @@ -81,25 +81,25 @@ features: The following standard library gates are now instances of :class:`~.SingletonControlledGate`: - * :class:`~.CHGate` - * :class:`~.CSGate` - * :class:`~.CSdgGate` - * :class:`~.CSwapGate` - * :class:`~.CSXGate` - * :class:`~.CXGate` - * :class:`~.CCXGate` - * :class:`~.C3SXGate` - * :class:`~.C3XGate` - * :class:`~.C4XGate` - * :class:`~.CYGate` - * :class:`~.CZGate` + * :class:`~.CHGate` + * :class:`~.CSGate` + * :class:`~.CSdgGate` + * :class:`~.CSwapGate` + * :class:`~.CSXGate` + * :class:`~.CXGate` + * :class:`~.CCXGate` + * :class:`~.C3SXGate` + * :class:`~.C3XGate` + * :class:`~.C4XGate` + * :class:`~.CYGate` + * :class:`~.CZGate` This means that unless a ``label``, ``condition``, ``duration``, ``unit``, or ``ctrl_state`` are set on the instance at creation time they will all share a single global instance whenever a new gate object is created. This results in large reduction in the memory overhead for > 1 object of these types. - | - Added a new method :meth`.Instruction.to_mutable` and attribute + Added a new method :meth:`.Instruction.to_mutable` and attribute :attr:`.Instruction.mutable` which is used to get a mutable copy and check whether an :class:`~.circuit.Instruction` object is mutable. With the introduction of :class:`~.SingletonGate` these methods can be used to have a unified interface @@ -155,7 +155,7 @@ upgrade: instead of the original object. ``label``, ``duration`` and ``unit`` can be given as keyword arguments to these gates at construction time, and a mutable instance will be returned automatically. - This change was necssary as part of converting + This change was necessary as part of converting these classes to be :class:`~.SingletonGate` and :class:`~.SingletonControlledGate` types which greatly reduces the memory footprint of repeated instances of these gates. - | @@ -164,7 +164,7 @@ upgrade: :class:`~.QuantumCircuit` or :class:`~.DAGCircuit` classes it is important to note that the use of shared references for instances is much more common now. Previously, it was possible to reuse and share an instance of a - a circuit operation it wasn't very commonly used and a copy would generate + circuit operation it wasn't very commonly used and a copy would generate a unique instance. This has changed starting in this release because of :class:`~.SingletonInstruction` and :class:`.SingletonGate` being made available (and a large number of standard library gates now built off of it). If your usage of these objects is diff --git a/releasenotes/notes/0.45/sparse-pauli-op-apply-layout-43149125d29ad015.yaml b/releasenotes/notes/0.45/sparse-pauli-op-apply-layout-43149125d29ad015.yaml index 2129e354288d..237b1caa491b 100644 --- a/releasenotes/notes/0.45/sparse-pauli-op-apply-layout-43149125d29ad015.yaml +++ b/releasenotes/notes/0.45/sparse-pauli-op-apply-layout-43149125d29ad015.yaml @@ -2,9 +2,9 @@ features: - | Added a new method, :meth:`~.SparsePauliOp.apply_layout`, - to the :class:~.SparsePauliOp` class. This method is used to apply + to the :class:`~.SparsePauliOp` class. This method is used to apply a :class:`~.TranspileLayout` layout from the transpiler - to a :class:~.SparsePauliOp` observable that was built for an + to a :class:`~.SparsePauliOp` observable that was built for an input circuit to the transpiler. This enables working with :class:`~.BaseEstimator` implementations and local transpilation more easily. For example:: diff --git a/releasenotes/notes/0.45/transpile-layout-improvements-118dd902d93e5b96.yaml b/releasenotes/notes/0.45/transpile-layout-improvements-118dd902d93e5b96.yaml index ed6761ccd0d2..139708ea41cd 100644 --- a/releasenotes/notes/0.45/transpile-layout-improvements-118dd902d93e5b96.yaml +++ b/releasenotes/notes/0.45/transpile-layout-improvements-118dd902d93e5b96.yaml @@ -28,7 +28,7 @@ features: (the mapping of input circuit qubits to the final position in the output). This is distinct from the :attr:`~.TranspileLayout.final_layout` attribute which is the permutation caused by routing as a :class:`.Layout` object. The :meth:`~.TranspileLayout.final_index_layout` method - returns a list to showthe output position for each qubit in the input circuit to the transpiler. + returns a list to show the output position for each qubit in the input circuit to the transpiler. For example, with an original circuit:: qc = QuantumCircuit(3) diff --git a/releasenotes/notes/dep-estimator-paulilist-ef2bb2865b66f012.yaml b/releasenotes/notes/dep-estimator-paulilist-ef2bb2865b66f012.yaml new file mode 100644 index 000000000000..4fad2f28e073 --- /dev/null +++ b/releasenotes/notes/dep-estimator-paulilist-ef2bb2865b66f012.yaml @@ -0,0 +1,7 @@ +--- +deprecations: + - | + Deprecates using a :class:`~.PauliList` as an observable that is implicitly + converted to a :class:`~.SparsePauliOp` with coefficients 1 when calling + :meth:`.Estimator.run`. Users should instead explicitly convert the argument + using ``SparsePauliOp(pauli_list)`` first. diff --git a/releasenotes/notes/dep-primitives-attr-6b4ec9fde34c42e8.yaml b/releasenotes/notes/dep-primitives-attr-6b4ec9fde34c42e8.yaml new file mode 100644 index 000000000000..43e98cf86166 --- /dev/null +++ b/releasenotes/notes/dep-primitives-attr-6b4ec9fde34c42e8.yaml @@ -0,0 +1,13 @@ +--- +deprecations: + - | + The :class:`~.BaseEstimator` attributes :attr:`~.BaseEstimator.circuits` + :attr:`~.BaseEstimator.observables`, :attr:`~.BaseEstimator.parameters` + and :class:`~.BaseSampler` attributes :attr:`~.BaseSampler.circuits` + :attr:`~.BaseSampler.parameters` have been deprecated. + - | + Deprecates the :class:`~.BaseEstimator` ``_circuits``, ``_observables`` + and ``_parameters``, and :class:`~.BaseSampler` ``_circuits`` and + ``_parameters`` attributes set during `__init__`. Any subclasses + relying on these methods should now manually initialize them to avoid + deprecation warnings. diff --git a/releasenotes/notes/deprecate-complex-amp-old-qpy-ae2359e42aa8afcd.yaml b/releasenotes/notes/deprecate-complex-amp-old-qpy-ae2359e42aa8afcd.yaml new file mode 100644 index 000000000000..3aa1de1a8d85 --- /dev/null +++ b/releasenotes/notes/deprecate-complex-amp-old-qpy-ae2359e42aa8afcd.yaml @@ -0,0 +1,13 @@ +--- +deprecations: + - | + Loading library :class:`~.qiskit.pulse.ScalableSymbolicPulse` objects with + complex ``amp`` parameter from + qpy files of version 5 or lower (Qiskit Terra < 0.23.0) is now deprecated. + Following the deprecation in Qiskit 1.0.0, complex `amp` will be automatically + converted to float (`amp`,`angle`). The change applies to the pulses: + + * :class:`~.qiskit.pulse.Constant` + * :class:`~.qiskit.pulse.Drag` + * :class:`~.qiskit.pulse.Gaussian` + * :class:`~.qiskit.pulse.GaussianSquare` diff --git a/releasenotes/notes/deprecate-discrete-pulse-library-d2482407d7965972.yaml b/releasenotes/notes/deprecate-discrete-pulse-library-d2482407d7965972.yaml new file mode 100644 index 000000000000..a1324ea03669 --- /dev/null +++ b/releasenotes/notes/deprecate-discrete-pulse-library-d2482407d7965972.yaml @@ -0,0 +1,25 @@ +--- +deprecations: + - | + The discrete pulse library is now deprecated and will be removed in a future release. This includes: + + * :func:`~qiskit.pulse.library.constant` + * :func:`~qiskit.pulse.library.zero` + * :func:`~qiskit.pulse.library.square` + * :func:`~qiskit.pulse.library.sawtooth` + * :func:`~qiskit.pulse.library.triangle` + * :func:`~qiskit.pulse.library.cos` + * :func:`~qiskit.pulse.library.sin` + * :func:`~qiskit.pulse.library.gaussian` + * :func:`~qiskit.pulse.library.gaussian_deriv` + * :func:`~qiskit.pulse.library.sech` + * :func:`~qiskit.pulse.library.sech_deriv` + * :func:`~qiskit.pulse.library.gaussian_square` + * :func:`~qiskit.pulse.library.drag` + + Instead, use the corresponding :class:`~qiskit.pulse.SymbolicPulse`, with :meth:`~.SymbolicPulse.get_waveform()`. + For example, instead of ``pulse.gaussian(100,0.5,10)`` use ``pulse.Gaussian(100,0.5,10).get_waveform()``. + + Note that the phase of both ``Sawtooth`` and ``Square`` is defined such that a phase of :math:``2\\pi`` + shifts by a full cycle, contrary to the discrete counterpart. Also note that complex amplitude support is + deprecated in the symbolic pulse library - use ``float`` ``amp`` and ``angle`` instead. diff --git a/releasenotes/notes/fix-aqc-optimizer-typehint-34b54c6278d23f79.yaml b/releasenotes/notes/fix-aqc-optimizer-typehint-34b54c6278d23f79.yaml new file mode 100644 index 000000000000..bca717c18745 --- /dev/null +++ b/releasenotes/notes/fix-aqc-optimizer-typehint-34b54c6278d23f79.yaml @@ -0,0 +1,21 @@ +--- +fixes: + - | + The use of the (deprecated) ``Optimizer`` class on :class:`~.AQC` did not have a + non-deprecated alternative path, which should have been introduced in + the original ``qiskit-algorithms`` deprecation PR + [#10406](https://github.com/Qiskit/qiskit/pull/10406). + It now accepts a callable that implements the :class:`~.Minimizer` protocol, + as explicitly stated in the deprecation warning. The callable can look like the + following example: + + .. code-block:: python + + from scipy.optimize import minimize + from qiskit.transpiler.synthesis.aqc.aqc import AQC + + optimizer = partial(minimize, args=(), method="L-BFGS-B", options={"maxiter": 200}) + aqc = AQC(optimizer=optimizer) + + + diff --git a/releasenotes/notes/fix-barrier-arg-list-check-ff69f37ede6bdf6c.yaml b/releasenotes/notes/fix-barrier-arg-list-check-ff69f37ede6bdf6c.yaml new file mode 100644 index 000000000000..b620a72666f3 --- /dev/null +++ b/releasenotes/notes/fix-barrier-arg-list-check-ff69f37ede6bdf6c.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed an issue with the :class:`.Barrier` class. When adding a + :class:`.Barrier` instance to a :class:`.QuantumCircuit` with the + :meth:`.QuantumCircuit.append` method previously there was no validation + that the size of the barrier matched the qargs specified. diff --git a/releasenotes/notes/fix-block-collapser-cregs-6c2d2dc6931d7bad.yaml b/releasenotes/notes/fix-block-collapser-cregs-6c2d2dc6931d7bad.yaml new file mode 100644 index 000000000000..57543efb27bd --- /dev/null +++ b/releasenotes/notes/fix-block-collapser-cregs-6c2d2dc6931d7bad.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + The :class:`.BlockCollapser` transpiler pass will now correctly handle circuits that contain + more than one condition on the same classical register. diff --git a/releasenotes/notes/fix-blueprint-circuit-_append-b4d6c9c41db860f5.yaml b/releasenotes/notes/fix-blueprint-circuit-_append-b4d6c9c41db860f5.yaml new file mode 100644 index 000000000000..0596bc50d964 --- /dev/null +++ b/releasenotes/notes/fix-blueprint-circuit-_append-b4d6c9c41db860f5.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + :class:`.BlueprintCircuit` subclasses will now behave correctly when the semi-public method + :meth:`.QuantumCircuit._append` is used with the blueprint in an unbuilt state, *i.e.* the + circuit will be built before attempting the append. diff --git a/releasenotes/notes/fix-error-message-qpy-version-cf0763da22ce2224.yaml b/releasenotes/notes/fix-error-message-qpy-version-cf0763da22ce2224.yaml new file mode 100644 index 000000000000..67690c011126 --- /dev/null +++ b/releasenotes/notes/fix-error-message-qpy-version-cf0763da22ce2224.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixed an issue with :func:`.qpy.load` when attempting to load a QPY format + version that is not supported by this version of Qiskit it will now display + a descriptive error message. Previously, it would raise an internal error + because of the incompatibility between the formats which was difficult to + debug. If the QPY format verison is not supported that indicates the Qiskit + version will need to be upgraded to read the QPY payload. diff --git a/releasenotes/notes/fix-param-global-phase-31547267f6124552.yaml b/releasenotes/notes/fix-param-global-phase-31547267f6124552.yaml new file mode 100644 index 000000000000..13048f626dc4 --- /dev/null +++ b/releasenotes/notes/fix-param-global-phase-31547267f6124552.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed compatibility of :class:`.DynamicalDecoupling` and + :class:`.PadDynamicalDecoupling` with circuits that have a parameterized + global phase. + Fixed `#10569 `__. diff --git a/releasenotes/notes/fix-parameterized-self-inverse-7cb2d68b273640f8.yaml b/releasenotes/notes/fix-parameterized-self-inverse-7cb2d68b273640f8.yaml new file mode 100644 index 000000000000..7ca796cdd2a5 --- /dev/null +++ b/releasenotes/notes/fix-parameterized-self-inverse-7cb2d68b273640f8.yaml @@ -0,0 +1,22 @@ +--- +fixes: + - | + Fixed an issue with the :class:`~.InverseCancellation` pass where it would + incorrectly cancel gates passed in as self inverses with a parameter + value, if a run of gates had a different parameter value. For example:: + + from math import pi + + from qiskit.circuit.library import RZGate + from qiskit.circuit import QuantumCircuit + from qiskit.transpiler.passes import InverseCancellation + + inverse_pass = InverseCancellation([RZGate(0)]) + + qc = QuantumCircuit(1) + qc.rz(0, 0) + qc.rz(pi, 0) + + inverse_pass(qc) + + would previously have incorrectly cancelled the two rz gates. diff --git a/releasenotes/notes/fix-plot-state-city-viz-2963c83bcf3d3347.yaml b/releasenotes/notes/fix-plot-state-city-viz-2963c83bcf3d3347.yaml new file mode 100644 index 000000000000..8205c2ded5f9 --- /dev/null +++ b/releasenotes/notes/fix-plot-state-city-viz-2963c83bcf3d3347.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Adjusted zoom, fontsize, and margins to fit the plot better for more figure + sizes. + - | + Corrected the Z-ordering behavior of bars and the zero-amplitude plane. + - | + Corrected display of negative real value bars diff --git a/releasenotes/notes/fix-qreg-visualization-after-layout-42d3e643b923d8bc.yaml b/releasenotes/notes/fix-qreg-visualization-after-layout-42d3e643b923d8bc.yaml new file mode 100644 index 000000000000..8e887846e432 --- /dev/null +++ b/releasenotes/notes/fix-qreg-visualization-after-layout-42d3e643b923d8bc.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fixed a bug in :class:`~.SabreLayout` where it would fail to add the layout + register information to the property set. This affected circuit visualization, as + ``circuit.draw()`` after transpilation with certain optimization levels would show + the full ``Qubit[register]`` label rather than the expected register name + (e.g. ``q0``). diff --git a/releasenotes/notes/fix_backend_name-e84661707058b529.yaml b/releasenotes/notes/fix_backend_name-e84661707058b529.yaml new file mode 100644 index 000000000000..ed0cbe9a2658 --- /dev/null +++ b/releasenotes/notes/fix_backend_name-e84661707058b529.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed an issue in the :class:`.QuantumInstance` class where it was assuming + all ``AerSimulator`` backends were always :class:`.BackendV1`. This would cause + combatibility issues with the 0.13.0 release of ``qiskit-aer`` which is starting to + use :class:`.BackendV2` for `AerSimulator`` backends. diff --git a/releasenotes/notes/new-exception-too-wide-3231c1df15952445.yaml b/releasenotes/notes/new-exception-too-wide-3231c1df15952445.yaml new file mode 100644 index 000000000000..d3bfa72c071e --- /dev/null +++ b/releasenotes/notes/new-exception-too-wide-3231c1df15952445.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Added a new exception class :exc:`.CircuitToWideForTarget` which + subclasses :exc:`.TranspilerError`. It's used in places where a + :exc:`.TranspilerError` was previously raised when the error was that + the number of circuit qubits was larger than the target backend's qubits. + The new class enables more differentiating between this error condition and + other :exc:`.TranspilerError`\s. diff --git a/releasenotes/notes/overlap-circuit-barriers-63b9b1be9c1da100.yaml b/releasenotes/notes/overlap-circuit-barriers-63b9b1be9c1da100.yaml new file mode 100644 index 000000000000..dcae6e5585f1 --- /dev/null +++ b/releasenotes/notes/overlap-circuit-barriers-63b9b1be9c1da100.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Fixed a bug which caused :class:`~qiskit.circuit.library.UnitaryOverlap` to error upon initialization if given an input circuit containing a barrier. diff --git a/releasenotes/notes/preset-passmanager-coupling-map-89e588e4260cb214.yaml b/releasenotes/notes/preset-passmanager-coupling-map-89e588e4260cb214.yaml new file mode 100644 index 000000000000..5830a57d2e2d --- /dev/null +++ b/releasenotes/notes/preset-passmanager-coupling-map-89e588e4260cb214.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Enhanced the :func:`.generate_preset_pass_manager` function to allow users to + provide a coupling map as a legacy-format list. Previously, users were required + to provide a :class:`.CouplingMap` object. diff --git a/releasenotes/notes/remove-dag-none-be220777dc246803.yaml b/releasenotes/notes/remove-dag-none-be220777dc246803.yaml new file mode 100644 index 000000000000..44fbd7d134ee --- /dev/null +++ b/releasenotes/notes/remove-dag-none-be220777dc246803.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + It is no longer allowable to pass ``None`` as the ``qargs`` or ``cargs`` parameters in + :meth:`.DAGCircuit.apply_operation_back` and :meth:`~.DAGCircuit.apply_operation_front`. If you + want to explicitly pass an empty argument, use the empty tuple ``()`` instead. diff --git a/releasenotes/notes/remove-namespace-hooks-995bdb7515a9fe35.yaml b/releasenotes/notes/remove-namespace-hooks-995bdb7515a9fe35.yaml new file mode 100644 index 000000000000..df8601ba031c --- /dev/null +++ b/releasenotes/notes/remove-namespace-hooks-995bdb7515a9fe35.yaml @@ -0,0 +1,14 @@ +--- +upgrade: + - | + Support for extensions of the ``qiskit`` and ``qiskit.providers`` namespaces + by external packages has been removed. Support for doing this was deprecated + in the Qiskit 0.44.0 release. In the past, the Qiskit project was composed + of elements that extended a shared namespace and hook points were added + to enable doing that. However, it was not intended for these interfaces to + ever be used by other packages. Now that the overall Qiskit package is no + longer using that packaging model, leaving the possibility for these + extensions carry more risk than benefits and has therefore been removed. + If you’re maintaining a package that extends the Qiskit namespace (i.e. + your users import from ``qiskit.x`` or ``qiskit.providers.y``) you should + transition to using a standalone Python namespace for your package. diff --git a/releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml b/releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml new file mode 100644 index 000000000000..51112c0d064a --- /dev/null +++ b/releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml @@ -0,0 +1,9 @@ +--- +upgrade: + - | + The ``qiskit.algorithms`` module has been removed, following its deprecation in + Qiskit 0.44. The primitive-based algorithms from this module have been migrated to a standalone library (``qiskit_algorithms``) + and can be found on PyPi or `GitHub `_. + The decision to migrate the algorithms module to a separate package + was made to clarify the purpose Qiskit and make a distinction between the tools + and libraries built on top of it. diff --git a/releasenotes/notes/singletonize-instructions-78723f68cd0ac03f.yaml b/releasenotes/notes/singletonize-instructions-78723f68cd0ac03f.yaml new file mode 100644 index 000000000000..ced4a873bd6a --- /dev/null +++ b/releasenotes/notes/singletonize-instructions-78723f68cd0ac03f.yaml @@ -0,0 +1,32 @@ +--- +features: + - | + The following standard library instructions are now instances of + :class:`~.SingletonInstruction`: + + * :class:`~.Measure` + * :class:`~.Reset` + + This means that if these classes are instantiated as (e.g.) ``Measure()`` using + all the constructor defaults, they will all share a single global + instance. This results in large reduction in the memory overhead for > 1 + object of these types and significantly faster object construction time. +upgrade: + - | + The following standard library instructions: + + * :class:`~.Measure` + * :class:`~.Reset` + + are immutable, unless the attributes ``label``, ``duration`` and ``unit`` are given as keyword + arguments during class construction. + The attributes :attr:`~.Instruction.label`, :attr:`~.Instruction.duration`, :attr:`~.Instruction.unit`, + and :attr:`~.Instruction.condition` attributes are all not publicly accessible and setting these attributes + directly is not allowed and it will raise an exception. If they are needed for a particular + instance you must ensure you have a mutable instance using :meth:`.Instruction.to_mutable` + and use :meth:`.Instruction.c_if` for :attr:`~.Instruction.condition` + For the singleton variant of these instructions, there is a special attribute + :attr:`~.SingletonInstruction._singleton_lookup_key`, that when called generates a key based on the input + arguments, which can be used for identifying and indexing these instructions within the framework. + + diff --git a/releasenotes/notes/symbolic-pulse-disable-validation-19cd8506b3a839b6.yaml b/releasenotes/notes/symbolic-pulse-disable-validation-19cd8506b3a839b6.yaml new file mode 100644 index 000000000000..0a7c6c599000 --- /dev/null +++ b/releasenotes/notes/symbolic-pulse-disable-validation-19cd8506b3a839b6.yaml @@ -0,0 +1,14 @@ +--- +upgrade: + - | + Validation of :class:`qiskit.pulse.SymbolicPulse` objects can now be disabled. By setting + the class attribute :attr:`qiskit.pulse.SymbolicPulse.disable_validation` to ``False`` + the method :meth:`validate_parameters` will not be triggered for all `SymbolicPulse` objects. + The automatic validation hindered JAX compatibility of the symbolic pulse library, and this + upgrade will make it easier to use Qiskit Pulse with JAX. + + Note that all library pulses automatically called :meth:`validate_parameters`. However, as part + of the upgrade the call was moved directly to the initialization process of + :class:`qiskit.pulse.SymbolicPulse`. While this doesn't change the behaviour of library pulses, + custom symbolic pulses which did not call :meth:`validate_parameters` will now trigger the + method. The new class attribute will allow to easily disable this. diff --git a/releasenotes/notes/update-dag-dependency-drawer-d06d4ae660c1cbc2.yaml b/releasenotes/notes/update-dag-dependency-drawer-d06d4ae660c1cbc2.yaml new file mode 100644 index 000000000000..e26bf092b786 --- /dev/null +++ b/releasenotes/notes/update-dag-dependency-drawer-d06d4ae660c1cbc2.yaml @@ -0,0 +1,22 @@ +--- +features: + - | + The :func:`.dag_drawer` has been updated for the :class:`.DAGDependency`. These + drawings have a new color scheme, and the nodes now indicate the ``Qubits`` and + ``Clbits`` that are used by the node. If the node has a ``condition`` the drawings + will indicate that as well. + + .. code-block:: python + + from qiskit.circuit import QuantumCircuit + from qiskit.converters import circuit_to_dagdependency + + qc = QuantumCircuit(3, 2) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + qc.x(1) + qc.barrier() + qc.measure(0, 0) + dagdep = circuit_to_dagdependency(qc) + dagdep.draw() diff --git a/releasenotes/notes/vf2-threading-b778a36de5b8832a.yaml b/releasenotes/notes/vf2-threading-b778a36de5b8832a.yaml new file mode 100644 index 000000000000..e813f9ed5129 --- /dev/null +++ b/releasenotes/notes/vf2-threading-b778a36de5b8832a.yaml @@ -0,0 +1,8 @@ +--- +other: + - | + The :class:`.VF2Layout` and :class:`.VF2PostLayout` transpiler passes previously would + potentially run their internal scoring using multithreading if the input + circuit's were sufficiently large. However, the multithreading usage has + been removed from the passes as it was shown to cause a performance + regression instead of an improvement like originally intended. diff --git a/releasenotes/notes/vf2postlayout-no-better-solution-eb5ced3c8a60ea23.yaml b/releasenotes/notes/vf2postlayout-no-better-solution-eb5ced3c8a60ea23.yaml new file mode 100644 index 000000000000..25d361e51896 --- /dev/null +++ b/releasenotes/notes/vf2postlayout-no-better-solution-eb5ced3c8a60ea23.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + :class:`.VF2PostLayout` now distinguishes between 'no solution' and 'no better solution' when + determining a :class:`.Layout` for a given quantum circuit. 'no better solution' is set when the + initial layout of a quantum circuit is also the optimal one, i.e. incurs the least cost in terms of + error rates. diff --git a/setup.py b/setup.py index 3162a11198b3..97ca604fd757 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ setup( name="qiskit-terra", - version="0.45.0rc1", + version="1.0.0", description="Software for developing quantum computing programs", long_description=README, long_description_content_type="text/markdown", diff --git a/test/python/algorithms/__init__.py b/test/python/algorithms/__init__.py deleted file mode 100644 index 5303c5b1256c..000000000000 --- a/test/python/algorithms/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Algorithms test module""" - -from .algorithms_test_case import QiskitAlgorithmsTestCase - -__all__ = ["QiskitAlgorithmsTestCase"] diff --git a/test/python/algorithms/algorithms_test_case.py b/test/python/algorithms/algorithms_test_case.py deleted file mode 100644 index 8fc9effd0bf7..000000000000 --- a/test/python/algorithms/algorithms_test_case.py +++ /dev/null @@ -1,21 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# 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. - -"""Algorithms Test Case""" - -from qiskit.test import QiskitTestCase - - -class QiskitAlgorithmsTestCase(QiskitTestCase): - """Algorithms test Case""" - - pass diff --git a/test/python/algorithms/eigensolvers/__init__.py b/test/python/algorithms/eigensolvers/__init__.py deleted file mode 100644 index e2113e5c114e..000000000000 --- a/test/python/algorithms/eigensolvers/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Tests for the eigensolvers.""" diff --git a/test/python/algorithms/eigensolvers/test_numpy_eigensolver.py b/test/python/algorithms/eigensolvers/test_numpy_eigensolver.py deleted file mode 100644 index 13f2f9a67e4b..000000000000 --- a/test/python/algorithms/eigensolvers/test_numpy_eigensolver.py +++ /dev/null @@ -1,216 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""Test NumPyEigensolver""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt - -from qiskit.algorithms.eigensolvers import NumPyEigensolver -from qiskit.algorithms import AlgorithmError -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import Operator, SparsePauliOp, Pauli, ScalarOp - -H2_SPARSE_PAULI = SparsePauliOp( - ["II", "ZI", "IZ", "ZZ", "XX"], - coeffs=[ - -1.052373245772859, - 0.39793742484318045, - -0.39793742484318045, - -0.01128010425623538, - 0.18093119978423156, - ], -) - -H2_OP = Operator(H2_SPARSE_PAULI.to_matrix()) - -H2_PAULI = PauliSumOp(H2_SPARSE_PAULI) - - -@ddt -class TestNumPyEigensolver(QiskitAlgorithmsTestCase): - """Test NumPy Eigen solver""" - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_ce(self, op): - """Test basics""" - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=op, aux_operators=[]) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_ce_k4(self, op): - """Test for k=4 eigenvalues""" - algo = NumPyEigensolver(k=4) - result = algo.compute_eigenvalues(operator=op, aux_operators=[]) - self.assertEqual(len(result.eigenvalues), 4) - self.assertEqual(len(result.eigenstates), 4) - self.assertEqual(result.eigenvalues.dtype, np.float64) - np.testing.assert_array_almost_equal( - result.eigenvalues, [-1.85727503, -1.24458455, -0.88272215, -0.22491125] - ) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_ce_k4_filtered(self, op): - """Test for k=4 eigenvalues with filter""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return v >= -1 - - algo = NumPyEigensolver(k=4, filter_criterion=criterion) - result = algo.compute_eigenvalues(operator=op, aux_operators=[]) - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(len(result.eigenstates), 2) - self.assertEqual(result.eigenvalues.dtype, np.float64) - np.testing.assert_array_almost_equal(result.eigenvalues, [-0.88272215, -0.22491125]) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_ce_k4_filtered_empty(self, op): - """Test for k=4 eigenvalues with filter always returning False""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return False - - algo = NumPyEigensolver(k=4, filter_criterion=criterion) - result = algo.compute_eigenvalues(operator=op, aux_operators=[]) - self.assertEqual(len(result.eigenvalues), 0) - self.assertEqual(len(result.eigenstates), 0) - - @data( - SparsePauliOp(["X"], coeffs=[1.0]), - SparsePauliOp(["Y"], coeffs=[1.0]), - SparsePauliOp(["Z"], coeffs=[1.0]), - ) - def test_ce_k1_1q(self, op): - """Test for 1 qubit operator""" - algo = NumPyEigensolver(k=1) - result = algo.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal(result.eigenvalues, [-1]) - - @data( - SparsePauliOp(["X"], coeffs=[1.0]), - SparsePauliOp(["Y"], coeffs=[1.0]), - SparsePauliOp(["Z"], coeffs=[1.0]), - ) - def test_ce_k2_1q(self, op): - """Test for 1 qubit operator""" - algo = NumPyEigensolver(k=2) - result = algo.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal(result.eigenvalues, [-1, 1]) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_aux_operators_list(self, op): - """Test list-based aux_operators.""" - aux_op1 = Operator(SparsePauliOp(["II"], coeffs=[2.0]).to_matrix()) - aux_op2 = SparsePauliOp(["II", "ZZ", "YY", "XX"], coeffs=[0.5, 0.5, 0.5, -0.5]) - aux_ops = [aux_op1, aux_op2] - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=op, aux_operators=aux_ops) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operators_evaluated), 1) - self.assertEqual(len(result.aux_operators_evaluated[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][0], 0, places=6) - # metadata - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][1].pop("variance"), 0.0) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - result = algo.compute_eigenvalues(operator=op, aux_operators=extra_ops) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operators_evaluated), 1) - self.assertEqual(len(result.aux_operators_evaluated[0]), 4) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][0], 0, places=6) - self.assertIsNone(result.aux_operators_evaluated[0][2], None) - self.assertEqual(result.aux_operators_evaluated[0][3][0], 0.0) - # metadata - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][1].pop("variance"), 0.0) - self.assertEqual(result.aux_operators_evaluated[0][3][1].pop("variance"), 0.0) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_aux_operators_dict(self, op): - """Test dict-based aux_operators.""" - aux_op1 = Operator(SparsePauliOp(["II"], coeffs=[2.0]).to_matrix()) - aux_op2 = SparsePauliOp(["II", "ZZ", "YY", "XX"], coeffs=[0.5, 0.5, 0.5, -0.5]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=op, aux_operators=aux_ops) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operators_evaluated), 1) - self.assertEqual(len(result.aux_operators_evaluated[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][0], 0, places=6) - # metadata - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][1].pop("variance"), 0.0) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - result = algo.compute_eigenvalues(operator=op, aux_operators=extra_ops) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operators_evaluated), 1) - self.assertEqual(len(result.aux_operators_evaluated[0]), 3) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operators_evaluated[0]["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operators_evaluated[0].keys()) - # metadata - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][1].pop("variance"), 0.0) - self.assertAlmostEqual( - result.aux_operators_evaluated[0]["zero_operator"][1].pop("variance"), 0.0 - ) - - def test_pauli_op(self): - """Test simple pauli operator""" - algo = NumPyEigensolver(k=1) - result = algo.compute_eigenvalues(operator=Pauli("X")) - np.testing.assert_array_almost_equal(result.eigenvalues, [-1]) - - def test_scalar_op(self): - """Test scalar operator""" - algo = NumPyEigensolver(k=1) - with self.assertRaises(AlgorithmError): - algo.compute_eigenvalues(operator=ScalarOp(1)) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/eigensolvers/test_vqd.py b/test/python/algorithms/eigensolvers/test_vqd.py deleted file mode 100644 index 47eab6404f2c..000000000000 --- a/test/python/algorithms/eigensolvers/test_vqd.py +++ /dev/null @@ -1,453 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022 -# -# 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. - -"""Test VQD""" - -import unittest -import warnings -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt - -from qiskit import QuantumCircuit -from qiskit.algorithms.eigensolvers import VQD, VQDResult -from qiskit.algorithms import AlgorithmError -from qiskit.algorithms.optimizers import COBYLA, L_BFGS_B, SLSQP, SPSA -from qiskit.algorithms.state_fidelities import ComputeUncompute -from qiskit.circuit.library import TwoLocal, RealAmplitudes -from qiskit.opflow import PauliSumOp -from qiskit.primitives import Sampler, Estimator -from qiskit.quantum_info import SparsePauliOp -from qiskit.quantum_info.operators import Operator -from qiskit.utils import algorithm_globals - - -H2_SPARSE_PAULI = SparsePauliOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] -) -H2_OP = Operator(H2_SPARSE_PAULI.to_matrix()) - -H2_PAULI = PauliSumOp(H2_SPARSE_PAULI) - - -@ddt -class TestVQD(QiskitAlgorithmsTestCase): - """Test VQD""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - self.h2_energy = -1.85727503 - self.h2_energy_excited = [-1.85727503, -1.24458455, -0.88272215, -0.22491125] - - self.ryrz_wavefunction = TwoLocal( - rotation_blocks=["ry", "rz"], entanglement_blocks="cz", reps=1 - ) - self.ry_wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - self.estimator = Estimator() - self.estimator_shots = Estimator(options={"shots": 1024, "seed": self.seed}) - self.fidelity = ComputeUncompute(Sampler()) - self.betas = [50, 50] - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_basic_operator(self, op): - """Test the VQD without aux_operators.""" - wavefunction = self.ryrz_wavefunction - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=wavefunction, - optimizer=COBYLA(), - betas=self.betas, - ) - - result = vqd.compute_eigenvalues(operator=op) - - with self.subTest(msg="test eigenvalue"): - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=1 - ) - - with self.subTest(msg="test dimension of optimal point"): - self.assertEqual(len(result.optimal_points[-1]), 8) - - with self.subTest(msg="assert cost_function_evals is set"): - self.assertIsNotNone(result.cost_function_evals) - - with self.subTest(msg="assert optimizer_times is set"): - self.assertIsNotNone(result.optimizer_times) - - with self.subTest(msg="assert return ansatz is set"): - job = self.estimator.run( - result.optimal_circuits, - [op] * len(result.optimal_points), - result.optimal_points, - ) - np.testing.assert_array_almost_equal(job.result().values, result.eigenvalues, 6) - - with self.subTest(msg="assert returned values are eigenvalues"): - np.testing.assert_array_almost_equal( - result.optimal_values, self.h2_energy_excited[:2], decimal=3 - ) - - def test_full_spectrum(self): - """Test obtaining all eigenvalues.""" - vqd = VQD(self.estimator, self.fidelity, self.ryrz_wavefunction, optimizer=L_BFGS_B(), k=4) - result = vqd.compute_eigenvalues(H2_PAULI) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - - @data(H2_PAULI, H2_SPARSE_PAULI) - def test_beta_autoeval(self, op): - """Test beta autoevaluation for different operator types.""" - - with self.assertLogs(level="INFO") as logs: - vqd = VQD( - self.estimator_shots, self.fidelity, self.ryrz_wavefunction, optimizer=L_BFGS_B() - ) - _ = vqd.compute_eigenvalues(op) - - # the first log message shows the value of beta[0] - beta = float(logs.output[0].split()[-1]) - self.assertAlmostEqual(beta, 20.40459399499687, 4) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_mismatching_num_qubits(self, op): - """Ensuring circuit and operator mismatch is caught""" - wavefunction = QuantumCircuit(1) - optimizer = SLSQP(maxiter=50) - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - k=1, - ansatz=wavefunction, - optimizer=optimizer, - betas=self.betas, - ) - with self.assertRaises(AlgorithmError): - _ = vqd.compute_eigenvalues(operator=op) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_missing_varform_params(self, op): - """Test specifying a variational form with no parameters raises an error.""" - circuit = QuantumCircuit(op.num_qubits) - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=circuit, - optimizer=SLSQP(), - k=1, - betas=self.betas, - ) - with self.assertRaises(AlgorithmError): - vqd.compute_eigenvalues(operator=op) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_callback(self, op): - """Test the callback on VQD.""" - history = {"eval_count": [], "parameters": [], "mean": [], "metadata": [], "step": []} - - def store_intermediate_result(eval_count, parameters, mean, metadata, step): - history["eval_count"].append(eval_count) - history["parameters"].append(parameters) - history["mean"].append(mean) - history["metadata"].append(metadata) - history["step"].append(step) - - optimizer = COBYLA(maxiter=3) - wavefunction = self.ry_wavefunction - - vqd = VQD( - estimator=self.estimator_shots, - fidelity=self.fidelity, - ansatz=wavefunction, - optimizer=optimizer, - callback=store_intermediate_result, - betas=self.betas, - ) - - vqd.compute_eigenvalues(operator=op) - - self.assertTrue(all(isinstance(count, int) for count in history["eval_count"])) - self.assertTrue(all(isinstance(mean, float) for mean in history["mean"])) - self.assertTrue(all(isinstance(metadata, dict) for metadata in history["metadata"])) - self.assertTrue(all(isinstance(count, int) for count in history["step"])) - for params in history["parameters"]: - self.assertTrue(all(isinstance(param, float) for param in params)) - - ref_eval_count = [1, 2, 3, 1, 2, 3] - ref_mean = [-1.07, -1.45, -1.37, 37.43, 48.55, 28.94] - # new ref_mean for statevector simulator. The old unit test was on qasm - # and the ref_mean values were slightly different. - - ref_step = [1, 1, 1, 2, 2, 2] - - np.testing.assert_array_almost_equal(history["eval_count"], ref_eval_count, decimal=0) - np.testing.assert_array_almost_equal(history["mean"], ref_mean, decimal=2) - np.testing.assert_array_almost_equal(history["step"], ref_step, decimal=0) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_vqd_optimizer(self, op): - """Test running same VQD twice to re-use optimizer, then switch optimizer""" - - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=RealAmplitudes(), - optimizer=SLSQP(), - k=2, - betas=self.betas, - ) - - def run_check(): - result = vqd.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=3 - ) - - run_check() - - with self.subTest("Optimizer re-use"): - run_check() - - with self.subTest("Optimizer replace"): - vqd.optimizer = L_BFGS_B() - run_check() - - with self.subTest("Batched optimizer replace"): - vqd.optimizer = SLSQP(maxiter=60, max_evals_grouped=10) - run_check() - - with self.subTest("SPSA replace"): - # SPSA takes too long to converge, so we will - # only check that it runs with no errors. - vqd.optimizer = SPSA(maxiter=5, learning_rate=0.01, perturbation=0.01) - result = vqd.compute_eigenvalues(operator=op) - self.assertIsInstance(result, VQDResult) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_optimizer_list(self, op): - """Test sending an optimizer list""" - - optimizers = [SLSQP(), L_BFGS_B()] - initial_point_1 = [ - 1.70256666, - -5.34843975, - -0.39542903, - 5.99477786, - -2.74374986, - -4.85284669, - 0.2442925, - -1.51638917, - ] - initial_point_2 = [ - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - ] - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=RealAmplitudes(), - optimizer=optimizers, - initial_point=[initial_point_1, initial_point_2], - k=2, - betas=self.betas, - ) - - result = vqd.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=3 - ) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_aux_operators_list(self, op): - """Test list-based aux_operators.""" - wavefunction = self.ry_wavefunction - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=wavefunction, - optimizer=SLSQP(), - k=2, - betas=self.betas, - ) - - # Start with an empty list - result = vqd.compute_eigenvalues(op, aux_operators=[]) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2 - ) - self.assertIsNone(result.aux_operators_evaluated) - - # Go again with two auxiliary operators - aux_op1 = SparsePauliOp.from_list([("II", 2.0)]) - aux_op2 = SparsePauliOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - result = vqd.compute_eigenvalues(op, aux_operators=aux_ops) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2 - ) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2, places=2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][0], 0, places=2) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][1][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][1][1], dict) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - result = vqd.compute_eigenvalues(op, aux_operators=extra_ops) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2 - ) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2, places=2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][0], 0, places=2) - self.assertEqual(result.aux_operators_evaluated[0][2][0], 0.0) - self.assertEqual(result.aux_operators_evaluated[0][3][0], 0.0) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][0][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][1][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][3][1], dict) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_aux_operators_dict(self, op): - """Test dictionary compatibility of aux_operators""" - wavefunction = self.ry_wavefunction - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=wavefunction, - optimizer=SLSQP(), - betas=self.betas, - ) - - # Start with an empty dictionary - result = vqd.compute_eigenvalues(op, aux_operators={}) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2 - ) - self.assertIsNone(result.aux_operators_evaluated) - - # Go again with two auxiliary operators - aux_op1 = SparsePauliOp.from_list([("II", 2.0)]) - aux_op2 = SparsePauliOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - result = vqd.compute_eigenvalues(op, aux_operators=aux_ops) - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(result.eigenvalues.dtype, np.complex128) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503, 2) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertEqual(len(result.aux_operators_evaluated[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][0], 0, places=1) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0]["aux_op1"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0]["aux_op2"][1], dict) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - result = vqd.compute_eigenvalues(op, aux_operators=extra_ops) - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(result.eigenvalues.dtype, np.complex128) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503, places=5) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertEqual(len(result.aux_operators_evaluated[0]), 3) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][0], 0.0, places=2) - self.assertEqual(result.aux_operators_evaluated[0]["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operators_evaluated[0].keys()) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0]["aux_op1"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0]["aux_op2"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0]["zero_operator"][1], dict) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_aux_operator_std_dev(self, op): - """Test non-zero standard deviations of aux operators.""" - wavefunction = self.ry_wavefunction - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=wavefunction, - initial_point=[ - 1.70256666, - -5.34843975, - -0.39542903, - 5.99477786, - -2.74374986, - -4.85284669, - 0.2442925, - -1.51638917, - ], - optimizer=COBYLA(maxiter=0), - betas=self.betas, - ) - - # Go again with two auxiliary operators - aux_op1 = SparsePauliOp.from_list([("II", 2.0)]) - aux_op2 = SparsePauliOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - result = vqd.compute_eigenvalues(op, aux_operators=aux_ops) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2.0, places=1) - self.assertAlmostEqual( - result.aux_operators_evaluated[0][1][0], 0.0019531249999999445, places=1 - ) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][0][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][1][1], dict) - - # Go again with additional None and zero operators - aux_ops = [*aux_ops, None, 0] - result = vqd.compute_eigenvalues(op, aux_operators=aux_ops) - self.assertEqual(len(result.aux_operators_evaluated[0]), 4) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2.0, places=1) - self.assertAlmostEqual( - result.aux_operators_evaluated[0][1][0], 0.0019531249999999445, places=1 - ) - self.assertEqual(result.aux_operators_evaluated[0][2][0], 0.0) - self.assertEqual(result.aux_operators_evaluated[0][3][0], 0.0) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][0][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][1][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][2][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][3][1], dict) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/evolvers/__init__.py b/test/python/algorithms/evolvers/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/test/python/algorithms/evolvers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. diff --git a/test/python/algorithms/evolvers/test_evolution_problem.py b/test/python/algorithms/evolvers/test_evolution_problem.py deleted file mode 100644 index dc9d45f548a2..000000000000 --- a/test/python/algorithms/evolvers/test_evolution_problem.py +++ /dev/null @@ -1,136 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Test evolver problem class.""" -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import data, ddt, unpack -from numpy.testing import assert_raises - -from qiskit.algorithms.evolvers.evolution_problem import EvolutionProblem -from qiskit.circuit import Parameter -from qiskit.opflow import Y, Z, One, X, Zero - - -@ddt -class TestEvolutionProblem(QiskitAlgorithmsTestCase): - """Test evolver problem class.""" - - def test_init_default(self): - """Tests that all default fields are initialized correctly.""" - hamiltonian = Y - time = 2.5 - initial_state = One - - with self.assertWarns(DeprecationWarning): - evo_problem = EvolutionProblem(hamiltonian, time, initial_state) - - expected_hamiltonian = Y - expected_time = 2.5 - expected_initial_state = One - expected_aux_operators = None - expected_t_param = None - expected_param_value_dict = None - - self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) - self.assertEqual(evo_problem.time, expected_time) - self.assertEqual(evo_problem.initial_state, expected_initial_state) - self.assertEqual(evo_problem.aux_operators, expected_aux_operators) - self.assertEqual(evo_problem.t_param, expected_t_param) - self.assertEqual(evo_problem.param_value_dict, expected_param_value_dict) - - def test_init_all(self): - """Tests that all fields are initialized correctly.""" - t_parameter = Parameter("t") - with self.assertWarns(DeprecationWarning): - hamiltonian = t_parameter * Z + Y - time = 2 - initial_state = One - aux_operators = [X, Y] - param_value_dict = {t_parameter: 3.2} - - with self.assertWarns(DeprecationWarning): - evo_problem = EvolutionProblem( - hamiltonian, - time, - initial_state, - aux_operators, - t_param=t_parameter, - param_value_dict=param_value_dict, - ) - expected_hamiltonian = Y + t_parameter * Z - - expected_time = 2 - expected_initial_state = One - expected_aux_operators = [X, Y] - expected_t_param = t_parameter - expected_param_value_dict = {t_parameter: 3.2} - - with self.assertWarns(DeprecationWarning): - self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) - - self.assertEqual(evo_problem.time, expected_time) - self.assertEqual(evo_problem.initial_state, expected_initial_state) - self.assertEqual(evo_problem.aux_operators, expected_aux_operators) - self.assertEqual(evo_problem.t_param, expected_t_param) - self.assertEqual(evo_problem.param_value_dict, expected_param_value_dict) - - @data([Y, -1, One], [Y, -1.2, One], [Y, 0, One]) - @unpack - def test_init_errors(self, hamiltonian, time, initial_state): - """Tests expected errors are thrown on invalid time argument.""" - with self.assertWarns(DeprecationWarning), assert_raises(ValueError): - _ = EvolutionProblem(hamiltonian, time, initial_state) - - def test_validate_params(self): - """Tests expected errors are thrown on parameters mismatch.""" - param_x = Parameter("x") - param_y = Parameter("y") - with self.subTest(msg="Parameter missing in dict."): - with self.assertWarns(DeprecationWarning): - hamiltonian = param_x * X + param_y * Y - - param_dict = {param_y: 2} - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem( - hamiltonian, 2, Zero, param_value_dict=param_dict - ) - with assert_raises(ValueError): - evolution_problem.validate_params() - - with self.subTest(msg="Empty dict."): - with self.assertWarns(DeprecationWarning): - hamiltonian = param_x * X + param_y * Y - - param_dict = {} - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem( - hamiltonian, 2, Zero, param_value_dict=param_dict - ) - with assert_raises(ValueError): - evolution_problem.validate_params() - - with self.subTest(msg="Extra parameter in dict."): - with self.assertWarns(DeprecationWarning): - hamiltonian = param_x * X + param_y * Y - - param_dict = {param_y: 2, param_x: 1, Parameter("z"): 1} - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem( - hamiltonian, 2, Zero, param_value_dict=param_dict - ) - with assert_raises(ValueError): - evolution_problem.validate_params() - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/evolvers/test_evolution_result.py b/test/python/algorithms/evolvers/test_evolution_result.py deleted file mode 100644 index 2fe40d8c66f8..000000000000 --- a/test/python/algorithms/evolvers/test_evolution_result.py +++ /dev/null @@ -1,50 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -"""Class for testing evolution result.""" -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit.algorithms.evolvers.evolution_result import EvolutionResult -from qiskit.opflow import Zero - - -class TestEvolutionResult(QiskitAlgorithmsTestCase): - """Class for testing evolution result and relevant metadata.""" - - def test_init_state(self): - """Tests that a class is initialized correctly with an evolved_state.""" - evolved_state = Zero - with self.assertWarns(DeprecationWarning): - evo_result = EvolutionResult(evolved_state=evolved_state) - - expected_state = Zero - expected_aux_ops_evaluated = None - - self.assertEqual(evo_result.evolved_state, expected_state) - self.assertEqual(evo_result.aux_ops_evaluated, expected_aux_ops_evaluated) - - def test_init_observable(self): - """Tests that a class is initialized correctly with an evolved_observable.""" - evolved_state = Zero - evolved_aux_ops_evaluated = [(5j, 5j), (1.0, 8j), (5 + 1j, 6 + 1j)] - with self.assertWarns(DeprecationWarning): - evo_result = EvolutionResult(evolved_state, evolved_aux_ops_evaluated) - - expected_state = Zero - expected_aux_ops_evaluated = [(5j, 5j), (1.0, 8j), (5 + 1j, 6 + 1j)] - - self.assertEqual(evo_result.evolved_state, expected_state) - self.assertEqual(evo_result.aux_ops_evaluated, expected_aux_ops_evaluated) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/evolvers/trotterization/__init__.py b/test/python/algorithms/evolvers/trotterization/__init__.py deleted file mode 100644 index 96c0cf22bec9..000000000000 --- a/test/python/algorithms/evolvers/trotterization/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. diff --git a/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py b/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py deleted file mode 100644 index 6635d4db5d85..000000000000 --- a/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py +++ /dev/null @@ -1,259 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# 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. - -"""Test TrotterQRTE.""" - -import unittest -import warnings - -from test.python.opflow import QiskitOpflowTestCase -from ddt import ddt, data, unpack -import numpy as np -from numpy.testing import assert_raises - -from qiskit import BasicAer, QuantumCircuit -from qiskit.algorithms import EvolutionProblem -from qiskit.algorithms.evolvers.trotterization import ( - TrotterQRTE, -) -from qiskit.circuit.library import ZGate -from qiskit.quantum_info import Statevector -from qiskit.utils import algorithm_globals, QuantumInstance -from qiskit.circuit import Parameter -from qiskit.opflow import ( - X, - Z, - Zero, - VectorStateFn, - StateFn, - I, - Y, - SummedOp, - ExpectationFactory, -) -from qiskit.synthesis import SuzukiTrotter, QDrift - - -@ddt -class TestTrotterQRTE(QiskitOpflowTestCase): - """TrotterQRTE tests.""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - backend_statevector = BasicAer.get_backend("statevector_simulator") - backend_qasm = BasicAer.get_backend("qasm_simulator") - with self.assertWarns(DeprecationWarning): - self.quantum_instance = QuantumInstance( - backend=backend_statevector, - shots=1, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - self.quantum_instance_qasm = QuantumInstance( - backend=backend_qasm, - shots=8000, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - self.backends_dict = { - "qi_sv": self.quantum_instance, - "qi_qasm": self.quantum_instance_qasm, - "b_sv": backend_statevector, - "None": None, - } - - self.backends_names = ["qi_qasm", "b_sv", "None", "qi_sv"] - self.backends_names_not_none = ["qi_sv", "b_sv", "qi_qasm"] - - @data( - ( - None, - VectorStateFn( - Statevector([0.29192658 - 0.45464871j, 0.70807342 - 0.45464871j], dims=(2,)) - ), - ), - ( - SuzukiTrotter(), - VectorStateFn(Statevector([0.29192658 - 0.84147098j, 0.0 - 0.45464871j], dims=(2,))), - ), - ) - @unpack - def test_trotter_qrte_trotter_single_qubit(self, product_formula, expected_state): - """Test for default TrotterQRTE on a single qubit.""" - operator = SummedOp([X, Z]) - initial_state = StateFn([1, 0]) - time = 1 - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem(operator, time, initial_state) - trotter_qrte = TrotterQRTE(product_formula=product_formula) - evolution_result_state_circuit = trotter_qrte.evolve(evolution_problem).evolved_state - - np.testing.assert_equal(evolution_result_state_circuit.eval(), expected_state) - - def test_trotter_qrte_trotter_single_qubit_aux_ops(self): - """Test for default TrotterQRTE on a single qubit with auxiliary operators.""" - operator = SummedOp([X, Z]) - # LieTrotter with 1 rep - aux_ops = [X, Y] - - initial_state = Zero - time = 3 - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem(operator, time, initial_state, aux_ops) - - expected_evolved_state = VectorStateFn( - Statevector([0.98008514 + 0.13970775j, 0.01991486 + 0.13970775j], dims=(2,)) - ) - expected_aux_ops_evaluated = [(0.078073, 0.0), (0.268286, 0.0)] - expected_aux_ops_evaluated_qasm = [ - (0.05799999999999995, 0.011161518713866855), - (0.2495, 0.010826759383582883), - ] - - for backend_name in self.backends_names_not_none: - with self.subTest(msg=f"Test {backend_name} backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - backend = self.backends_dict[backend_name] - with self.assertWarns(DeprecationWarning): - expectation = ExpectationFactory.build( - operator=operator, - backend=backend, - ) - with self.assertWarns(DeprecationWarning): - trotter_qrte = TrotterQRTE(quantum_instance=backend, expectation=expectation) - evolution_result = trotter_qrte.evolve(evolution_problem) - - np.testing.assert_equal( - evolution_result.evolved_state.eval(), expected_evolved_state - ) - if backend_name == "qi_qasm": - expected_aux_ops_evaluated = expected_aux_ops_evaluated_qasm - np.testing.assert_array_almost_equal( - evolution_result.aux_ops_evaluated, expected_aux_ops_evaluated - ) - - @data( - ( - SummedOp([(X ^ Y), (Y ^ X)]), - VectorStateFn( - Statevector( - [-0.41614684 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.90929743 + 0.0j], dims=(2, 2) - ) - ), - ), - ( - (Z ^ Z) + (Z ^ I) + (I ^ Z), - VectorStateFn( - Statevector( - [-0.9899925 - 0.14112001j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], dims=(2, 2) - ) - ), - ), - ( - Y ^ Y, - VectorStateFn( - Statevector( - [0.54030231 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.84147098j], dims=(2, 2) - ) - ), - ), - ) - @unpack - def test_trotter_qrte_trotter_two_qubits(self, operator, expected_state): - """Test for TrotterQRTE on two qubits with various types of a Hamiltonian.""" - # LieTrotter with 1 rep - initial_state = StateFn([1, 0, 0, 0]) - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem(operator, 1, initial_state) - trotter_qrte = TrotterQRTE() - evolution_result = trotter_qrte.evolve(evolution_problem) - np.testing.assert_equal(evolution_result.evolved_state.eval(), expected_state) - - def test_trotter_qrte_trotter_two_qubits_with_params(self): - """Test for TrotterQRTE on two qubits with a parametrized Hamiltonian.""" - # LieTrotter with 1 rep - initial_state = StateFn([1, 0, 0, 0]) - w_param = Parameter("w") - u_param = Parameter("u") - params_dict = {w_param: 2.0, u_param: 3.0} - operator = w_param * (Z ^ Z) / 2.0 + (Z ^ I) + u_param * (I ^ Z) / 3.0 - time = 1 - expected_state = VectorStateFn( - Statevector([-0.9899925 - 0.14112001j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], dims=(2, 2)) - ) - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem( - operator, time, initial_state, param_value_dict=params_dict - ) - trotter_qrte = TrotterQRTE() - evolution_result = trotter_qrte.evolve(evolution_problem) - np.testing.assert_equal(evolution_result.evolved_state.eval(), expected_state) - - @data( - ( - Zero, - VectorStateFn( - Statevector([0.23071786 - 0.69436148j, 0.4646314 - 0.49874749j], dims=(2,)) - ), - ), - ( - QuantumCircuit(1).compose(ZGate(), [0]), - VectorStateFn( - Statevector([0.23071786 - 0.69436148j, 0.4646314 - 0.49874749j], dims=(2,)) - ), - ), - ) - @unpack - def test_trotter_qrte_qdrift(self, initial_state, expected_state): - """Test for TrotterQRTE with QDrift.""" - operator = SummedOp([X, Z]) - time = 1 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem(operator, time, initial_state) - trotter_qrte = TrotterQRTE(product_formula=QDrift(seed=0)) - evolution_result = trotter_qrte.evolve(evolution_problem) - np.testing.assert_equal(evolution_result.evolved_state.eval(), expected_state) - - @data((Parameter("t"), {}), (None, {Parameter("x"): 2}), (None, None)) - @unpack - def test_trotter_qrte_trotter_errors(self, t_param, param_value_dict): - """Test TrotterQRTE with raising errors.""" - operator = X * Parameter("t") + Z - initial_state = Zero - time = 1 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - with self.assertWarns(DeprecationWarning): - trotter_qrte = TrotterQRTE() - evolution_problem = EvolutionProblem( - operator, - time, - initial_state, - t_param=t_param, - param_value_dict=param_value_dict, - ) - with assert_raises(ValueError): - _ = trotter_qrte.evolve(evolution_problem) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/gradients/__init__.py b/test/python/algorithms/gradients/__init__.py deleted file mode 100644 index 756b2a26196b..000000000000 --- a/test/python/algorithms/gradients/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Tests for the primitive-based gradients""" diff --git a/test/python/algorithms/gradients/logging_primitives.py b/test/python/algorithms/gradients/logging_primitives.py deleted file mode 100644 index ef2cad9e2cc0..000000000000 --- a/test/python/algorithms/gradients/logging_primitives.py +++ /dev/null @@ -1,42 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Test primitives that check what kind of operations are in the circuits they execute.""" - -from qiskit.primitives import Estimator, Sampler - - -class LoggingEstimator(Estimator): - """An estimator checking what operations were in the circuits it executed.""" - - def __init__(self, options=None, operations_callback=None): - super().__init__(options=options) - self.operations_callback = operations_callback - - def _run(self, circuits, observables, parameter_values, **run_options): - if self.operations_callback is not None: - ops = [circuit.count_ops() for circuit in circuits] - self.operations_callback(ops) - return super()._run(circuits, observables, parameter_values, **run_options) - - -class LoggingSampler(Sampler): - """A sampler checking what operations were in the circuits it executed.""" - - def __init__(self, operations_callback): - super().__init__() - self.operations_callback = operations_callback - - def _run(self, circuits, parameter_values, **run_options): - ops = [circuit.count_ops() for circuit in circuits] - self.operations_callback(ops) - return super()._run(circuits, parameter_values, **run_options) diff --git a/test/python/algorithms/gradients/test_estimator_gradient.py b/test/python/algorithms/gradients/test_estimator_gradient.py deleted file mode 100644 index 202727c5835a..000000000000 --- a/test/python/algorithms/gradients/test_estimator_gradient.py +++ /dev/null @@ -1,519 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2021. -# -# 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. -# ============================================================================= - -"""Test Estimator Gradients""" - -import unittest - -import numpy as np -from ddt import ddt, data, unpack - -from qiskit import QuantumCircuit -from qiskit.algorithms.gradients import ( - FiniteDiffEstimatorGradient, - LinCombEstimatorGradient, - ParamShiftEstimatorGradient, - SPSAEstimatorGradient, - ReverseEstimatorGradient, - DerivativeType, -) -from qiskit.circuit import Parameter -from qiskit.circuit.library import EfficientSU2, RealAmplitudes -from qiskit.circuit.library.standard_gates import RXXGate, RYYGate, RZXGate, RZZGate -from qiskit.primitives import Estimator -from qiskit.quantum_info import Operator, SparsePauliOp, Pauli -from qiskit.quantum_info.random import random_pauli_list -from qiskit.test import QiskitTestCase - -from .logging_primitives import LoggingEstimator - -gradient_factories = [ - lambda estimator: FiniteDiffEstimatorGradient(estimator, epsilon=1e-6, method="central"), - lambda estimator: FiniteDiffEstimatorGradient(estimator, epsilon=1e-6, method="forward"), - lambda estimator: FiniteDiffEstimatorGradient(estimator, epsilon=1e-6, method="backward"), - ParamShiftEstimatorGradient, - LinCombEstimatorGradient, - lambda estimator: ReverseEstimatorGradient(), # does not take an estimator! -] - - -@ddt -class TestEstimatorGradient(QiskitTestCase): - """Test Estimator Gradient""" - - @data(*gradient_factories) - def test_gradient_operators(self, grad): - """Test the estimator gradient for different operators""" - estimator = Estimator() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.h(0) - qc.p(a, 0) - qc.h(0) - gradient = grad(estimator) - op = SparsePauliOp.from_list([("Z", 1)]) - correct_result = -1 / np.sqrt(2) - param = [np.pi / 4] - value = gradient.run([qc], [op], [param]).result().gradients[0] - self.assertAlmostEqual(value[0], correct_result, 3) - op = SparsePauliOp.from_list([("Z", 1)]) - value = gradient.run([qc], [op], [param]).result().gradients[0] - self.assertAlmostEqual(value[0], correct_result, 3) - op = Operator.from_label("Z") - value = gradient.run([qc], [op], [param]).result().gradients[0] - self.assertAlmostEqual(value[0], correct_result, 3) - - @data(*gradient_factories) - def test_single_circuit_observable(self, grad): - """Test the estimator gradient for a single circuit and observable""" - estimator = Estimator() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.h(0) - qc.p(a, 0) - qc.h(0) - gradient = grad(estimator) - op = SparsePauliOp.from_list([("Z", 1)]) - correct_result = -1 / np.sqrt(2) - param = [np.pi / 4] - value = gradient.run(qc, op, [param]).result().gradients[0] - self.assertAlmostEqual(value[0], correct_result, 3) - - @data(*gradient_factories) - def test_gradient_p(self, grad): - """Test the estimator gradient for p""" - estimator = Estimator() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.h(0) - qc.p(a, 0) - qc.h(0) - gradient = grad(estimator) - op = SparsePauliOp.from_list([("Z", 1)]) - param_list = [[np.pi / 4], [0], [np.pi / 2]] - correct_results = [[-1 / np.sqrt(2)], [0], [-1]] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [op], [param]).result().gradients[0] - for j, value in enumerate(gradients): - self.assertAlmostEqual(value, correct_results[i][j], 3) - - @data(*gradient_factories) - def test_gradient_u(self, grad): - """Test the estimator gradient for u""" - estimator = Estimator() - a = Parameter("a") - b = Parameter("b") - c = Parameter("c") - qc = QuantumCircuit(1) - qc.h(0) - qc.u(a, b, c, 0) - qc.h(0) - gradient = grad(estimator) - op = SparsePauliOp.from_list([("Z", 1)]) - - param_list = [[np.pi / 4, 0, 0], [np.pi / 4, np.pi / 4, np.pi / 4]] - correct_results = [[-0.70710678, 0.0, 0.0], [-0.35355339, -0.85355339, -0.85355339]] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [op], [param]).result().gradients[0] - for j, value in enumerate(gradients): - self.assertAlmostEqual(value, correct_results[i][j], 3) - - @data(*gradient_factories) - def test_gradient_efficient_su2(self, grad): - """Test the estimator gradient for EfficientSU2""" - estimator = Estimator() - qc = EfficientSU2(2, reps=1) - op = SparsePauliOp.from_list([("ZI", 1)]) - gradient = grad(estimator) - param_list = [ - [np.pi / 4 for param in qc.parameters], - [np.pi / 2 for param in qc.parameters], - ] - correct_results = [ - [ - -0.35355339, - -0.70710678, - 0, - 0.35355339, - 0, - -0.70710678, - 0, - 0, - ], - [0, 0, 0, 1, 0, 0, 0, 0], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [op], [param]).result().gradients[0] - np.testing.assert_allclose(gradients, correct_results[i], atol=1e-3) - - @data(*gradient_factories) - def test_gradient_2qubit_gate(self, grad): - """Test the estimator gradient for 2 qubit gates""" - estimator = Estimator() - for gate in [RXXGate, RYYGate, RZZGate, RZXGate]: - param_list = [[np.pi / 4], [np.pi / 2]] - correct_results = [ - [-0.70710678], - [-1], - ] - op = SparsePauliOp.from_list([("ZI", 1)]) - for i, param in enumerate(param_list): - a = Parameter("a") - qc = QuantumCircuit(2) - gradient = grad(estimator) - - if gate is RZZGate: - qc.h([0, 1]) - qc.append(gate(a), [qc.qubits[0], qc.qubits[1]], []) - qc.h([0, 1]) - else: - qc.append(gate(a), [qc.qubits[0], qc.qubits[1]], []) - gradients = gradient.run([qc], [op], [param]).result().gradients[0] - np.testing.assert_allclose(gradients, correct_results[i], atol=1e-3) - - @data(*gradient_factories) - def test_gradient_parameter_coefficient(self, grad): - """Test the estimator gradient for parameter variables with coefficients""" - estimator = Estimator() - qc = RealAmplitudes(num_qubits=2, reps=1) - qc.rz(qc.parameters[0].exp() + 2 * qc.parameters[1], 0) - qc.rx(3.0 * qc.parameters[0] + qc.parameters[1].sin(), 1) - qc.u(qc.parameters[0], qc.parameters[1], qc.parameters[3], 1) - qc.p(2 * qc.parameters[0] + 1, 0) - qc.rxx(qc.parameters[0] + 2, 0, 1) - gradient = grad(estimator) - param_list = [[np.pi / 4 for _ in qc.parameters], [np.pi / 2 for _ in qc.parameters]] - correct_results = [ - [-0.7266653, -0.4905135, -0.0068606, -0.9228880], - [-3.5972095, 0.10237173, -0.3117748, 0], - ] - op = SparsePauliOp.from_list([("ZI", 1)]) - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [op], [param]).result().gradients[0] - np.testing.assert_allclose(gradients, correct_results[i], atol=1e-3) - - @data(*gradient_factories) - def test_gradient_parameters(self, grad): - """Test the estimator gradient for parameters""" - estimator = Estimator() - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rx(b, 0) - gradient = grad(estimator) - param_list = [[np.pi / 4, np.pi / 2]] - correct_results = [ - [-0.70710678], - ] - op = SparsePauliOp.from_list([("Z", 1)]) - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [op], [param], parameters=[[a]]).result().gradients[0] - np.testing.assert_allclose(gradients, correct_results[i], atol=1e-3) - - # parameter order - with self.subTest(msg="The order of gradients"): - c = Parameter("c") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rz(b, 0) - qc.rx(c, 0) - - param_list = [[np.pi / 4, np.pi / 2, np.pi / 3]] - correct_results = [ - [-0.35355339, 0.61237244, -0.61237244], - [-0.61237244, 0.61237244, -0.35355339], - [-0.35355339, -0.61237244], - [-0.61237244, -0.35355339], - ] - param = [[a, b, c], [c, b, a], [a, c], [c, a]] - op = SparsePauliOp.from_list([("Z", 1)]) - for i, p in enumerate(param): - gradient = grad(estimator) - gradients = ( - gradient.run([qc], [op], param_list, parameters=[p]).result().gradients[0] - ) - np.testing.assert_allclose(gradients, correct_results[i], atol=1e-3) - - @data(*gradient_factories) - def test_gradient_multi_arguments(self, grad): - """Test the estimator gradient for multiple arguments""" - estimator = Estimator() - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc2 = QuantumCircuit(1) - qc2.rx(b, 0) - gradient = grad(estimator) - param_list = [[np.pi / 4], [np.pi / 2]] - correct_results = [ - [-0.70710678], - [-1], - ] - op = SparsePauliOp.from_list([("Z", 1)]) - gradients = gradient.run([qc, qc2], [op] * 2, param_list).result().gradients - np.testing.assert_allclose(gradients, correct_results, atol=1e-3) - - c = Parameter("c") - qc3 = QuantumCircuit(1) - qc3.rx(c, 0) - qc3.ry(a, 0) - param_list2 = [[np.pi / 4], [np.pi / 4, np.pi / 4], [np.pi / 4, np.pi / 4]] - correct_results2 = [ - [-0.70710678], - [-0.5], - [-0.5, -0.5], - ] - gradients2 = ( - gradient.run([qc, qc3, qc3], [op] * 3, param_list2, parameters=[[a], [c], None]) - .result() - .gradients - ) - np.testing.assert_allclose(gradients2[0], correct_results2[0], atol=1e-3) - np.testing.assert_allclose(gradients2[1], correct_results2[1], atol=1e-3) - np.testing.assert_allclose(gradients2[2], correct_results2[2], atol=1e-3) - - @data(*gradient_factories) - def test_gradient_validation(self, grad): - """Test estimator gradient's validation""" - estimator = Estimator() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - gradient = grad(estimator) - param_list = [[np.pi / 4], [np.pi / 2]] - op = SparsePauliOp.from_list([("Z", 1)]) - with self.assertRaises(ValueError): - gradient.run([qc], [op], param_list) - with self.assertRaises(ValueError): - gradient.run([qc, qc], [op, op], param_list, parameters=[[a]]) - with self.assertRaises(ValueError): - gradient.run([qc, qc], [op], param_list, parameters=[[a]]) - with self.assertRaises(ValueError): - gradient.run([qc], [op], [[np.pi / 4, np.pi / 4]]) - - def test_spsa_gradient(self): - """Test the SPSA estimator gradient""" - estimator = Estimator() - with self.assertRaises(ValueError): - _ = SPSAEstimatorGradient(estimator, epsilon=-0.1) - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(2) - qc.rx(b, 0) - qc.rx(a, 1) - param_list = [[1, 1]] - correct_results = [[-0.84147098, 0.84147098]] - op = SparsePauliOp.from_list([("ZI", 1)]) - gradient = SPSAEstimatorGradient(estimator, epsilon=1e-6, seed=123) - gradients = gradient.run([qc], [op], param_list).result().gradients - np.testing.assert_allclose(gradients, correct_results, atol=1e-3) - - # multi parameters - with self.subTest(msg="Multiple parameters"): - gradient = SPSAEstimatorGradient(estimator, epsilon=1e-6, seed=123) - param_list2 = [[1, 1], [1, 1], [3, 3]] - gradients2 = ( - gradient.run([qc] * 3, [op] * 3, param_list2, parameters=[None, [b], None]) - .result() - .gradients - ) - correct_results2 = [[-0.84147098, 0.84147098], [0.84147098], [-0.14112001, 0.14112001]] - for grad, correct in zip(gradients2, correct_results2): - np.testing.assert_allclose(grad, correct, atol=1e-3) - - # batch size - with self.subTest(msg="Batch size"): - correct_results = [[-0.84147098, 0.1682942]] - gradient = SPSAEstimatorGradient(estimator, epsilon=1e-6, batch_size=5, seed=123) - gradients = gradient.run([qc], [op], param_list).result().gradients - np.testing.assert_allclose(gradients, correct_results, atol=1e-3) - - # parameter order - with self.subTest(msg="The order of gradients"): - gradient = SPSAEstimatorGradient(estimator, epsilon=1e-6, seed=123) - c = Parameter("c") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rz(b, 0) - qc.rx(c, 0) - op = SparsePauliOp.from_list([("Z", 1)]) - param_list3 = [[np.pi / 4, np.pi / 2, np.pi / 3]] - param = [[a, b, c], [c, b, a], [a, c], [c, a]] - expected = [ - [-0.3535525, 0.3535525, 0.3535525], - [0.3535525, 0.3535525, -0.3535525], - [-0.3535525, 0.3535525], - [0.3535525, -0.3535525], - ] - for i, p in enumerate(param): - gradient = SPSAEstimatorGradient(estimator, epsilon=1e-6, seed=123) - gradients = ( - gradient.run([qc], [op], param_list3, parameters=[p]).result().gradients[0] - ) - np.testing.assert_allclose(gradients, expected[i], atol=1e-3) - - @data(ParamShiftEstimatorGradient, LinCombEstimatorGradient) - def test_gradient_random_parameters(self, grad): - """Test param shift and lin comb w/ random parameters""" - rng = np.random.default_rng(123) - qc = RealAmplitudes(num_qubits=3, reps=1) - params = qc.parameters - qc.rx(3.0 * params[0] + params[1].sin(), 0) - qc.ry(params[0].exp() + 2 * params[1], 1) - qc.rz(params[0] * params[1] - params[2], 2) - qc.p(2 * params[0] + 1, 0) - qc.u(params[0].sin(), params[1] - 2, params[2] * params[3], 1) - qc.sx(2) - qc.rxx(params[0].sin(), 1, 2) - qc.ryy(params[1].cos(), 2, 0) - qc.rzz(params[2] * 2, 0, 1) - qc.crx(params[0].exp(), 1, 2) - qc.cry(params[1].arctan(), 2, 0) - qc.crz(params[2] * -2, 0, 1) - qc.dcx(0, 1) - qc.csdg(0, 1) - qc.ccx(0, 1, 2) - qc.iswap(0, 2) - qc.swap(1, 2) - qc.global_phase = params[0] * params[1] + params[2].cos().exp() - - size = 10 - op = SparsePauliOp(random_pauli_list(num_qubits=qc.num_qubits, size=size, seed=rng)) - op.coeffs = rng.normal(0, 10, size) - - estimator = Estimator() - findiff = FiniteDiffEstimatorGradient(estimator, 1e-6) - gradient = grad(estimator) - - num_tries = 10 - param_values = rng.normal(0, 2, (num_tries, qc.num_parameters)).tolist() - np.testing.assert_allclose( - findiff.run([qc] * num_tries, [op] * num_tries, param_values).result().gradients, - gradient.run([qc] * num_tries, [op] * num_tries, param_values).result().gradients, - rtol=1e-4, - ) - - @data((DerivativeType.IMAG, -1.0), (DerivativeType.COMPLEX, -1.0j)) - @unpack - def test_complex_gradient(self, derivative_type, expected_gradient_value): - """Tests if the ``LinCombEstimatorGradient`` has the correct value.""" - estimator = Estimator() - lcu = LinCombEstimatorGradient(estimator, derivative_type=derivative_type) - reverse = ReverseEstimatorGradient(derivative_type=derivative_type) - - for gradient in [lcu, reverse]: - with self.subTest(gradient=gradient): - c = QuantumCircuit(1) - c.rz(Parameter("p"), 0) - result = gradient.run([c], [Pauli("I")], [[0.0]]).result() - self.assertAlmostEqual(result.gradients[0][0], expected_gradient_value) - - @data( - FiniteDiffEstimatorGradient, - ParamShiftEstimatorGradient, - LinCombEstimatorGradient, - SPSAEstimatorGradient, - ) - def test_options(self, grad): - """Test estimator gradient's run options""" - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - op = SparsePauliOp.from_list([("Z", 1)]) - estimator = Estimator(options={"shots": 100}) - with self.subTest("estimator"): - if grad is FiniteDiffEstimatorGradient or grad is SPSAEstimatorGradient: - gradient = grad(estimator, epsilon=1e-6) - else: - gradient = grad(estimator) - options = gradient.options - result = gradient.run([qc], [op], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("gradient init"): - if grad is FiniteDiffEstimatorGradient or grad is SPSAEstimatorGradient: - gradient = grad(estimator, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(estimator, options={"shots": 200}) - options = gradient.options - result = gradient.run([qc], [op], [[1]]).result() - self.assertEqual(result.options.get("shots"), 200) - self.assertEqual(options.get("shots"), 200) - - with self.subTest("gradient update"): - if grad is FiniteDiffEstimatorGradient or grad is SPSAEstimatorGradient: - gradient = grad(estimator, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(estimator, options={"shots": 200}) - gradient.update_default_options(shots=100) - options = gradient.options - result = gradient.run([qc], [op], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("gradient run"): - if grad is FiniteDiffEstimatorGradient or grad is SPSAEstimatorGradient: - gradient = grad(estimator, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(estimator, options={"shots": 200}) - options = gradient.options - result = gradient.run([qc], [op], [[1]], shots=300).result() - self.assertEqual(result.options.get("shots"), 300) - # Only default + estimator options. Not run. - self.assertEqual(options.get("shots"), 200) - - @data( - FiniteDiffEstimatorGradient, - ParamShiftEstimatorGradient, - LinCombEstimatorGradient, - SPSAEstimatorGradient, - ) - def test_operations_preserved(self, gradient_cls): - """Test non-parameterized instructions are preserved and not unrolled.""" - x = Parameter("x") - circuit = QuantumCircuit(2) - circuit.initialize([0.5, 0.5, 0.5, 0.5]) # this should remain as initialize - circuit.crx(x, 0, 1) # this should get unrolled - - values = [np.pi / 2] - expect = -1 / (2 * np.sqrt(2)) - - observable = SparsePauliOp(["XX"]) - - ops = [] - - def operations_callback(op): - ops.append(op) - - estimator = LoggingEstimator(operations_callback=operations_callback) - - if gradient_cls in [SPSAEstimatorGradient, FiniteDiffEstimatorGradient]: - gradient = gradient_cls(estimator, epsilon=0.01) - else: - gradient = gradient_cls(estimator) - - job = gradient.run([circuit], [observable], [values]) - result = job.result() - - with self.subTest(msg="assert initialize is preserved"): - self.assertTrue(all("initialize" in ops_i[0].keys() for ops_i in ops)) - - with self.subTest(msg="assert result is correct"): - self.assertAlmostEqual(result.gradients[0].item(), expect, places=5) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/gradients/test_qfi.py b/test/python/algorithms/gradients/test_qfi.py deleted file mode 100644 index b0d05ac7030f..000000000000 --- a/test/python/algorithms/gradients/test_qfi.py +++ /dev/null @@ -1,151 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. -# ============================================================================= - -"""Test QFI.""" - -import unittest -from ddt import ddt, data - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.algorithms.gradients import LinCombQGT, ReverseQGT, QFI, DerivativeType -from qiskit.circuit import Parameter -from qiskit.circuit.parametervector import ParameterVector -from qiskit.primitives import Estimator -from qiskit.test import QiskitTestCase - - -@ddt -class TestQFI(QiskitTestCase): - """Test QFI""" - - def setUp(self): - super().setUp() - self.estimator = Estimator() - self.lcu_qgt = LinCombQGT(self.estimator, derivative_type=DerivativeType.REAL) - self.reverse_qgt = ReverseQGT(derivative_type=DerivativeType.REAL) - - def test_qfi(self): - """Test if the quantum fisher information calculation is correct for a simple test case. - QFI = [[1, 0], [0, 1]] - [[0, 0], [0, cos^2(a)]] - """ - # create the circuit - a, b = Parameter("a"), Parameter("b") - qc = QuantumCircuit(1) - qc.h(0) - qc.rz(a, 0) - qc.rx(b, 0) - - param_list = [[np.pi / 4, 0.1], [np.pi, 0.1], [np.pi / 2, 0.1]] - correct_values = [[[1, 0], [0, 0.5]], [[1, 0], [0, 0]], [[1, 0], [0, 1]]] - - qfi = QFI(self.lcu_qgt) - for i, param in enumerate(param_list): - qfis = qfi.run([qc], [param]).result().qfis - np.testing.assert_allclose(qfis[0], correct_values[i], atol=1e-3) - - def test_qfi_phase_fix(self): - """Test the phase-fix argument in the QFI calculation""" - # create the circuit - a, b = Parameter("a"), Parameter("b") - qc = QuantumCircuit(1) - qc.h(0) - qc.rz(a, 0) - qc.rx(b, 0) - - param = [np.pi / 4, 0.1] - # test for different values - correct_values = [[1, 0], [0, 1]] - qgt = LinCombQGT(self.estimator, phase_fix=False) - qfi = QFI(qgt) - qfis = qfi.run([qc], [param]).result().qfis - np.testing.assert_allclose(qfis[0], correct_values, atol=1e-3) - - @data("lcu", "reverse") - def test_qfi_maxcut(self, qgt_kind): - """Test the QFI for a simple MaxCut problem. - - This is interesting because it contains the same parameters in different gates. - """ - # create maxcut circuit for the hamiltonian - # H = (I ^ I ^ Z ^ Z) + (I ^ Z ^ I ^ Z) + (Z ^ I ^ I ^ Z) + (I ^ Z ^ Z ^ I) - - x = ParameterVector("x", 2) - ansatz = QuantumCircuit(4) - - # initial hadamard layer - ansatz.h(ansatz.qubits) - - # e^{iZZ} layers - def expiz(qubit0, qubit1): - ansatz.cx(qubit0, qubit1) - ansatz.rz(2 * x[0], qubit1) - ansatz.cx(qubit0, qubit1) - - expiz(2, 1) - expiz(3, 0) - expiz(2, 0) - expiz(1, 0) - - # mixer layer with RX gates - for i in range(ansatz.num_qubits): - ansatz.rx(2 * x[1], i) - - reference = np.array([[16.0, -5.551], [-5.551, 18.497]]) - param = [0.4, 0.69] - - qgt = self.lcu_qgt if qgt_kind == "lcu" else self.reverse_qgt - qfi = QFI(qgt) - qfi_result = qfi.run([ansatz], [param]).result().qfis - np.testing.assert_array_almost_equal(qfi_result[0], reference, decimal=3) - - def test_options(self): - """Test QFI's options""" - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qgt = LinCombQGT(estimator=self.estimator, options={"shots": 100}) - - with self.subTest("QGT"): - qfi = QFI(qgt=qgt) - options = qfi.options - result = qfi.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("QFI init"): - qfi = QFI(qgt=qgt, options={"shots": 200}) - result = qfi.run([qc], [[1]]).result() - options = qfi.options - self.assertEqual(result.options.get("shots"), 200) - self.assertEqual(options.get("shots"), 200) - - with self.subTest("QFI update"): - qfi = QFI(qgt, options={"shots": 200}) - qfi.update_default_options(shots=100) - options = qfi.options - result = qfi.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("QFI run"): - qfi = QFI(qgt=qgt, options={"shots": 200}) - result = qfi.run([qc], [[0]], shots=300).result() - options = qfi.options - self.assertEqual(result.options.get("shots"), 300) - self.assertEqual(options.get("shots"), 200) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/gradients/test_qgt.py b/test/python/algorithms/gradients/test_qgt.py deleted file mode 100644 index 74f22d462d7c..000000000000 --- a/test/python/algorithms/gradients/test_qgt.py +++ /dev/null @@ -1,309 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. -# ============================================================================= - -"""Test QGT.""" - -import unittest -from ddt import ddt, data - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.algorithms.gradients import DerivativeType, LinCombQGT, ReverseQGT -from qiskit.circuit import Parameter -from qiskit.circuit.library import RealAmplitudes -from qiskit.primitives import Estimator -from qiskit.test import QiskitTestCase - -from .logging_primitives import LoggingEstimator - - -@ddt -class TestQGT(QiskitTestCase): - """Test QGT""" - - def setUp(self): - super().setUp() - self.estimator = Estimator() - - @data(LinCombQGT, ReverseQGT) - def test_qgt_derivative_type(self, qgt_type): - """Test QGT derivative_type""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args, derivative_type=DerivativeType.REAL) - - a, b = Parameter("a"), Parameter("b") - qc = QuantumCircuit(1) - qc.h(0) - qc.rz(a, 0) - qc.rx(b, 0) - - param_list = [[np.pi / 4, 0], [np.pi / 2, np.pi / 4]] - correct_values = [ - np.array([[1, 0.707106781j], [-0.707106781j, 0.5]]) / 4, - np.array([[1, 1j], [-1j, 1]]) / 4, - ] - - # test real derivative - with self.subTest("Test with DerivativeType.REAL"): - qgt.derivative_type = DerivativeType.REAL - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i].real, atol=1e-3) - - # test imaginary derivative - with self.subTest("Test with DerivativeType.IMAG"): - qgt.derivative_type = DerivativeType.IMAG - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i].imag, atol=1e-3) - - # test real + imaginary derivative - with self.subTest("Test with DerivativeType.COMPLEX"): - qgt.derivative_type = DerivativeType.COMPLEX - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i], atol=1e-3) - - @data(LinCombQGT, ReverseQGT) - def test_qgt_phase_fix(self, qgt_type): - """Test the phase-fix argument in a QGT calculation""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args, phase_fix=False) - - # create the circuit - a, b = Parameter("a"), Parameter("b") - qc = QuantumCircuit(1) - qc.h(0) - qc.rz(a, 0) - qc.rx(b, 0) - - param_list = [[np.pi / 4, 0], [np.pi / 2, np.pi / 4]] - correct_values = [ - np.array([[1, 0.707106781j], [-0.707106781j, 1]]) / 4, - np.array([[1, 1j], [-1j, 1]]) / 4, - ] - - # test real derivative - with self.subTest("Test phase fix with DerivativeType.REAL"): - qgt.derivative_type = DerivativeType.REAL - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i].real, atol=1e-3) - - # test imaginary derivative - with self.subTest("Test phase fix with DerivativeType.IMAG"): - qgt.derivative_type = DerivativeType.IMAG - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i].imag, atol=1e-3) - - # test real + imaginary derivative - with self.subTest("Test phase fix with DerivativeType.COMPLEX"): - qgt.derivative_type = DerivativeType.COMPLEX - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i], atol=1e-3) - - @data(LinCombQGT, ReverseQGT) - def test_qgt_coefficients(self, qgt_type): - """Test the derivative option of QGT""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args, derivative_type=DerivativeType.REAL) - - qc = RealAmplitudes(num_qubits=2, reps=1) - qc.rz(qc.parameters[0].exp() + 2 * qc.parameters[1], 0) - qc.rx(3.0 * qc.parameters[2] + qc.parameters[3].sin(), 1) - - # test imaginary derivative - param_list = [ - [np.pi / 4 for param in qc.parameters], - [np.pi / 2 for param in qc.parameters], - ] - correct_values = ( - np.array( - [ - [ - [5.707309, 4.2924833, 1.5295868, 0.1938604], - [4.2924833, 4.9142136, 0.75, 0.8838835], - [1.5295868, 0.75, 3.4430195, 0.0758252], - [0.1938604, 0.8838835, 0.0758252, 1.1357233], - ], - [ - [1.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - [1.0, 0.0, 10.0, -0.0], - [0.0, 0.0, -0.0, 1.0], - ], - ] - ) - / 4 - ) - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i], atol=1e-3) - - @data(LinCombQGT, ReverseQGT) - def test_qgt_parameters(self, qgt_type): - """Test the QGT with specified parameters""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args, derivative_type=DerivativeType.REAL) - - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.ry(b, 0) - param_values = [np.pi / 4, np.pi / 4] - qgt_result = qgt.run([qc], [param_values], [[a]]).result().qgts - np.testing.assert_allclose(qgt_result[0], [[1 / 4]], atol=1e-3) - - with self.subTest("Test with different parameter orders"): - c = Parameter("c") - qc2 = QuantumCircuit(1) - qc2.rx(a, 0) - qc2.rz(b, 0) - qc2.rx(c, 0) - param_values = [np.pi / 4, np.pi / 4, np.pi / 4] - params = [[a, b, c], [c, b, a], [a, c], [b, a]] - expected = [ - np.array( - [ - [0.25, 0.0, 0.1767767], - [0.0, 0.125, -0.08838835], - [0.1767767, -0.08838835, 0.1875], - ] - ), - np.array( - [ - [0.1875, -0.08838835, 0.1767767], - [-0.08838835, 0.125, 0.0], - [0.1767767, 0.0, 0.25], - ] - ), - np.array([[0.25, 0.1767767], [0.1767767, 0.1875]]), - np.array([[0.125, 0.0], [0.0, 0.25]]), - ] - for i, param in enumerate(params): - qgt_result = qgt.run([qc2], [param_values], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], expected[i], atol=1e-3) - - @data(LinCombQGT, ReverseQGT) - def test_qgt_multi_arguments(self, qgt_type): - """Test the QGT for multiple arguments""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args, derivative_type=DerivativeType.REAL) - - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.ry(b, 0) - qc2 = QuantumCircuit(1) - qc2.rx(a, 0) - qc2.ry(b, 0) - - param_list = [[np.pi / 4], [np.pi / 2]] - correct_values = [[[1 / 4]], [[1 / 4, 0], [0, 0]]] - param_list = [[np.pi / 4, np.pi / 4], [np.pi / 2, np.pi / 2]] - qgt_results = qgt.run([qc, qc2], param_list, [[a], None]).result().qgts - for i, _ in enumerate(param_list): - np.testing.assert_allclose(qgt_results[i], correct_values[i], atol=1e-3) - - @data(LinCombQGT, ReverseQGT) - def test_qgt_validation(self, qgt_type): - """Test estimator QGT's validation""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args) - - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - parameter_values = [[np.pi / 4]] - with self.subTest("assert number of circuits does not match"): - with self.assertRaises(ValueError): - qgt.run([qc, qc], parameter_values) - with self.subTest("assert number of parameter values does not match"): - with self.assertRaises(ValueError): - qgt.run([qc], [[np.pi / 4], [np.pi / 2]]) - with self.subTest("assert number of parameters does not match"): - with self.assertRaises(ValueError): - qgt.run([qc], parameter_values, parameters=[[a], [a]]) - - def test_options(self): - """Test QGT's options""" - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - estimator = Estimator(options={"shots": 100}) - - with self.subTest("estimator"): - qgt = LinCombQGT(estimator) - options = qgt.options - result = qgt.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("QGT init"): - qgt = LinCombQGT(estimator, options={"shots": 200}) - result = qgt.run([qc], [[1]]).result() - options = qgt.options - self.assertEqual(result.options.get("shots"), 200) - self.assertEqual(options.get("shots"), 200) - - with self.subTest("QGT update"): - qgt = LinCombQGT(estimator, options={"shots": 200}) - qgt.update_default_options(shots=100) - options = qgt.options - result = qgt.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("QGT run"): - qgt = LinCombQGT(estimator, options={"shots": 200}) - result = qgt.run([qc], [[0]], shots=300).result() - options = qgt.options - self.assertEqual(result.options.get("shots"), 300) - self.assertEqual(options.get("shots"), 200) - - def test_operations_preserved(self): - """Test non-parameterized instructions are preserved and not unrolled.""" - x, y = Parameter("x"), Parameter("y") - circuit = QuantumCircuit(2) - circuit.initialize([0.5, 0.5, 0.5, 0.5]) # this should remain as initialize - circuit.crx(x, 0, 1) # this should get unrolled - circuit.ry(y, 0) - - values = [np.pi / 2, np.pi] - expect = np.diag([0.25, 0.5]) / 4 - - ops = [] - - def operations_callback(op): - ops.append(op) - - estimator = LoggingEstimator(operations_callback=operations_callback) - qgt = LinCombQGT(estimator, derivative_type=DerivativeType.REAL) - - job = qgt.run([circuit], [values]) - result = job.result() - - with self.subTest(msg="assert initialize is preserved"): - self.assertTrue(all("initialize" in ops_i[0].keys() for ops_i in ops)) - - with self.subTest(msg="assert result is correct"): - np.testing.assert_allclose(result.qgts[0], expect, atol=1e-5) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/gradients/test_sampler_gradient.py b/test/python/algorithms/gradients/test_sampler_gradient.py deleted file mode 100644 index 2faf44df6466..000000000000 --- a/test/python/algorithms/gradients/test_sampler_gradient.py +++ /dev/null @@ -1,690 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2021. -# -# 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. -# ============================================================================= - -"""Test Sampler Gradients""" - -import unittest -from typing import List - -import numpy as np -from ddt import ddt, data - -from qiskit import QuantumCircuit -from qiskit.algorithms.gradients import ( - FiniteDiffSamplerGradient, - LinCombSamplerGradient, - ParamShiftSamplerGradient, - SPSASamplerGradient, -) -from qiskit.circuit import Parameter -from qiskit.circuit.library import EfficientSU2, RealAmplitudes -from qiskit.circuit.library.standard_gates import RXXGate -from qiskit.primitives import Sampler -from qiskit.result import QuasiDistribution -from qiskit.test import QiskitTestCase - -from .logging_primitives import LoggingSampler - -gradient_factories = [ - lambda sampler: FiniteDiffSamplerGradient(sampler, epsilon=1e-6, method="central"), - lambda sampler: FiniteDiffSamplerGradient(sampler, epsilon=1e-6, method="forward"), - lambda sampler: FiniteDiffSamplerGradient(sampler, epsilon=1e-6, method="backward"), - ParamShiftSamplerGradient, - LinCombSamplerGradient, -] - - -@ddt -class TestSamplerGradient(QiskitTestCase): - """Test Sampler Gradient""" - - @data(*gradient_factories) - def test_single_circuit(self, grad): - """Test the sampler gradient for a single circuit""" - sampler = Sampler() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.h(0) - qc.p(a, 0) - qc.h(0) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4], [0], [np.pi / 2]] - expected = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}], - [{0: 0, 1: 0}], - [{0: -0.499999, 1: 0.499999}], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(expected[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_p(self, grad): - """Test the sampler gradient for p""" - sampler = Sampler() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.h(0) - qc.p(a, 0) - qc.h(0) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4], [0], [np.pi / 2]] - expected = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}], - [{0: 0, 1: 0}], - [{0: -0.499999, 1: 0.499999}], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(expected[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_u(self, grad): - """Test the sampler gradient for u""" - sampler = Sampler() - a = Parameter("a") - b = Parameter("b") - c = Parameter("c") - qc = QuantumCircuit(1) - qc.h(0) - qc.u(a, b, c, 0) - qc.h(0) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4, 0, 0], [np.pi / 4, np.pi / 4, np.pi / 4]] - expected = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}, {0: 0, 1: 0}, {0: 0, 1: 0}], - [{0: -0.176777, 1: 0.176777}, {0: -0.426777, 1: 0.426777}, {0: -0.426777, 1: 0.426777}], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(expected[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_efficient_su2(self, grad): - """Test the sampler gradient for EfficientSU2""" - sampler = Sampler() - qc = EfficientSU2(2, reps=1) - qc.measure_all() - gradient = grad(sampler) - param_list = [ - [np.pi / 4 for param in qc.parameters], - [np.pi / 2 for param in qc.parameters], - ] - expected = [ - [ - { - 0: -0.11963834764831836, - 1: -0.05713834764831845, - 2: -0.21875000000000003, - 3: 0.39552669529663675, - }, - { - 0: -0.32230339059327373, - 1: -0.031250000000000014, - 2: 0.2339150429449554, - 3: 0.11963834764831843, - }, - { - 0: 0.012944173824159189, - 1: -0.01294417382415923, - 2: 0.07544417382415919, - 3: -0.07544417382415919, - }, - { - 0: 0.2080266952966367, - 1: -0.03125000000000002, - 2: -0.11963834764831842, - 3: -0.057138347648318405, - }, - { - 0: -0.11963834764831838, - 1: 0.11963834764831838, - 2: -0.21875000000000003, - 3: 0.21875, - }, - { - 0: -0.2781092167691146, - 1: -0.0754441738241592, - 2: 0.27810921676911443, - 3: 0.07544417382415924, - }, - {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0}, - {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0}, - ], - [ - { - 0: -4.163336342344337e-17, - 1: 2.7755575615628914e-17, - 2: -4.163336342344337e-17, - 3: 0.0, - }, - {0: 0.0, 1: -1.3877787807814457e-17, 2: 4.163336342344337e-17, 3: 0.0}, - { - 0: -0.24999999999999994, - 1: 0.24999999999999994, - 2: 0.24999999999999994, - 3: -0.24999999999999994, - }, - { - 0: 0.24999999999999994, - 1: 0.24999999999999994, - 2: -0.24999999999999994, - 3: -0.24999999999999994, - }, - { - 0: -4.163336342344337e-17, - 1: 4.163336342344337e-17, - 2: -4.163336342344337e-17, - 3: 5.551115123125783e-17, - }, - { - 0: -0.24999999999999994, - 1: 0.24999999999999994, - 2: 0.24999999999999994, - 3: -0.24999999999999994, - }, - {0: 0.0, 1: 2.7755575615628914e-17, 2: 0.0, 3: 2.7755575615628914e-17}, - {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0}, - ], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=2) - array2 = _quasi2array(expected[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_2qubit_gate(self, grad): - """Test the sampler gradient for 2 qubit gates""" - sampler = Sampler() - for gate in [RXXGate]: - param_list = [[np.pi / 4], [np.pi / 2]] - correct_results = [ - [{0: -0.5 / np.sqrt(2), 1: 0, 2: 0, 3: 0.5 / np.sqrt(2)}], - [{0: -0.5, 1: 0, 2: 0, 3: 0.5}], - ] - for i, param in enumerate(param_list): - a = Parameter("a") - qc = QuantumCircuit(2) - qc.append(gate(a), [qc.qubits[0], qc.qubits[1]], []) - qc.measure_all() - gradient = grad(sampler) - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=2) - array2 = _quasi2array(correct_results[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_parameter_coefficient(self, grad): - """Test the sampler gradient for parameter variables with coefficients""" - sampler = Sampler() - qc = RealAmplitudes(num_qubits=2, reps=1) - qc.rz(qc.parameters[0].exp() + 2 * qc.parameters[1], 0) - qc.rx(3.0 * qc.parameters[0] + qc.parameters[1].sin(), 1) - qc.u(qc.parameters[0], qc.parameters[1], qc.parameters[3], 1) - qc.p(2 * qc.parameters[0] + 1, 0) - qc.rxx(qc.parameters[0] + 2, 0, 1) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4 for _ in qc.parameters], [np.pi / 2 for _ in qc.parameters]] - correct_results = [ - [ - { - 0: 0.30014831912265927, - 1: -0.6634809704357856, - 2: 0.343589357193753, - 3: 0.019743294119373426, - }, - { - 0: 0.16470607453981906, - 1: -0.40996282450610577, - 2: 0.08791803062881773, - 3: 0.15733871933746948, - }, - { - 0: 0.27036068339663866, - 1: -0.273790986018701, - 2: 0.12752010079553433, - 3: -0.12408979817347202, - }, - { - 0: -0.2098616294167757, - 1: -0.2515823946449894, - 2: 0.21929102305386305, - 3: 0.24215300100790207, - }, - ], - [ - { - 0: -1.844810060881004, - 1: 0.04620532700836027, - 2: 1.6367366426074323, - 3: 0.16186809126521057, - }, - { - 0: 0.07296073407769421, - 1: -0.021774869186331716, - 2: 0.02177486918633173, - 3: -0.07296073407769456, - }, - { - 0: -0.07794369186049102, - 1: -0.07794369186049122, - 2: 0.07794369186049117, - 3: 0.07794369186049112, - }, - { - 0: 0.0, - 1: 0.0, - 2: 0.0, - 3: 0.0, - }, - ], - ] - - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=2) - array2 = _quasi2array(correct_results[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_parameters(self, grad): - """Test the sampler gradient for parameters""" - sampler = Sampler() - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rz(b, 0) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4, np.pi / 2]] - expected = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param], parameters=[[a]]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(expected[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - # parameter order - with self.subTest(msg="The order of gradients"): - c = Parameter("c") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rz(b, 0) - qc.rx(c, 0) - qc.measure_all() - param_values = [[np.pi / 4, np.pi / 2, np.pi / 3]] - params = [[a, b, c], [c, b, a], [a, c], [c, a]] - expected = [ - [ - {0: -0.17677666583387008, 1: 0.17677666583378482}, - {0: 0.3061861668168149, 1: -0.3061861668167012}, - {0: -0.3061861668168149, 1: 0.30618616681678645}, - ], - [ - {0: -0.3061861668168149, 1: 0.30618616681678645}, - {0: 0.3061861668168149, 1: -0.3061861668167012}, - {0: -0.17677666583387008, 1: 0.17677666583378482}, - ], - [ - {0: -0.17677666583387008, 1: 0.17677666583378482}, - {0: -0.3061861668168149, 1: 0.30618616681678645}, - ], - [ - {0: -0.3061861668168149, 1: 0.30618616681678645}, - {0: -0.17677666583387008, 1: 0.17677666583378482}, - ], - ] - for i, p in enumerate(params): - gradients = gradient.run([qc], param_values, parameters=[p]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(expected[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_multi_arguments(self, grad): - """Test the sampler gradient for multiple arguments""" - sampler = Sampler() - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.measure_all() - qc2 = QuantumCircuit(1) - qc2.rx(b, 0) - qc2.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4], [np.pi / 2]] - correct_results = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}], - [{0: -0.499999, 1: 0.499999}], - ] - gradients = gradient.run([qc, qc2], param_list).result().gradients - for i, q_dists in enumerate(gradients): - array1 = _quasi2array(q_dists, num_qubits=1) - array2 = _quasi2array(correct_results[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - # parameters - with self.subTest(msg="Different parameters"): - c = Parameter("c") - qc3 = QuantumCircuit(1) - qc3.rx(c, 0) - qc3.ry(a, 0) - qc3.measure_all() - param_list2 = [[np.pi / 4], [np.pi / 4, np.pi / 4], [np.pi / 4, np.pi / 4]] - gradients = ( - gradient.run([qc, qc3, qc3], param_list2, parameters=[[a], [c], None]) - .result() - .gradients - ) - correct_results = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}], - [{0: -0.25, 1: 0.25}], - [{0: -0.25, 1: 0.25}, {0: -0.25, 1: 0.25}], - ] - for i, q_dists in enumerate(gradients): - array1 = _quasi2array(q_dists, num_qubits=1) - array2 = _quasi2array(correct_results[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_validation(self, grad): - """Test sampler gradient's validation""" - sampler = Sampler() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4], [np.pi / 2]] - with self.assertRaises(ValueError): - gradient.run([qc], param_list) - with self.assertRaises(ValueError): - gradient.run([qc, qc], param_list, parameters=[[a]]) - with self.assertRaises(ValueError): - gradient.run([qc], [[np.pi / 4, np.pi / 4]]) - - def test_spsa_gradient(self): - """Test the SPSA sampler gradient""" - sampler = Sampler() - with self.assertRaises(ValueError): - _ = SPSASamplerGradient(sampler, epsilon=-0.1) - - a = Parameter("a") - b = Parameter("b") - c = Parameter("c") - qc = QuantumCircuit(2) - qc.rx(b, 0) - qc.rx(a, 1) - qc.measure_all() - param_list = [[1, 2]] - correct_results = [ - [ - {0: 0.2273244, 1: -0.6480598, 2: 0.2273244, 3: 0.1934111}, - {0: -0.2273244, 1: 0.6480598, 2: -0.2273244, 3: -0.1934111}, - ], - ] - gradient = SPSASamplerGradient(sampler, epsilon=1e-6, seed=123) - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=2) - array2 = _quasi2array(correct_results[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - # multi parameters - with self.subTest(msg="Multiple parameters"): - param_list2 = [[1, 2], [1, 2], [3, 4]] - correct_results2 = [ - [ - {0: 0.2273244, 1: -0.6480598, 2: 0.2273244, 3: 0.1934111}, - {0: -0.2273244, 1: 0.6480598, 2: -0.2273244, 3: -0.1934111}, - ], - [ - {0: -0.2273244, 1: 0.6480598, 2: -0.2273244, 3: -0.1934111}, - ], - [ - {0: -0.0141129, 1: -0.0564471, 2: -0.3642884, 3: 0.4348484}, - {0: 0.0141129, 1: 0.0564471, 2: 0.3642884, 3: -0.4348484}, - ], - ] - gradient = SPSASamplerGradient(sampler, epsilon=1e-6, seed=123) - gradients = ( - gradient.run([qc] * 3, param_list2, parameters=[None, [b], None]).result().gradients - ) - for i, result in enumerate(gradients): - array1 = _quasi2array(result, num_qubits=2) - array2 = _quasi2array(correct_results2[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - # batch size - with self.subTest(msg="Batch size"): - param_list = [[1, 1]] - gradient = SPSASamplerGradient(sampler, epsilon=1e-6, batch_size=4, seed=123) - gradients = gradient.run([qc], param_list).result().gradients - correct_results3 = [ - [ - { - 0: -0.1620149622932887, - 1: -0.25872053011771756, - 2: 0.3723827084675668, - 3: 0.04835278392088804, - }, - { - 0: -0.1620149622932887, - 1: 0.3723827084675668, - 2: -0.25872053011771756, - 3: 0.04835278392088804, - }, - ] - ] - for i, q_dists in enumerate(gradients): - array1 = _quasi2array(q_dists, num_qubits=2) - array2 = _quasi2array(correct_results3[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - # parameter order - with self.subTest(msg="The order of gradients"): - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rz(b, 0) - qc.rx(c, 0) - qc.measure_all() - param_list = [[np.pi / 4, np.pi / 2, np.pi / 3]] - param = [[a, b, c], [c, b, a], [a, c], [c, a]] - correct_results = [ - [ - {0: -0.17677624757590138, 1: 0.17677624757590138}, - {0: 0.17677624757590138, 1: -0.17677624757590138}, - {0: 0.17677624757590138, 1: -0.17677624757590138}, - ], - [ - {0: 0.17677624757590138, 1: -0.17677624757590138}, - {0: 0.17677624757590138, 1: -0.17677624757590138}, - {0: -0.17677624757590138, 1: 0.17677624757590138}, - ], - [ - {0: -0.17677624757590138, 1: 0.17677624757590138}, - {0: 0.17677624757590138, 1: -0.17677624757590138}, - ], - [ - {0: 0.17677624757590138, 1: -0.17677624757590138}, - {0: -0.17677624757590138, 1: 0.17677624757590138}, - ], - ] - for i, p in enumerate(param): - gradient = SPSASamplerGradient(sampler, epsilon=1e-6, seed=123) - gradients = gradient.run([qc], param_list, parameters=[p]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(correct_results[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(ParamShiftSamplerGradient, LinCombSamplerGradient) - def test_gradient_random_parameters(self, grad): - """Test param shift and lin comb w/ random parameters""" - rng = np.random.default_rng(123) - qc = RealAmplitudes(num_qubits=3, reps=1) - params = qc.parameters - qc.rx(3.0 * params[0] + params[1].sin(), 0) - qc.ry(params[0].exp() + 2 * params[1], 1) - qc.rz(params[0] * params[1] - params[2], 2) - qc.p(2 * params[0] + 1, 0) - qc.u(params[0].sin(), params[1] - 2, params[2] * params[3], 1) - qc.sx(2) - qc.rxx(params[0].sin(), 1, 2) - qc.ryy(params[1].cos(), 2, 0) - qc.rzz(params[2] * 2, 0, 1) - qc.crx(params[0].exp(), 1, 2) - qc.cry(params[1].arctan(), 2, 0) - qc.crz(params[2] * -2, 0, 1) - qc.dcx(0, 1) - qc.csdg(0, 1) - qc.ccx(0, 1, 2) - qc.iswap(0, 2) - qc.swap(1, 2) - qc.global_phase = params[0] * params[1] + params[2].cos().exp() - qc.measure_all() - - sampler = Sampler() - findiff = FiniteDiffSamplerGradient(sampler, 1e-6) - gradient = grad(sampler) - - num_qubits = qc.num_qubits - num_tries = 10 - param_values = rng.normal(0, 2, (num_tries, qc.num_parameters)).tolist() - result1 = findiff.run([qc] * num_tries, param_values).result().gradients - result2 = gradient.run([qc] * num_tries, param_values).result().gradients - self.assertEqual(len(result1), len(result2)) - for res1, res2 in zip(result1, result2): - array1 = _quasi2array(res1, num_qubits) - array2 = _quasi2array(res2, num_qubits) - np.testing.assert_allclose(array1, array2, rtol=1e-4) - - @data( - FiniteDiffSamplerGradient, - ParamShiftSamplerGradient, - LinCombSamplerGradient, - SPSASamplerGradient, - ) - def test_options(self, grad): - """Test sampler gradient's run options""" - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.measure_all() - sampler = Sampler(options={"shots": 100}) - with self.subTest("sampler"): - if grad is FiniteDiffSamplerGradient or grad is SPSASamplerGradient: - gradient = grad(sampler, epsilon=1e-6) - else: - gradient = grad(sampler) - options = gradient.options - result = gradient.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("gradient init"): - if grad is FiniteDiffSamplerGradient or grad is SPSASamplerGradient: - gradient = grad(sampler, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(sampler, options={"shots": 200}) - options = gradient.options - result = gradient.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 200) - self.assertEqual(options.get("shots"), 200) - - with self.subTest("gradient update"): - if grad is FiniteDiffSamplerGradient or grad is SPSASamplerGradient: - gradient = grad(sampler, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(sampler, options={"shots": 200}) - gradient.update_default_options(shots=100) - options = gradient.options - result = gradient.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("gradient run"): - if grad is FiniteDiffSamplerGradient or grad is SPSASamplerGradient: - gradient = grad(sampler, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(sampler, options={"shots": 200}) - options = gradient.options - result = gradient.run([qc], [[1]], shots=300).result() - self.assertEqual(result.options.get("shots"), 300) - # Only default + sampler options. Not run. - self.assertEqual(options.get("shots"), 200) - - @data( - FiniteDiffSamplerGradient, - ParamShiftSamplerGradient, - LinCombSamplerGradient, - SPSASamplerGradient, - ) - def test_operations_preserved(self, gradient_cls): - """Test non-parameterized instructions are preserved and not unrolled.""" - x = Parameter("x") - circuit = QuantumCircuit(2) - circuit.initialize(np.array([1, 1, 0, 0]) / np.sqrt(2)) # this should remain as initialize - circuit.crx(x, 0, 1) # this should get unrolled - circuit.measure_all() - - values = [np.pi / 2] - expect = [{0: 0, 1: -0.25, 2: 0, 3: 0.25}] - - ops = [] - - def operations_callback(op): - ops.append(op) - - sampler = LoggingSampler(operations_callback=operations_callback) - - if gradient_cls in [SPSASamplerGradient, FiniteDiffSamplerGradient]: - gradient = gradient_cls(sampler, epsilon=0.01) - else: - gradient = gradient_cls(sampler) - - job = gradient.run([circuit], [values]) - result = job.result() - - with self.subTest(msg="assert initialize is preserved"): - self.assertTrue(all("initialize" in ops_i[0].keys() for ops_i in ops)) - - with self.subTest(msg="assert result is correct"): - array1 = _quasi2array(result.gradients[0], num_qubits=2) - array2 = _quasi2array(expect, num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-5) - - -def _quasi2array(quasis: List[QuasiDistribution], num_qubits: int) -> np.ndarray: - ret = np.zeros((len(quasis), 2**num_qubits)) - for i, quasi in enumerate(quasis): - ret[i, list(quasi.keys())] = list(quasi.values()) - return ret - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/__init__.py b/test/python/algorithms/minimum_eigensolvers/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. diff --git a/test/python/algorithms/minimum_eigensolvers/test_adapt_vqe.py b/test/python/algorithms/minimum_eigensolvers/test_adapt_vqe.py deleted file mode 100644 index 9509abf422f3..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_adapt_vqe.py +++ /dev/null @@ -1,245 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Test of the AdaptVQE minimum eigensolver""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from ddt import ddt, data, unpack -import numpy as np - -from qiskit.algorithms.minimum_eigensolvers import VQE -from qiskit.algorithms.minimum_eigensolvers.adapt_vqe import AdaptVQE, TerminationCriterion -from qiskit.algorithms.optimizers import SLSQP -from qiskit.circuit import QuantumCircuit, QuantumRegister -from qiskit.circuit.library import EvolvedOperatorAnsatz -from qiskit.opflow import PauliSumOp -from qiskit.primitives import Estimator -from qiskit.quantum_info import SparsePauliOp -from qiskit.utils import algorithm_globals - - -@ddt -class TestAdaptVQE(QiskitAlgorithmsTestCase): - """Test of the AdaptVQE minimum eigensolver""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 42 - - with self.assertWarns(DeprecationWarning): - self.h2_op = PauliSumOp.from_list( - [ - ("IIII", -0.8105479805373266), - ("ZZII", -0.2257534922240251), - ("IIZI", +0.12091263261776641), - ("ZIZI", +0.12091263261776641), - ("IZZI", +0.17218393261915543), - ("IIIZ", +0.17218393261915546), - ("IZIZ", +0.1661454325638243), - ("ZZIZ", +0.1661454325638243), - ("IIZZ", -0.2257534922240251), - ("IZZZ", +0.16892753870087926), - ("ZZZZ", +0.17464343068300464), - ("IXIX", +0.04523279994605788), - ("ZXIX", +0.04523279994605788), - ("IXZX", -0.04523279994605788), - ("ZXZX", -0.04523279994605788), - ] - ) - self.excitation_pool = [ - PauliSumOp( - SparsePauliOp(["IIIY", "IIZY"], coeffs=[0.5 + 0.0j, -0.5 + 0.0j]), coeff=1.0 - ), - PauliSumOp( - SparsePauliOp(["ZYII", "IYZI"], coeffs=[-0.5 + 0.0j, 0.5 + 0.0j]), coeff=1.0 - ), - PauliSumOp( - SparsePauliOp( - ["ZXZY", "IXIY", "IYIX", "ZYZX", "IYZX", "ZYIX", "ZXIY", "IXZY"], - coeffs=[ - -0.125 + 0.0j, - 0.125 + 0.0j, - -0.125 + 0.0j, - 0.125 + 0.0j, - 0.125 + 0.0j, - -0.125 + 0.0j, - 0.125 + 0.0j, - -0.125 + 0.0j, - ], - ), - coeff=1.0, - ), - ] - self.initial_state = QuantumCircuit(QuantumRegister(4)) - self.initial_state.x(0) - self.initial_state.x(1) - self.ansatz = EvolvedOperatorAnsatz( - self.excitation_pool, initial_state=self.initial_state - ) - self.optimizer = SLSQP() - - def test_default(self): - """Default execution""" - calc = AdaptVQE(VQE(Estimator(), self.ansatz, self.optimizer)) - - with self.assertWarns(DeprecationWarning): - res = calc.compute_minimum_eigenvalue(operator=self.h2_op) - - expected_eigenvalue = -1.85727503 - - self.assertAlmostEqual(res.eigenvalue, expected_eigenvalue, places=6) - np.testing.assert_allclose(res.eigenvalue_history, [expected_eigenvalue], rtol=1e-6) - - def test_with_quantum_info(self): - """Test behavior with quantum_info-based operators.""" - ansatz = EvolvedOperatorAnsatz( - [op.primitive for op in self.excitation_pool], - initial_state=self.initial_state, - ) - - calc = AdaptVQE(VQE(Estimator(), ansatz, self.optimizer)) - res = calc.compute_minimum_eigenvalue(operator=self.h2_op.primitive) - - expected_eigenvalue = -1.85727503 - - self.assertAlmostEqual(res.eigenvalue, expected_eigenvalue, places=6) - np.testing.assert_allclose(res.eigenvalue_history, [expected_eigenvalue], rtol=1e-6) - - def test_converged(self): - """Test to check termination criteria""" - calc = AdaptVQE( - VQE(Estimator(), self.ansatz, self.optimizer), - gradient_threshold=1e-3, - ) - with self.assertWarns(DeprecationWarning): - res = calc.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertEqual(res.termination_criterion, TerminationCriterion.CONVERGED) - - def test_maximum(self): - """Test to check termination criteria""" - calc = AdaptVQE( - VQE(Estimator(), self.ansatz, self.optimizer), - max_iterations=1, - ) - with self.assertWarns(DeprecationWarning): - res = calc.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertEqual(res.termination_criterion, TerminationCriterion.MAXIMUM) - - def test_eigenvalue_threshold(self): - """Test for the eigenvalue_threshold attribute.""" - operator = SparsePauliOp.from_list( - [ - ("XX", 1.0), - ("ZX", -0.5), - ("XZ", -0.5), - ] - ) - ansatz = EvolvedOperatorAnsatz( - [ - SparsePauliOp.from_list([("YZ", 0.4)]), - SparsePauliOp.from_list([("ZY", 0.5)]), - ], - initial_state=QuantumCircuit(2), - ) - - calc = AdaptVQE( - VQE(Estimator(), ansatz, self.optimizer), - eigenvalue_threshold=1, - ) - res = calc.compute_minimum_eigenvalue(operator) - - self.assertEqual(res.termination_criterion, TerminationCriterion.CONVERGED) - - def test_threshold_attribute(self): - """Test the (pending deprecated) threshold attribute""" - with self.assertWarns(PendingDeprecationWarning): - calc = AdaptVQE( - VQE(Estimator(), self.ansatz, self.optimizer), - threshold=1e-3, - ) - with self.assertWarns(DeprecationWarning): - res = calc.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertEqual(res.termination_criterion, TerminationCriterion.CONVERGED) - - @data( - ([1, 1], True), - ([1, 11], False), - ([11, 1], False), - ([1, 12], False), - ([12, 2], False), - ([1, 1, 1], True), - ([1, 2, 1], False), - ([1, 2, 2], True), - ([1, 2, 21], False), - ([1, 12, 2], False), - ([11, 1, 2], False), - ([1, 2, 1, 1], True), - ([1, 2, 1, 2], True), - ([1, 2, 1, 21], False), - ([11, 2, 1, 2], False), - ([1, 11, 1, 111], False), - ([11, 1, 111, 1], False), - ([1, 2, 3, 1, 2, 3], True), - ([1, 2, 3, 4, 1, 2, 3], False), - ([11, 2, 3, 1, 2, 3], False), - ([1, 2, 3, 1, 2, 31], False), - ([1, 2, 3, 4, 1, 2, 3, 4], True), - ([11, 2, 3, 4, 1, 2, 3, 4], False), - ([1, 2, 3, 4, 1, 2, 3, 41], False), - ([1, 2, 3, 4, 5, 1, 2, 3, 4], False), - ) - @unpack - def test_cyclicity(self, seq, is_cycle): - """Test AdaptVQE index cycle detection""" - self.assertEqual(is_cycle, AdaptVQE._check_cyclicity(seq)) - - def test_vqe_solver(self): - """Test to check if the VQE solver remains the same or not""" - solver = VQE(Estimator(), self.ansatz, self.optimizer) - calc = AdaptVQE(solver) - with self.assertWarns(DeprecationWarning): - _ = calc.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertEqual(solver.ansatz, calc.solver.ansatz) - - def test_gradient_calculation(self): - """Test to check if the gradient calculation""" - solver = VQE(Estimator(), QuantumCircuit(1), self.optimizer) - calc = AdaptVQE(solver) - calc._excitation_pool = [SparsePauliOp("X")] - res = calc._compute_gradients(operator=SparsePauliOp("Y"), theta=[]) - # compare with manually computed reference value - self.assertAlmostEqual(res[0][0], 2.0) - - def test_supports_aux_operators(self): - """Test that auxiliary operators are supported""" - calc = AdaptVQE(VQE(Estimator(), self.ansatz, self.optimizer)) - with self.assertWarns(DeprecationWarning): - res = calc.compute_minimum_eigenvalue(operator=self.h2_op, aux_operators=[self.h2_op]) - - expected_eigenvalue = -1.85727503 - - self.assertAlmostEqual(res.eigenvalue, expected_eigenvalue, places=6) - self.assertAlmostEqual(res.aux_operators_evaluated[0][0], expected_eigenvalue, places=6) - np.testing.assert_allclose(res.eigenvalue_history, [expected_eigenvalue], rtol=1e-6) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/test_numpy_minimum_eigensolver.py b/test/python/algorithms/minimum_eigensolvers/test_numpy_minimum_eigensolver.py deleted file mode 100644 index f5724f77c152..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_numpy_minimum_eigensolver.py +++ /dev/null @@ -1,240 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Test NumPy minimum eigensolver""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import ddt, data - -from qiskit.algorithms.minimum_eigensolvers import NumPyMinimumEigensolver -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import Operator, SparsePauliOp - -H2_SPARSE_PAULI = SparsePauliOp( - ["II", "ZI", "IZ", "ZZ", "XX"], - coeffs=[ - -1.052373245772859, - 0.39793742484318045, - -0.39793742484318045, - -0.01128010425623538, - 0.18093119978423156, - ], -) - -H2_OP = Operator(H2_SPARSE_PAULI.to_matrix()) - -H2_PAULI = PauliSumOp(H2_SPARSE_PAULI) - - -@ddt -class TestNumPyMinimumEigensolver(QiskitAlgorithmsTestCase): - """Test NumPy minimum eigensolver""" - - def setUp(self): - super().setUp() - aux_op1 = Operator(SparsePauliOp(["II"], coeffs=[2.0]).to_matrix()) - aux_op2 = SparsePauliOp(["II", "ZZ", "YY", "XX"], coeffs=[0.5, 0.5, 0.5, -0.5]) - self.aux_ops_list = [aux_op1, aux_op2] - self.aux_ops_dict = {"aux_op1": aux_op1, "aux_op2": aux_op2} - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_cme(self, op): - """Basic test""" - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_cme_reuse(self, op): - """Test reuse""" - algo = NumPyMinimumEigensolver() - - with self.subTest("Test with no operator or aux_operators, give via compute method"): - result = algo.compute_minimum_eigenvalue(operator=op) - self.assertEqual(result.eigenvalue.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalue, -1.85727503) - self.assertIsNone(result.aux_operators_evaluated) - - with self.subTest("Test with added aux_operators"): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0) - - with self.subTest("Test with aux_operators removed"): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=[]) - self.assertEqual(result.eigenvalue.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalue, -1.85727503) - self.assertIsNone(result.aux_operators_evaluated) - - with self.subTest("Test with aux_operators set again"): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0) - - with self.subTest("Test after setting first aux_operators as main operator"): - result = algo.compute_minimum_eigenvalue( - operator=self.aux_ops_list[0], aux_operators=[] - ) - self.assertAlmostEqual(result.eigenvalue, 2 + 0j) - self.assertIsNone(result.aux_operators_evaluated) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_cme_filter(self, op): - """Basic test""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return v >= -0.5 - - algo = NumPyMinimumEigensolver(filter_criterion=criterion) - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertAlmostEqual(result.eigenvalue, -0.22491125 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_cme_filter_empty(self, op): - """Test with filter always returning False""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return False - - algo = NumPyMinimumEigensolver(filter_criterion=criterion) - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertEqual(result.eigenvalue, None) - self.assertEqual(result.eigenstate, None) - self.assertEqual(result.aux_operators_evaluated, None) - - @data("X", "Y", "Z") - def test_cme_1q(self, op): - """Test for 1 qubit operator""" - algo = NumPyMinimumEigensolver() - operator = SparsePauliOp([op], coeffs=1.0) - result = algo.compute_minimum_eigenvalue(operator=operator) - self.assertAlmostEqual(result.eigenvalue, -1) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_cme_aux_ops_dict(self, op): - """Test dictionary compatibility of aux_operators""" - # Start with an empty dictionary - algo = NumPyMinimumEigensolver() - - with self.subTest("Test with an empty dictionary."): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators={}) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertIsNone(result.aux_operators_evaluated) - - with self.subTest("Test with two auxiliary operators."): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_dict) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0) - - with self.subTest("Test with additional zero and None operators."): - extra_ops = {"None_op": None, "zero_op": 0, **self.aux_ops_dict} - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=extra_ops) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 3) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0) - self.assertEqual(result.aux_operators_evaluated["zero_op"], (0.0, {"variance": 0})) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_aux_operators_list(self, op): - """Test list-based aux_operators.""" - algo = NumPyMinimumEigensolver() - - with self.subTest("Test with two auxiliary operators."): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operators_evaluated[0][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[1][1].pop("variance"), 0.0) - - with self.subTest("Test with additional zero and None operators."): - extra_ops = [*self.aux_ops_list, None, 0] - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=extra_ops) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 4) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0, places=6) - self.assertIsNone(result.aux_operators_evaluated[2], None) - self.assertEqual(result.aux_operators_evaluated[3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operators_evaluated[0][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[1][1].pop("variance"), 0.0) - self.assertEqual(result.aux_operators_evaluated[3][1].pop("variance"), 0.0) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_aux_operators_dict(self, op): - """Test dict-based aux_operators.""" - algo = NumPyMinimumEigensolver() - - with self.subTest("Test with two auxiliary operators."): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_dict) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0, places=6) - # standard deviations - self.assertAlmostEqual( - result.aux_operators_evaluated["aux_op1"][1].pop("variance"), 0.0 - ) - self.assertAlmostEqual( - result.aux_operators_evaluated["aux_op2"][1].pop("variance"), 0.0 - ) - - with self.subTest("Test with additional zero and None operators."): - extra_ops = {**self.aux_ops_dict, "None_operator": None, "zero_operator": 0} - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=extra_ops) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 3) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operators_evaluated["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operators_evaluated.keys()) - # standard deviations - self.assertAlmostEqual( - result.aux_operators_evaluated["aux_op1"][1].pop("variance"), 0.0 - ) - self.assertAlmostEqual( - result.aux_operators_evaluated["aux_op2"][1].pop("variance"), 0.0 - ) - self.assertAlmostEqual( - result.aux_operators_evaluated["zero_operator"][1].pop("variance"), 0.0 - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/test_qaoa.py b/test/python/algorithms/minimum_eigensolvers/test_qaoa.py deleted file mode 100644 index c269f2aa0769..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_qaoa.py +++ /dev/null @@ -1,304 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Test the QAOA algorithm.""" - -import unittest -import warnings - -from functools import partial -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -import rustworkx as rx -from ddt import ddt, idata, unpack -from scipy.optimize import minimize as scipy_minimize - -from qiskit import QuantumCircuit -from qiskit.algorithms.minimum_eigensolvers import QAOA -from qiskit.algorithms.optimizers import COBYLA, NELDER_MEAD -from qiskit.circuit import Parameter -from qiskit.primitives import Sampler -from qiskit.quantum_info import Pauli, SparsePauliOp -from qiskit.result import QuasiDistribution -from qiskit.utils import algorithm_globals - - -W1 = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) -P1 = 1 -M1 = SparsePauliOp.from_list( - [ - ("IIIX", 1), - ("IIXI", 1), - ("IXII", 1), - ("XIII", 1), - ] -) -S1 = {"0101", "1010"} - - -W2 = np.array( - [ - [0.0, 8.0, -9.0, 0.0], - [8.0, 0.0, 7.0, 9.0], - [-9.0, 7.0, 0.0, -8.0], - [0.0, 9.0, -8.0, 0.0], - ] -) -P2 = 1 -M2 = None -S2 = {"1011", "0100"} - -CUSTOM_SUPERPOSITION = [1 / np.sqrt(15)] * 15 + [0] - - -@ddt -class TestQAOA(QiskitAlgorithmsTestCase): - """Test QAOA with MaxCut.""" - - def setUp(self): - super().setUp() - self.seed = 10598 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - self.sampler = Sampler() - - @idata( - [ - [W1, P1, M1, S1], - [W2, P2, M2, S2], - ] - ) - @unpack - def test_qaoa(self, w, reps, mixer, solutions): - """QAOA test""" - self.log.debug("Testing %s-step QAOA with MaxCut on graph\n%s", reps, w) - - qubit_op, _ = self._get_operator(w) - - qaoa = QAOA(self.sampler, COBYLA(), reps=reps, mixer=mixer) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - @idata( - [ - [W1, P1, S1], - [W2, P2, S2], - ] - ) - @unpack - def test_qaoa_qc_mixer(self, w, prob, solutions): - """QAOA test with a mixer as a parameterized circuit""" - self.log.debug( - "Testing %s-step QAOA with MaxCut on graph with a mixer as a parameterized circuit\n%s", - prob, - w, - ) - - optimizer = COBYLA() - qubit_op, _ = self._get_operator(w) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - theta = Parameter("θ") - mixer.rx(theta, range(num_qubits)) - - qaoa = QAOA(self.sampler, optimizer, reps=prob, mixer=mixer) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - def test_qaoa_qc_mixer_many_parameters(self): - """QAOA test with a mixer as a parameterized circuit with the num of parameters > 1.""" - optimizer = COBYLA() - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - for i in range(num_qubits): - theta = Parameter("θ" + str(i)) - mixer.rx(theta, range(num_qubits)) - - qaoa = QAOA(self.sampler, optimizer, reps=2, mixer=mixer) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - self.log.debug(x) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, S1) - - def test_qaoa_qc_mixer_no_parameters(self): - """QAOA test with a mixer as a parameterized circuit with zero parameters.""" - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - # just arbitrary circuit - mixer.rx(np.pi / 2, range(num_qubits)) - - qaoa = QAOA(self.sampler, COBYLA(), reps=1, mixer=mixer) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - # we just assert that we get a result, it is not meaningful. - self.assertIsNotNone(result.eigenstate) - - def test_change_operator_size(self): - """QAOA change operator size test""" - qubit_op, _ = self._get_operator( - np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) - ) - qaoa = QAOA(self.sampler, COBYLA(), reps=1) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 4x4"): - self.assertIn(graph_solution, {"0101", "1010"}) - - qubit_op, _ = self._get_operator( - np.array( - [ - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - ] - ) - ) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 6x6"): - self.assertIn(graph_solution, {"010101", "101010"}) - - @idata([[W2, S2, None], [W2, S2, [0.0, 0.0]], [W2, S2, [1.0, 0.8]]]) - @unpack - def test_qaoa_initial_point(self, w, solutions, init_pt): - """Check first parameter value used is initial point as expected""" - qubit_op, _ = self._get_operator(w) - - first_pt = [] - - def cb_callback(eval_count, parameters, mean, metadata): - nonlocal first_pt - if eval_count == 1: - first_pt = list(parameters) - - qaoa = QAOA( - self.sampler, - COBYLA(), - initial_point=init_pt, - callback=cb_callback, - ) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - - with self.subTest("Initial Point"): - # If None the preferred random initial point of QAOA variational form - if init_pt is None: - self.assertLess(result.eigenvalue, -0.97) - else: - self.assertListEqual(init_pt, first_pt) - - with self.subTest("Solution"): - self.assertIn(graph_solution, solutions) - - def test_qaoa_random_initial_point(self): - """QAOA random initial point""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - qaoa = QAOA(self.sampler, NELDER_MEAD(disp=True), reps=2) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - self.assertLess(result.eigenvalue, -0.97) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - qaoa = QAOA( - self.sampler, - partial(scipy_minimize, method="Nelder-Mead", options={"maxiter": 2}), - ) - result = qaoa.compute_minimum_eigenvalue(qubit_op) - self.assertEqual(result.cost_function_evals, 5) - - def _get_operator(self, weight_matrix): - """Generate Hamiltonian for the max-cut problem of a graph. - - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - - Returns: - PauliSumOp: operator for the Hamiltonian - float: a constant shift for the obj function. - - """ - num_nodes = weight_matrix.shape[0] - pauli_list = [] - shift = 0 - for i in range(num_nodes): - for j in range(i): - if weight_matrix[i, j] != 0: - x_p = np.zeros(num_nodes, dtype=bool) - z_p = np.zeros(num_nodes, dtype=bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([0.5 * weight_matrix[i, j], Pauli((z_p, x_p))]) - shift -= 0.5 * weight_matrix[i, j] - lst = [(pauli[1].to_label(), pauli[0]) for pauli in pauli_list] - return SparsePauliOp.from_list(lst), shift - - def _get_graph_solution(self, x: np.ndarray) -> str: - """Get graph solution from binary string. - - Args: - x : binary string as numpy array. - - Returns: - a graph solution as string. - """ - - return "".join([str(int(i)) for i in 1 - x]) - - def _sample_most_likely(self, state_vector: QuasiDistribution) -> np.ndarray: - """Compute the most likely binary string from state vector. - Args: - state_vector: Quasi-distribution. - - Returns: - Binary string as numpy.ndarray of ints. - """ - values = list(state_vector.values()) - n = int(np.log2(len(values))) - k = np.argmax(np.abs(values)) - x = np.zeros(n) - for i in range(n): - x[i] = k % 2 - k >>= 1 - return x - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/test_qaoa_opflow.py b/test/python/algorithms/minimum_eigensolvers/test_qaoa_opflow.py deleted file mode 100644 index 18527c85046e..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_qaoa_opflow.py +++ /dev/null @@ -1,312 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Test the QAOA algorithm with opflow.""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from functools import partial -import numpy as np - -from scipy.optimize import minimize as scipy_minimize -from ddt import ddt, idata, unpack - -import rustworkx as rx - -from qiskit import QuantumCircuit -from qiskit.algorithms.minimum_eigensolvers import QAOA -from qiskit.algorithms.optimizers import COBYLA, NELDER_MEAD -from qiskit.circuit import Parameter -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import Pauli -from qiskit.result import QuasiDistribution -from qiskit.primitives import Sampler -from qiskit.utils import algorithm_globals - -I = PauliSumOp.from_list([("I", 1)]) -X = PauliSumOp.from_list([("X", 1)]) - -W1 = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) -P1 = 1 -M1 = (I ^ I ^ I ^ X) + (I ^ I ^ X ^ I) + (I ^ X ^ I ^ I) + (X ^ I ^ I ^ I) -S1 = {"0101", "1010"} - - -W2 = np.array( - [ - [0.0, 8.0, -9.0, 0.0], - [8.0, 0.0, 7.0, 9.0], - [-9.0, 7.0, 0.0, -8.0], - [0.0, 9.0, -8.0, 0.0], - ] -) -P2 = 1 -M2 = None -S2 = {"1011", "0100"} - -CUSTOM_SUPERPOSITION = [1 / np.sqrt(15)] * 15 + [0] - - -@ddt -class TestQAOA(QiskitAlgorithmsTestCase): - """Test QAOA with MaxCut.""" - - def setUp(self): - super().setUp() - self.seed = 10598 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - self.sampler = Sampler() - - @idata( - [ - [W1, P1, M1, S1], - [W2, P2, M2, S2], - ] - ) - @unpack - def test_qaoa(self, w, reps, mixer, solutions): - """QAOA test""" - self.log.debug("Testing %s-step QAOA with MaxCut on graph\n%s", reps, w) - - qubit_op, _ = self._get_operator(w) - - qaoa = QAOA(self.sampler, COBYLA(), reps=reps, mixer=mixer) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - @idata( - [ - [W1, P1, S1], - [W2, P2, S2], - ] - ) - @unpack - def test_qaoa_qc_mixer(self, w, prob, solutions): - """QAOA test with a mixer as a parameterized circuit""" - self.log.debug( - "Testing %s-step QAOA with MaxCut on graph with a mixer as a parameterized circuit\n%s", - prob, - w, - ) - - optimizer = COBYLA() - qubit_op, _ = self._get_operator(w) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - theta = Parameter("θ") - mixer.rx(theta, range(num_qubits)) - - qaoa = QAOA(self.sampler, optimizer, reps=prob, mixer=mixer) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - def test_qaoa_qc_mixer_many_parameters(self): - """QAOA test with a mixer as a parameterized circuit with the num of parameters > 1.""" - optimizer = COBYLA() - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - for i in range(num_qubits): - theta = Parameter("θ" + str(i)) - mixer.rx(theta, range(num_qubits)) - - qaoa = QAOA(self.sampler, optimizer, reps=2, mixer=mixer) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - self.log.debug(x) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, S1) - - def test_qaoa_qc_mixer_no_parameters(self): - """QAOA test with a mixer as a parameterized circuit with zero parameters.""" - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - # just arbitrary circuit - mixer.rx(np.pi / 2, range(num_qubits)) - - qaoa = QAOA(self.sampler, COBYLA(), reps=1, mixer=mixer) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - # we just assert that we get a result, it is not meaningful. - self.assertIsNotNone(result.eigenstate) - - def test_change_operator_size(self): - """QAOA change operator size test""" - qubit_op, _ = self._get_operator( - np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) - ) - qaoa = QAOA(self.sampler, COBYLA(), reps=1) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 4x4"): - self.assertIn(graph_solution, {"0101", "1010"}) - - qubit_op, _ = self._get_operator( - np.array( - [ - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - ] - ) - ) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 6x6"): - self.assertIn(graph_solution, {"010101", "101010"}) - - @idata([[W2, S2, None], [W2, S2, [0.0, 0.0]], [W2, S2, [1.0, 0.8]]]) - @unpack - def test_qaoa_initial_point(self, w, solutions, init_pt): - """Check first parameter value used is initial point as expected""" - qubit_op, _ = self._get_operator(w) - - first_pt = [] - - def cb_callback(eval_count, parameters, mean, metadata): - nonlocal first_pt - if eval_count == 1: - first_pt = list(parameters) - - qaoa = QAOA( - self.sampler, - COBYLA(), - initial_point=init_pt, - callback=cb_callback, - ) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - - with self.subTest("Initial Point"): - # If None the preferred random initial point of QAOA variational form - if init_pt is None: - self.assertLess(result.eigenvalue, -0.97) - else: - self.assertListEqual(init_pt, first_pt) - - with self.subTest("Solution"): - self.assertIn(graph_solution, solutions) - - def test_qaoa_random_initial_point(self): - """QAOA random initial point""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - qaoa = QAOA(self.sampler, NELDER_MEAD(disp=True), reps=2) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - self.assertLess(result.eigenvalue, -0.97) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - qaoa = QAOA( - self.sampler, - partial(scipy_minimize, method="Nelder-Mead", options={"maxiter": 2}), - ) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(qubit_op) - self.assertEqual(result.cost_function_evals, 5) - - def _get_operator(self, weight_matrix): - """Generate Hamiltonian for the max-cut problem of a graph. - - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - - Returns: - PauliSumOp: operator for the Hamiltonian - float: a constant shift for the obj function. - - """ - num_nodes = weight_matrix.shape[0] - pauli_list = [] - shift = 0 - for i in range(num_nodes): - for j in range(i): - if weight_matrix[i, j] != 0: - x_p = np.zeros(num_nodes, dtype=bool) - z_p = np.zeros(num_nodes, dtype=bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([0.5 * weight_matrix[i, j], Pauli((z_p, x_p))]) - shift -= 0.5 * weight_matrix[i, j] - opflow_list = [(pauli[1].to_label(), pauli[0]) for pauli in pauli_list] - with self.assertWarns(DeprecationWarning): - return PauliSumOp.from_list(opflow_list), shift - - def _get_graph_solution(self, x: np.ndarray) -> str: - """Get graph solution from binary string. - - Args: - x : binary string as numpy array. - - Returns: - a graph solution as string. - """ - - return "".join([str(int(i)) for i in 1 - x]) - - def _sample_most_likely(self, state_vector: QuasiDistribution) -> np.ndarray: - """Compute the most likely binary string from state vector. - Args: - state_vector: Quasi-distribution. - - Returns: - Binary string as numpy.ndarray of ints. - """ - values = list(state_vector.values()) - n = int(np.log2(len(values))) - k = np.argmax(np.abs(values)) - x = np.zeros(n) - for i in range(n): - x[i] = k % 2 - k >>= 1 - return x - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/test_sampling_vqe.py b/test/python/algorithms/minimum_eigensolvers/test_sampling_vqe.py deleted file mode 100644 index 4a7eb1929ef9..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_sampling_vqe.py +++ /dev/null @@ -1,287 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# 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. - -"""Test the Sampler VQE.""" - - -import unittest -import warnings -from functools import partial -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt -from scipy.optimize import minimize as scipy_minimize - -from qiskit.algorithms import AlgorithmError -from qiskit.algorithms.minimum_eigensolvers import SamplingVQE -from qiskit.algorithms.optimizers import L_BFGS_B, QNSPSA, SLSQP, OptimizerResult -from qiskit.algorithms.state_fidelities import ComputeUncompute -from qiskit.circuit import ParameterVector, QuantumCircuit -from qiskit.circuit.library import RealAmplitudes, TwoLocal -from qiskit.opflow import PauliSumOp -from qiskit.primitives import Sampler -from qiskit.quantum_info import Operator, Pauli, SparsePauliOp -from qiskit.utils import algorithm_globals - - -# pylint: disable=invalid-name -def _mock_optimizer(fun, x0, jac=None, bounds=None, inputs=None): - """A mock of a callable that can be used as minimizer in the VQE. - - If ``inputs`` is given as a dictionary, stores the inputs in that dictionary. - """ - result = OptimizerResult() - result.x = np.zeros_like(x0) - result.fun = fun(result.x) - result.nit = 0 - - if inputs is not None: - inputs.update({"fun": fun, "x0": x0, "jac": jac, "bounds": bounds}) - return result - - -PAULI_OP = PauliSumOp(SparsePauliOp(["ZZ", "IZ", "II"], coeffs=[1, -0.5, 0.12])) -OP = Operator(PAULI_OP.to_matrix()) - - -@ddt -class TestSamplerVQE(QiskitAlgorithmsTestCase): - """Test VQE""" - - def setUp(self): - super().setUp() - self.optimal_value = -1.38 - self.optimal_bitstring = "10" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 42 - - @data(PAULI_OP, OP) - def test_exact_sampler(self, op): - """Test the VQE on BasicAer's statevector simulator.""" - thetas = ParameterVector("th", 4) - ansatz = QuantumCircuit(2) - ansatz.rx(thetas[0], 0) - ansatz.rx(thetas[1], 1) - ansatz.cz(0, 1) - ansatz.ry(thetas[2], 0) - ansatz.ry(thetas[3], 1) - - optimizer = L_BFGS_B() - - # start in maximal superposition - initial_point = np.zeros(ansatz.num_parameters) - initial_point[-ansatz.num_qubits :] = np.pi / 2 - - vqe = SamplingVQE(Sampler(), ansatz, optimizer, initial_point=initial_point) - result = vqe.compute_minimum_eigenvalue(operator=op) - - with self.subTest(msg="test eigenvalue"): - self.assertAlmostEqual(result.eigenvalue, self.optimal_value, places=5) - - with self.subTest(msg="test optimal_value"): - self.assertAlmostEqual(result.optimal_value, self.optimal_value, places=5) - - with self.subTest(msg="test dimension of optimal point"): - self.assertEqual(len(result.optimal_point), ansatz.num_parameters) - - with self.subTest(msg="assert cost_function_evals is set"): - self.assertIsNotNone(result.cost_function_evals) - - with self.subTest(msg="assert optimizer_time is set"): - self.assertIsNotNone(result.optimizer_time) - - with self.subTest(msg="check best measurement"): - self.assertEqual(result.best_measurement["bitstring"], self.optimal_bitstring) - self.assertEqual(result.best_measurement["value"], self.optimal_value) - - @data(PAULI_OP, OP) - def test_invalid_initial_point(self, op): - """Test the proper error is raised when the initial point has the wrong size.""" - ansatz = RealAmplitudes(2, reps=1) - initial_point = np.array([1]) - - vqe = SamplingVQE(Sampler(), ansatz, SLSQP(), initial_point=initial_point) - - with self.assertRaises(ValueError): - _ = vqe.compute_minimum_eigenvalue(operator=op) - - @data(PAULI_OP, OP) - def test_ansatz_resize(self, op): - """Test the ansatz is properly resized if it's a blueprint circuit.""" - ansatz = RealAmplitudes(1, reps=1) - vqe = SamplingVQE(Sampler(), ansatz, SLSQP()) - result = vqe.compute_minimum_eigenvalue(operator=op) - self.assertAlmostEqual(result.eigenvalue, self.optimal_value, places=5) - - @data(PAULI_OP, OP) - def test_invalid_ansatz_size(self, op): - """Test an error is raised if the ansatz has the wrong number of qubits.""" - ansatz = QuantumCircuit(1) - ansatz.compose(RealAmplitudes(1, reps=2)) - vqe = SamplingVQE(Sampler(), ansatz, SLSQP()) - - with self.assertRaises(AlgorithmError): - _ = vqe.compute_minimum_eigenvalue(operator=op) - - @data(PAULI_OP, OP) - def test_missing_varform_params(self, op): - """Test specifying a variational form with no parameters raises an error.""" - circuit = QuantumCircuit(op.num_qubits) - vqe = SamplingVQE(Sampler(), circuit, SLSQP()) - with self.assertRaises(AlgorithmError): - vqe.compute_minimum_eigenvalue(operator=op) - - @data(PAULI_OP, OP) - def test_batch_evaluate_slsqp(self, op): - """Test batching with SLSQP (as representative of SciPyOptimizer).""" - optimizer = SLSQP(max_evals_grouped=10) - vqe = SamplingVQE(Sampler(), RealAmplitudes(), optimizer) - result = vqe.compute_minimum_eigenvalue(operator=op) - self.assertAlmostEqual(result.eigenvalue, self.optimal_value, places=5) - - def test_batch_evaluate_with_qnspsa(self): - """Test batch evaluating with QNSPSA works.""" - ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - - wrapped_sampler = Sampler() - inner_sampler = Sampler() - - callcount = {"count": 0} - - def wrapped_run(*args, **kwargs): - kwargs["callcount"]["count"] += 1 - return inner_sampler.run(*args, **kwargs) - - wrapped_sampler.run = partial(wrapped_run, callcount=callcount) - - fidelity = ComputeUncompute(wrapped_sampler) - - def fidelity_callable(left, right): - batchsize = np.asarray(left).shape[0] - job = fidelity.run(batchsize * [ansatz], batchsize * [ansatz], left, right) - return job.result().fidelities - - qnspsa = QNSPSA(fidelity_callable, maxiter=5) - qnspsa.set_max_evals_grouped(100) - - vqe = SamplingVQE(wrapped_sampler, ansatz, qnspsa) - _ = vqe.compute_minimum_eigenvalue(Pauli("ZZ")) - - # 1 calibration + 1 stddev estimation + 1 initial blocking - # + 5 (1 loss + 1 fidelity + 1 blocking) + 1 return loss + 1 VQE eval - expected = 1 + 1 + 1 + 5 * 3 + 1 + 1 - - self.assertEqual(callcount["count"], expected) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - vqe = SamplingVQE( - Sampler(), - RealAmplitudes(), - partial(scipy_minimize, method="COBYLA", options={"maxiter": 2}), - ) - result = vqe.compute_minimum_eigenvalue(Pauli("Z")) - self.assertEqual(result.cost_function_evals, 2) - - def test_optimizer_callable(self): - """Test passing a optimizer directly as callable.""" - ansatz = RealAmplitudes(1, reps=1) - vqe = SamplingVQE(Sampler(), ansatz, _mock_optimizer) - result = vqe.compute_minimum_eigenvalue(Pauli("Z")) - self.assertTrue(np.all(result.optimal_point == np.zeros(ansatz.num_parameters))) - - @data(PAULI_OP, OP) - def test_auxops(self, op): - """Test passing auxiliary operators.""" - ansatz = RealAmplitudes(2, reps=1) - vqe = SamplingVQE(Sampler(), ansatz, SLSQP()) - - as_list = [Pauli("ZZ"), Pauli("II")] - with self.subTest(auxops=as_list): - result = vqe.compute_minimum_eigenvalue(op, aux_operators=as_list) - self.assertIsInstance(result.aux_operators_evaluated, list) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], -1 + 0j, places=5) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 1 + 0j, places=5) - - as_dict = {"magnetization": SparsePauliOp(["ZI", "IZ"])} - with self.subTest(auxops=as_dict): - result = vqe.compute_minimum_eigenvalue(op, aux_operators=as_dict) - self.assertIsInstance(result.aux_operators_evaluated, dict) - self.assertEqual(len(result.aux_operators_evaluated.keys()), 1) - self.assertAlmostEqual(result.aux_operators_evaluated["magnetization"][0], 0j, places=5) - - def test_nondiag_observable_raises(self): - """Test passing a non-diagonal observable raises an error.""" - vqe = SamplingVQE(Sampler(), RealAmplitudes(), SLSQP()) - - with self.assertRaises(ValueError): - _ = vqe.compute_minimum_eigenvalue(Pauli("X")) - - @data(PAULI_OP, OP) - def test_callback(self, op): - """Test the callback on VQE.""" - history = { - "eval_count": [], - "parameters": [], - "mean": [], - "metadata": [], - } - - def store_intermediate_result(eval_count, parameters, mean, metadata): - history["eval_count"].append(eval_count) - history["parameters"].append(parameters) - history["mean"].append(mean) - history["metadata"].append(metadata) - - sampling_vqe = SamplingVQE( - Sampler(), - RealAmplitudes(2, reps=1), - SLSQP(), - callback=store_intermediate_result, - ) - sampling_vqe.compute_minimum_eigenvalue(operator=op) - - self.assertTrue(all(isinstance(count, int) for count in history["eval_count"])) - self.assertTrue(all(isinstance(mean, complex) for mean in history["mean"])) - self.assertTrue(all(isinstance(metadata, dict) for metadata in history["metadata"])) - for params in history["parameters"]: - self.assertTrue(all(isinstance(param, float) for param in params)) - - def test_aggregation(self): - """Test the aggregation works.""" - - # test a custom aggregration that just uses the best measurement - def best_measurement(measurements): - res = min(measurements, key=lambda meas: meas[1])[1] - return res - - # test CVaR with alpha of 0.4 (i.e. 40% of the best measurements) - alpha = 0.4 - - ansatz = RealAmplitudes(1, reps=0) - ansatz.h(0) - - for aggregation in [alpha, best_measurement]: - with self.subTest(aggregation=aggregation): - vqe = SamplingVQE(Sampler(), ansatz, _mock_optimizer, aggregation=best_measurement) - result = vqe.compute_minimum_eigenvalue(Pauli("Z")) - - # evaluation at x0=0 samples -1 and 1 with 50% probability, and our aggregation - # takes the smallest value - self.assertAlmostEqual(result.optimal_value, -1) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/test_vqe.py b/test/python/algorithms/minimum_eigensolvers/test_vqe.py deleted file mode 100644 index 210c622ca817..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_vqe.py +++ /dev/null @@ -1,488 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Test the variational quantum eigensolver algorithm.""" - -import unittest -import warnings -from test.python.algorithms import QiskitAlgorithmsTestCase - -from functools import partial -import numpy as np -from scipy.optimize import minimize as scipy_minimize -from ddt import data, ddt - -from qiskit import QuantumCircuit -from qiskit.algorithms import AlgorithmError -from qiskit.algorithms.gradients import ParamShiftEstimatorGradient -from qiskit.algorithms.minimum_eigensolvers import VQE -from qiskit.algorithms.optimizers import ( - CG, - COBYLA, - GradientDescent, - L_BFGS_B, - OptimizerResult, - P_BFGS, - QNSPSA, - SLSQP, - SPSA, - TNC, -) -from qiskit.algorithms.state_fidelities import ComputeUncompute -from qiskit.circuit.library import RealAmplitudes, TwoLocal -from qiskit.opflow import PauliSumOp, TwoQubitReduction -from qiskit.quantum_info import SparsePauliOp, Operator, Pauli -from qiskit.primitives import Estimator, Sampler -from qiskit.utils import algorithm_globals - -# pylint: disable=invalid-name -def _mock_optimizer(fun, x0, jac=None, bounds=None, inputs=None) -> OptimizerResult: - """A mock of a callable that can be used as minimizer in the VQE.""" - result = OptimizerResult() - result.x = np.zeros_like(x0) - result.fun = fun(result.x) - result.nit = 0 - - if inputs is not None: - inputs.update({"fun": fun, "x0": x0, "jac": jac, "bounds": bounds}) - return result - - -@ddt -class TestVQE(QiskitAlgorithmsTestCase): - """Test VQE""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - self.h2_op = SparsePauliOp( - ["II", "IZ", "ZI", "ZZ", "XX"], - coeffs=[ - -1.052373245772859, - 0.39793742484318045, - -0.39793742484318045, - -0.01128010425623538, - 0.18093119978423156, - ], - ) - self.h2_energy = -1.85727503 - - self.ryrz_wavefunction = TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - self.ry_wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - @data(L_BFGS_B(), COBYLA()) - def test_basic_aer_statevector(self, estimator): - """Test VQE using reference Estimator.""" - vqe = VQE(Estimator(), self.ryrz_wavefunction, estimator) - - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - with self.subTest(msg="test eigenvalue"): - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - with self.subTest(msg="test optimal_value"): - self.assertAlmostEqual(result.optimal_value, self.h2_energy) - - with self.subTest(msg="test dimension of optimal point"): - self.assertEqual(len(result.optimal_point), 16) - - with self.subTest(msg="assert cost_function_evals is set"): - self.assertIsNotNone(result.cost_function_evals) - - with self.subTest(msg="assert optimizer_time is set"): - self.assertIsNotNone(result.optimizer_time) - - with self.subTest(msg="assert optimizer_result is set"): - self.assertIsNotNone(result.optimizer_result) - - with self.subTest(msg="assert optimizer_result."): - self.assertAlmostEqual(result.optimizer_result.fun, self.h2_energy, places=5) - - with self.subTest(msg="assert return ansatz is set"): - estimator = Estimator() - job = estimator.run(result.optimal_circuit, self.h2_op, result.optimal_point) - np.testing.assert_array_almost_equal(job.result().values, result.eigenvalue, 6) - - def test_invalid_initial_point(self): - """Test the proper error is raised when the initial point has the wrong size.""" - ansatz = self.ryrz_wavefunction - initial_point = np.array([1]) - - vqe = VQE( - Estimator(), - ansatz, - SLSQP(), - initial_point=initial_point, - ) - - with self.assertRaises(ValueError): - _ = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - def test_ansatz_resize(self): - """Test the ansatz is properly resized if it's a blueprint circuit.""" - ansatz = RealAmplitudes(1, reps=1) - vqe = VQE(Estimator(), ansatz, SLSQP()) - result = vqe.compute_minimum_eigenvalue(self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - def test_invalid_ansatz_size(self): - """Test an error is raised if the ansatz has the wrong number of qubits.""" - ansatz = QuantumCircuit(1) - ansatz.compose(RealAmplitudes(1, reps=2)) - vqe = VQE(Estimator(), ansatz, SLSQP()) - - with self.assertRaises(AlgorithmError): - _ = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - def test_missing_ansatz_params(self): - """Test specifying an ansatz with no parameters raises an error.""" - ansatz = QuantumCircuit(self.h2_op.num_qubits) - vqe = VQE(Estimator(), ansatz, SLSQP()) - with self.assertRaises(AlgorithmError): - vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - def test_max_evals_grouped(self): - """Test with SLSQP with max_evals_grouped.""" - optimizer = SLSQP(maxiter=50, max_evals_grouped=5) - vqe = VQE( - Estimator(), - self.ryrz_wavefunction, - optimizer, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - @data( - CG(), - L_BFGS_B(), - P_BFGS(), - SLSQP(), - TNC(), - ) - def test_with_gradient(self, optimizer): - """Test VQE using gradient primitive.""" - estimator = Estimator() - vqe = VQE( - estimator, - self.ry_wavefunction, - optimizer, - gradient=ParamShiftEstimatorGradient(estimator), - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - def test_gradient_passed(self): - """Test the gradient is properly passed into the optimizer.""" - inputs = {} - estimator = Estimator() - vqe = VQE( - estimator, - RealAmplitudes(), - partial(_mock_optimizer, inputs=inputs), - gradient=ParamShiftEstimatorGradient(estimator), - ) - _ = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertIsNotNone(inputs["jac"]) - - def test_gradient_run(self): - """Test using the gradient to calculate the minimum.""" - estimator = Estimator() - vqe = VQE( - estimator, - RealAmplitudes(), - GradientDescent(maxiter=200, learning_rate=0.1), - gradient=ParamShiftEstimatorGradient(estimator), - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - def test_with_two_qubit_reduction(self): - """Test the VQE using TwoQubitReduction.""" - with self.assertWarns(DeprecationWarning): - qubit_op = PauliSumOp.from_list( - [ - ("IIII", -0.8105479805373266), - ("IIIZ", 0.17218393261915552), - ("IIZZ", -0.22575349222402472), - ("IZZI", 0.1721839326191556), - ("ZZII", -0.22575349222402466), - ("IIZI", 0.1209126326177663), - ("IZZZ", 0.16892753870087912), - ("IXZX", -0.045232799946057854), - ("ZXIX", 0.045232799946057854), - ("IXIX", 0.045232799946057854), - ("ZXZX", -0.045232799946057854), - ("ZZIZ", 0.16614543256382414), - ("IZIZ", 0.16614543256382414), - ("ZZZZ", 0.17464343068300453), - ("ZIZI", 0.1209126326177663), - ] - ) - tapered_qubit_op = TwoQubitReduction(num_particles=2).convert(qubit_op) - vqe = VQE( - Estimator(), - self.ry_wavefunction, - SPSA(maxiter=300, last_avg=5), - ) - result = vqe.compute_minimum_eigenvalue(tapered_qubit_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=2) - - def test_callback(self): - """Test the callback on VQE.""" - history = {"eval_count": [], "parameters": [], "mean": [], "metadata": []} - - def store_intermediate_result(eval_count, parameters, mean, metadata): - history["eval_count"].append(eval_count) - history["parameters"].append(parameters) - history["mean"].append(mean) - history["metadata"].append(metadata) - - optimizer = COBYLA(maxiter=3) - wavefunction = self.ry_wavefunction - - estimator = Estimator() - - vqe = VQE( - estimator, - wavefunction, - optimizer, - callback=store_intermediate_result, - ) - vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertTrue(all(isinstance(count, int) for count in history["eval_count"])) - self.assertTrue(all(isinstance(mean, float) for mean in history["mean"])) - self.assertTrue(all(isinstance(metadata, dict) for metadata in history["metadata"])) - for params in history["parameters"]: - self.assertTrue(all(isinstance(param, float) for param in params)) - - def test_reuse(self): - """Test re-using a VQE algorithm instance.""" - ansatz = TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - vqe = VQE(Estimator(), ansatz, SLSQP(maxiter=300)) - with self.subTest(msg="assert VQE works once all info is available"): - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - operator = Operator(np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 2, 0], [0, 0, 0, 3]])) - - with self.subTest(msg="assert vqe works on re-use."): - result = vqe.compute_minimum_eigenvalue(operator=operator) - self.assertAlmostEqual(result.eigenvalue.real, -1.0, places=5) - - def test_vqe_optimizer_reuse(self): - """Test running same VQE twice to re-use optimizer, then switch optimizer""" - vqe = VQE( - Estimator(), - self.ryrz_wavefunction, - SLSQP(), - ) - - def run_check(): - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - run_check() - - with self.subTest("Optimizer re-use."): - run_check() - - with self.subTest("Optimizer replace."): - vqe.optimizer = L_BFGS_B() - run_check() - - def test_default_batch_evaluation_on_spsa(self): - """Test the default batching works.""" - ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - - wrapped_estimator = Estimator() - inner_estimator = Estimator() - - callcount = {"estimator": 0} - - def wrapped_estimator_run(*args, **kwargs): - kwargs["callcount"]["estimator"] += 1 - return inner_estimator.run(*args, **kwargs) - - wrapped_estimator.run = partial(wrapped_estimator_run, callcount=callcount) - - spsa = SPSA(maxiter=5) - - vqe = VQE(wrapped_estimator, ansatz, spsa) - _ = vqe.compute_minimum_eigenvalue(Pauli("ZZ")) - - # 1 calibration + 5 loss + 1 return loss - expected_estimator_runs = 1 + 5 + 1 - - with self.subTest(msg="check callcount"): - self.assertEqual(callcount["estimator"], expected_estimator_runs) - - with self.subTest(msg="check reset to original max evals grouped"): - self.assertIsNone(spsa._max_evals_grouped) - - def test_batch_evaluate_with_qnspsa(self): - """Test batch evaluating with QNSPSA works.""" - ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - - wrapped_sampler = Sampler() - inner_sampler = Sampler() - - wrapped_estimator = Estimator() - inner_estimator = Estimator() - - callcount = {"sampler": 0, "estimator": 0} - - def wrapped_estimator_run(*args, **kwargs): - kwargs["callcount"]["estimator"] += 1 - return inner_estimator.run(*args, **kwargs) - - def wrapped_sampler_run(*args, **kwargs): - kwargs["callcount"]["sampler"] += 1 - return inner_sampler.run(*args, **kwargs) - - wrapped_estimator.run = partial(wrapped_estimator_run, callcount=callcount) - wrapped_sampler.run = partial(wrapped_sampler_run, callcount=callcount) - - fidelity = ComputeUncompute(wrapped_sampler) - - def fidelity_callable(left, right): - batchsize = np.asarray(left).shape[0] - job = fidelity.run(batchsize * [ansatz], batchsize * [ansatz], left, right) - return job.result().fidelities - - qnspsa = QNSPSA(fidelity_callable, maxiter=5) - qnspsa.set_max_evals_grouped(100) - - vqe = VQE( - wrapped_estimator, - ansatz, - qnspsa, - ) - _ = vqe.compute_minimum_eigenvalue(Pauli("ZZ")) - - # 5 (fidelity) - expected_sampler_runs = 5 - # 1 calibration + 1 stddev estimation + 1 initial blocking - # + 5 (1 loss + 1 blocking) + 1 return loss - expected_estimator_runs = 1 + 1 + 1 + 5 * 2 + 1 - - self.assertEqual(callcount["sampler"], expected_sampler_runs) - self.assertEqual(callcount["estimator"], expected_estimator_runs) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - vqe = VQE( - Estimator(), - self.ryrz_wavefunction, - partial(scipy_minimize, method="L-BFGS-B", options={"maxiter": 10}), - ) - result = vqe.compute_minimum_eigenvalue(self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=2) - - def test_optimizer_callable(self): - """Test passing a optimizer directly as callable.""" - ansatz = RealAmplitudes(1, reps=1) - vqe = VQE(Estimator(), ansatz, _mock_optimizer) - result = vqe.compute_minimum_eigenvalue(SparsePauliOp("Z")) - self.assertTrue(np.all(result.optimal_point == np.zeros(ansatz.num_parameters))) - - def test_aux_operators_list(self): - """Test list-based aux_operators.""" - vqe = VQE(Estimator(), self.ry_wavefunction, SLSQP(maxiter=300)) - - with self.subTest("Test with an empty list."): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=[]) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertIsInstance(result.aux_operators_evaluated, list) - self.assertEqual(len(result.aux_operators_evaluated), 0) - - with self.subTest("Test with two auxiliary operators."): - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list( - [("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)] - ) - aux_ops = [aux_op1, aux_op2] - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0.0, places=6) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[1][1], dict) - - with self.subTest("Test with additional zero operator."): - extra_ops = [*aux_ops, 0] - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=extra_ops) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - self.assertEqual(len(result.aux_operators_evaluated), 3) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0.0, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[2][0], 0.0) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[1][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[2][1], dict) - - def test_aux_operators_dict(self): - """Test dictionary compatibility of aux_operators""" - vqe = VQE(Estimator(), self.ry_wavefunction, SLSQP(maxiter=300)) - - with self.subTest("Test with an empty dictionary."): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators={}) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertIsInstance(result.aux_operators_evaluated, dict) - self.assertEqual(len(result.aux_operators_evaluated), 0) - - with self.subTest("Test with two auxiliary operators."): - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list( - [("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)] - ) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operators_evaluated), 2) - - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2.0, places=5) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0.0, places=5) - # metadata - self.assertIsInstance(result.aux_operators_evaluated["aux_op1"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated["aux_op2"][1], dict) - - with self.subTest("Test with additional zero operator."): - extra_ops = {**aux_ops, "zero_operator": 0} - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=extra_ops) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operators_evaluated), 3) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2.0, places=5) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0.0, places=5) - self.assertAlmostEqual(result.aux_operators_evaluated["zero_operator"][0], 0.0) - # metadata - self.assertIsInstance(result.aux_operators_evaluated["aux_op1"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated["aux_op2"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated["zero_operator"][1], dict) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/optimizers/__init__.py b/test/python/algorithms/optimizers/__init__.py deleted file mode 100644 index c6268f028739..000000000000 --- a/test/python/algorithms/optimizers/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. - -"""Qiskit's algorithm optimizer tests.""" diff --git a/test/python/algorithms/optimizers/test_gradient_descent.py b/test/python/algorithms/optimizers/test_gradient_descent.py deleted file mode 100644 index 8d2396650176..000000000000 --- a/test/python/algorithms/optimizers/test_gradient_descent.py +++ /dev/null @@ -1,196 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# 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. - -"""Tests for the Gradient Descent optimizer.""" - -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from qiskit.algorithms.optimizers import GradientDescent, GradientDescentState -from qiskit.algorithms.optimizers.steppable_optimizer import TellData, AskData -from qiskit.circuit.library import PauliTwoDesign -from qiskit.opflow import I, Z, StateFn - - -class TestGradientDescent(QiskitAlgorithmsTestCase): - """Tests for the gradient descent optimizer.""" - - def setUp(self): - super().setUp() - np.random.seed(12) - self.initial_point = np.array([1, 1, 1, 1, 0]) - - def objective(self, x): - """Objective Function for the tests""" - return (np.linalg.norm(x) - 1) ** 2 - - def grad(self, x): - """Gradient of the objective function""" - return 2 * (np.linalg.norm(x) - 1) * x / np.linalg.norm(x) - - def test_pauli_two_design(self): - """Test standard gradient descent on the Pauli two-design example.""" - circuit = PauliTwoDesign(3, reps=3, seed=2) - parameters = list(circuit.parameters) - with self.assertWarns(DeprecationWarning): - obs = Z ^ Z ^ I - expr = ~StateFn(obs) @ StateFn(circuit) - - initial_point = np.array( - [ - 0.1822308, - -0.27254251, - 0.83684425, - 0.86153976, - -0.7111668, - 0.82766631, - 0.97867993, - 0.46136964, - 2.27079901, - 0.13382699, - 0.29589915, - 0.64883193, - ] - ) - - def objective_pauli(x): - return expr.bind_parameters(dict(zip(parameters, x))).eval().real - - optimizer = GradientDescent(maxiter=100, learning_rate=0.1, perturbation=0.1) - - with self.assertWarns(DeprecationWarning): - result = optimizer.minimize(objective_pauli, x0=initial_point) - self.assertLess(result.fun, -0.95) # final loss - self.assertEqual(result.nfev, 1300) # function evaluations - - def test_callback(self): - """Test the callback.""" - - history = [] - - def callback(*args): - history.append(args) - - optimizer = GradientDescent(maxiter=1, callback=callback) - - _ = optimizer.minimize(self.objective, np.array([1, -1])) - - self.assertEqual(len(history), 1) - self.assertIsInstance(history[0][0], int) # nfevs - self.assertIsInstance(history[0][1], np.ndarray) # parameters - self.assertIsInstance(history[0][2], float) # function value - self.assertIsInstance(history[0][3], float) # norm of the gradient - - def test_minimize(self): - """Test setting the learning rate as iterator and minimizing the funciton.""" - - def learning_rate(): - power = 0.6 - constant_coeff = 0.1 - - def powerlaw(): - n = 0 - while True: - yield constant_coeff * (n**power) - n += 1 - - return powerlaw() - - optimizer = GradientDescent(maxiter=20, learning_rate=learning_rate) - result = optimizer.minimize(self.objective, self.initial_point, self.grad) - - self.assertLess(result.fun, 1e-5) - - def test_no_start(self): - """Tests that making a step without having started the optimizer raises an error.""" - optimizer = GradientDescent() - with self.assertRaises(AttributeError): - optimizer.step() - - def test_start(self): - """Tests if the start method initializes the state properly.""" - optimizer = GradientDescent() - self.assertIsNone(optimizer.state) - self.assertIsNone(optimizer.perturbation) - optimizer.start(x0=self.initial_point, fun=self.objective) - - test_state = GradientDescentState( - x=self.initial_point, - fun=self.objective, - jac=None, - nfev=0, - njev=0, - nit=0, - learning_rate=1, - stepsize=None, - ) - - self.assertEqual(test_state, optimizer.state) - - def test_ask(self): - """Test the ask method.""" - optimizer = GradientDescent() - optimizer.start(fun=self.objective, x0=self.initial_point) - - ask_data = optimizer.ask() - np.testing.assert_equal(ask_data.x_jac, self.initial_point) - self.assertIsNone(ask_data.x_fun) - - def test_evaluate(self): - """Test the evaluate method.""" - optimizer = GradientDescent(perturbation=1e-10) - optimizer.start(fun=self.objective, x0=self.initial_point) - ask_data = AskData(x_jac=self.initial_point) - tell_data = optimizer.evaluate(ask_data=ask_data) - np.testing.assert_almost_equal(tell_data.eval_jac, self.grad(self.initial_point), decimal=2) - - def test_tell(self): - """Test the tell method.""" - optimizer = GradientDescent(learning_rate=1.0) - optimizer.start(fun=self.objective, x0=self.initial_point) - ask_data = AskData(x_jac=self.initial_point) - tell_data = TellData(eval_jac=self.initial_point) - optimizer.tell(ask_data=ask_data, tell_data=tell_data) - np.testing.assert_equal(optimizer.state.x, np.zeros(optimizer.state.x.shape)) - - def test_continue_condition(self): - """Test if the continue condition is working properly.""" - optimizer = GradientDescent(tol=1) - optimizer.start(fun=self.objective, x0=self.initial_point) - self.assertTrue(optimizer.continue_condition()) - optimizer.state.stepsize = 0.1 - self.assertFalse(optimizer.continue_condition()) - optimizer.state.stepsize = 10 - optimizer.state.nit = 1000 - self.assertFalse(optimizer.continue_condition()) - - def test_step(self): - """Tests if performing one step yields the desired result.""" - optimizer = GradientDescent(learning_rate=1.0) - optimizer.start(fun=self.objective, jac=self.grad, x0=self.initial_point) - optimizer.step() - np.testing.assert_almost_equal( - optimizer.state.x, self.initial_point - self.grad(self.initial_point), 6 - ) - - def test_wrong_dimension_gradient(self): - """Tests if an error is raised when a gradient of the wrong dimension is passed.""" - - optimizer = GradientDescent(learning_rate=1.0) - optimizer.start(fun=self.objective, x0=self.initial_point) - ask_data = AskData(x_jac=self.initial_point) - tell_data = TellData(eval_jac=np.array([1.0, 5])) - with self.assertRaises(ValueError): - optimizer.tell(ask_data=ask_data, tell_data=tell_data) - - tell_data = TellData(eval_jac=np.array(1)) - with self.assertRaises(ValueError): - optimizer.tell(ask_data=ask_data, tell_data=tell_data) diff --git a/test/python/algorithms/optimizers/test_optimizer_aqgd.py b/test/python/algorithms/optimizers/test_optimizer_aqgd.py deleted file mode 100644 index 9bef92b48cd4..000000000000 --- a/test/python/algorithms/optimizers/test_optimizer_aqgd.py +++ /dev/null @@ -1,122 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2023 -# -# 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. - -"""Test of AQGD optimizer""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit.circuit.library import RealAmplitudes -from qiskit.utils import QuantumInstance, algorithm_globals, optionals -from qiskit.opflow import PauliSumOp -from qiskit.algorithms.optimizers import AQGD -from qiskit.algorithms import VQE, AlgorithmError -from qiskit.opflow.gradients import Gradient -from qiskit.test import slow_test - - -class TestOptimizerAQGD(QiskitAlgorithmsTestCase): - """Test AQGD optimizer using RY for analytic gradient with VQE""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 50 - with self.assertWarns(DeprecationWarning): - self.qubit_op = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - - @slow_test - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") - def test_simple(self): - """test AQGD optimizer with the parameters as single values.""" - from qiskit_aer import Aer - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - Aer.get_backend("aer_simulator_statevector"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - aqgd = AQGD(momentum=0.0) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=RealAmplitudes(), - optimizer=aqgd, - gradient=Gradient("lin_comb"), - quantum_instance=q_instance, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") - def test_list(self): - """test AQGD optimizer with the parameters as lists.""" - from qiskit_aer import Aer - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - Aer.get_backend("aer_simulator_statevector"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - aqgd = AQGD(maxiter=[1000, 1000, 1000], eta=[1.0, 0.5, 0.3], momentum=[0.0, 0.5, 0.75]) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=RealAmplitudes(), optimizer=aqgd, quantum_instance=q_instance) - result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) - - def test_raises_exception(self): - """tests that AQGD raises an exception when incorrect values are passed.""" - self.assertRaises(AlgorithmError, AQGD, maxiter=[1000], eta=[1.0, 0.5], momentum=[0.0, 0.5]) - - @slow_test - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") - def test_int_values(self): - """test AQGD with int values passed as eta and momentum.""" - from qiskit_aer import Aer - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - Aer.get_backend("aer_simulator_statevector"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - aqgd = AQGD(maxiter=1000, eta=1, momentum=0) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=RealAmplitudes(), - optimizer=aqgd, - gradient=Gradient("lin_comb"), - quantum_instance=q_instance, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/optimizers/test_optimizer_nft.py b/test/python/algorithms/optimizers/test_optimizer_nft.py deleted file mode 100644 index e904c3bffd91..000000000000 --- a/test/python/algorithms/optimizers/test_optimizer_nft.py +++ /dev/null @@ -1,64 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# 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. - -"""Test of NFT optimizer""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit import BasicAer -from qiskit.circuit.library import RealAmplitudes -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.opflow import PauliSumOp -from qiskit.algorithms.optimizers import NFT -from qiskit.algorithms import VQE - - -class TestOptimizerNFT(QiskitAlgorithmsTestCase): - """Test NFT optimizer using RY with VQE""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 50 - with self.assertWarns(DeprecationWarning): - self.qubit_op = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - - def test_nft(self): - """Test NFT optimizer by using it""" - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=RealAmplitudes(), - optimizer=NFT(), - quantum_instance=QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ), - ) - result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.857275, places=6) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/optimizers/test_optimizers.py b/test/python/algorithms/optimizers/test_optimizers.py deleted file mode 100644 index f0e759ce4e4e..000000000000 --- a/test/python/algorithms/optimizers/test_optimizers.py +++ /dev/null @@ -1,404 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# 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. - -"""Test Optimizers""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from typing import Optional, List, Tuple -from ddt import ddt, data, unpack -import numpy as np -from scipy.optimize import rosen, rosen_der - -from qiskit.algorithms.optimizers import ( - ADAM, - AQGD, - BOBYQA, - IMFIL, - CG, - CRS, - COBYLA, - DIRECT_L, - DIRECT_L_RAND, - GSLS, - GradientDescent, - L_BFGS_B, - NELDER_MEAD, - Optimizer, - P_BFGS, - POWELL, - SLSQP, - SPSA, - QNSPSA, - TNC, - SciPyOptimizer, -) -from qiskit.circuit.library import RealAmplitudes -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.utils import algorithm_globals, optionals - - -@ddt -class TestOptimizers(QiskitAlgorithmsTestCase): - """Test Optimizers""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 52 - - def run_optimizer( - self, - optimizer: Optimizer, - max_nfev: int, - grad: bool = False, - bounds: Optional[List[Tuple[float, float]]] = None, - ): - """Test the optimizer. - - Args: - optimizer: The optimizer instance to test. - max_nfev: The maximal allowed number of function evaluations. - grad: Whether to pass the gradient function as input. - bounds: Optimizer bounds. - """ - x_0 = [1.3, 0.7, 0.8, 1.9, 1.2] - jac = rosen_der if grad else None - - res = optimizer.minimize(rosen, x_0, jac, bounds) - x_opt = res.x - nfev = res.nfev - - np.testing.assert_array_almost_equal(x_opt, [1.0] * len(x_0), decimal=2) - self.assertLessEqual(nfev, max_nfev) - - def test_adam(self): - """adam test""" - optimizer = ADAM(maxiter=10000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_cg(self): - """cg test""" - optimizer = CG(maxiter=1000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_gradient_descent(self): - """cg test""" - optimizer = GradientDescent(maxiter=100000, tol=1e-06, learning_rate=1e-3) - self.run_optimizer(optimizer, grad=True, max_nfev=100000) - - def test_cobyla(self): - """cobyla test""" - optimizer = COBYLA(maxiter=100000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=100000) - - def test_l_bfgs_b(self): - """l_bfgs_b test""" - optimizer = L_BFGS_B(maxfun=1000) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_p_bfgs(self): - """parallel l_bfgs_b test""" - optimizer = P_BFGS(maxfun=1000, max_processes=4) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_nelder_mead(self): - """nelder mead test""" - optimizer = NELDER_MEAD(maxfev=10000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_powell(self): - """powell test""" - optimizer = POWELL(maxfev=10000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_slsqp(self): - """slsqp test""" - optimizer = SLSQP(maxiter=1000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - @unittest.skip("Skipping SPSA as it does not do well on non-convex rozen") - def test_spsa(self): - """spsa test""" - optimizer = SPSA(maxiter=10000) - self.run_optimizer(optimizer, max_nfev=100000) - - def test_tnc(self): - """tnc test""" - optimizer = TNC(maxiter=1000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_gsls(self): - """gsls test""" - optimizer = GSLS( - sample_size_factor=40, - sampling_radius=1.0e-12, - maxiter=10000, - max_eval=10000, - min_step_size=1.0e-12, - ) - x_0 = [1.3, 0.7, 0.8, 1.9, 1.2] - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 1 - res = optimizer.minimize(rosen, x_0) - x_value = res.fun - n_evals = res.nfev - - # Ensure value is near-optimal - self.assertLessEqual(x_value, 0.01) - self.assertLessEqual(n_evals, 10000) - - def test_scipy_optimizer(self): - """scipy_optimizer test""" - optimizer = SciPyOptimizer("BFGS", options={"maxiter": 1000}) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_scipy_optimizer_callback(self): - """scipy_optimizer callback test""" - values = [] - - def callback(x): - values.append(x) - - optimizer = SciPyOptimizer("BFGS", options={"maxiter": 1000}, callback=callback) - self.run_optimizer(optimizer, max_nfev=10000) - self.assertTrue(values) # Check the list is nonempty. - - # ESCH and ISRES do not do well with rosen - @data( - (CRS, True), - (DIRECT_L, True), - (DIRECT_L_RAND, True), - (CRS, False), - (DIRECT_L, False), - (DIRECT_L_RAND, False), - ) - @unpack - def test_nlopt(self, optimizer_cls, use_bound): - """NLopt test""" - bounds = [(-6, 6)] * 5 if use_bound else None - try: - optimizer = optimizer_cls() - optimizer.set_options(**{"max_evals": 50000}) - self.run_optimizer(optimizer, max_nfev=50000, bounds=bounds) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - -@ddt -class TestOptimizerSerialization(QiskitAlgorithmsTestCase): - """Tests concerning the serialization of optimizers.""" - - @data( - ("BFGS", {"maxiter": 100, "eps": np.array([0.1])}), - ("CG", {"maxiter": 200, "gtol": 1e-8}), - ("COBYLA", {"maxiter": 10}), - ("L_BFGS_B", {"maxiter": 30}), - ("NELDER_MEAD", {"maxiter": 0}), - ("NFT", {"maxiter": 100}), - ("P_BFGS", {"maxiter": 5}), - ("POWELL", {"maxiter": 1}), - ("SLSQP", {"maxiter": 400}), - ("TNC", {"maxiter": 20}), - ("dogleg", {"maxiter": 100}), - ("trust-constr", {"maxiter": 10}), - ("trust-ncg", {"maxiter": 100}), - ("trust-exact", {"maxiter": 120}), - ("trust-krylov", {"maxiter": 150}), - ) - @unpack - def test_scipy(self, method, options): - """Test the SciPyOptimizer is serializable.""" - - optimizer = SciPyOptimizer(method, options=options) - serialized = optimizer.settings - from_dict = SciPyOptimizer(**serialized) - - self.assertEqual(from_dict._method, method.lower()) - self.assertEqual(from_dict._options, options) - - def test_independent_reconstruction(self): - """Test the SciPyOptimizers don't reset all settings upon creating a new instance. - - COBYLA is used as representative example here.""" - - kwargs = {"coffee": "without sugar"} - options = {"tea": "with milk"} - optimizer = COBYLA(maxiter=1, options=options, **kwargs) - serialized = optimizer.settings - from_dict = COBYLA(**serialized) - - with self.subTest(msg="test attributes"): - self.assertEqual(from_dict.settings["maxiter"], 1) - - with self.subTest(msg="test options"): - # options should only contain values that are *not* already in the initializer - # (e.g. should not contain maxiter) - self.assertEqual(from_dict.settings["options"], {"tea": "with milk"}) - - with self.subTest(msg="test kwargs"): - self.assertEqual(from_dict.settings["coffee"], "without sugar") - - with self.subTest(msg="option ids differ"): - self.assertNotEqual(id(serialized["options"]), id(from_dict.settings["options"])) - - def test_adam(self): - """Test ADAM is serializable.""" - - adam = ADAM(maxiter=100, amsgrad=True) - settings = adam.settings - - self.assertEqual(settings["maxiter"], 100) - self.assertTrue(settings["amsgrad"]) - - def test_aqgd(self): - """Test AQGD is serializable.""" - - opt = AQGD(maxiter=[200, 100], eta=[0.2, 0.1], momentum=[0.25, 0.1]) - settings = opt.settings - - self.assertListEqual(settings["maxiter"], [200, 100]) - self.assertListEqual(settings["eta"], [0.2, 0.1]) - self.assertListEqual(settings["momentum"], [0.25, 0.1]) - - @unittest.skipIf(not optionals.HAS_SKQUANT, "Install scikit-quant to run this test.") - def test_bobyqa(self): - """Test BOBYQA is serializable.""" - - opt = BOBYQA(maxiter=200) - settings = opt.settings - - self.assertEqual(settings["maxiter"], 200) - - @unittest.skipIf(not optionals.HAS_SKQUANT, "Install scikit-quant to run this test.") - def test_imfil(self): - """Test IMFIL is serializable.""" - - opt = IMFIL(maxiter=200) - settings = opt.settings - - self.assertEqual(settings["maxiter"], 200) - - def test_gradient_descent(self): - """Test GradientDescent is serializable.""" - - opt = GradientDescent(maxiter=10, learning_rate=0.01) - settings = opt.settings - - self.assertEqual(settings["maxiter"], 10) - self.assertEqual(settings["learning_rate"], 0.01) - - def test_gsls(self): - """Test GSLS is serializable.""" - - opt = GSLS(maxiter=100, sampling_radius=1e-3) - settings = opt.settings - - self.assertEqual(settings["maxiter"], 100) - self.assertEqual(settings["sampling_radius"], 1e-3) - - def test_spsa(self): - """Test SPSA optimizer is serializable.""" - options = { - "maxiter": 100, - "blocking": True, - "allowed_increase": 0.1, - "second_order": True, - "learning_rate": 0.02, - "perturbation": 0.05, - "regularization": 0.1, - "resamplings": 2, - "perturbation_dims": 5, - "trust_region": False, - "initial_hessian": None, - "lse_solver": None, - "hessian_delay": 0, - "callback": None, - "termination_checker": None, - } - spsa = SPSA(**options) - - self.assertDictEqual(spsa.settings, options) - - def test_spsa_custom_iterators(self): - """Test serialization works with custom iterators for learning rate and perturbation.""" - rate = 0.99 - - def powerlaw(): - n = 0 - while True: - yield rate**n - n += 1 - - def steps(): - n = 1 - divide_after = 20 - epsilon = 0.5 - while True: - yield epsilon - n += 1 - if n % divide_after == 0: - epsilon /= 2 - - learning_rate = powerlaw() - expected_learning_rate = np.array([next(learning_rate) for _ in range(200)]) - - perturbation = steps() - expected_perturbation = np.array([next(perturbation) for _ in range(200)]) - - spsa = SPSA(maxiter=200, learning_rate=powerlaw, perturbation=steps) - settings = spsa.settings - - self.assertTrue(np.allclose(settings["learning_rate"], expected_learning_rate)) - self.assertTrue(np.allclose(settings["perturbation"], expected_perturbation)) - - def test_qnspsa(self): - """Test QN-SPSA optimizer is serializable.""" - ansatz = RealAmplitudes(1) - fidelity = QNSPSA.get_fidelity(ansatz) - options = { - "fidelity": fidelity, - "maxiter": 100, - "blocking": True, - "allowed_increase": 0.1, - "learning_rate": 0.02, - "perturbation": 0.05, - "regularization": 0.1, - "resamplings": 2, - "perturbation_dims": 5, - "lse_solver": None, - "initial_hessian": None, - "callback": None, - "termination_checker": None, - "hessian_delay": 0, - } - spsa = QNSPSA(**options) - - settings = spsa.settings - expected = options.copy() - - with self.subTest(msg="check constructed dictionary"): - self.assertDictEqual(settings, expected) - - reconstructed = QNSPSA(**settings) # pylint: disable=unexpected-keyword-arg - with self.subTest(msg="test reconstructed optimizer"): - self.assertDictEqual(reconstructed.settings, expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/optimizers/test_optimizers_scikitquant.py b/test/python/algorithms/optimizers/test_optimizers_scikitquant.py deleted file mode 100644 index f3d3f645d895..000000000000 --- a/test/python/algorithms/optimizers/test_optimizers_scikitquant.py +++ /dev/null @@ -1,118 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# 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. - -"""Test of scikit-quant optimizers.""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from ddt import ddt, data, unpack - -import numpy -from qiskit import BasicAer -from qiskit.circuit.library import RealAmplitudes -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.opflow import PauliSumOp -from qiskit.algorithms import VQE -from qiskit.algorithms.optimizers import BOBYQA, SNOBFIT, IMFIL - - -@ddt -class TestOptimizers(QiskitAlgorithmsTestCase): - """Test scikit-quant optimizers.""" - - def setUp(self): - """Set the problem.""" - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 50 - with self.assertWarns(DeprecationWarning): - self.qubit_op = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - - def _optimize(self, optimizer): - """launch vqe""" - with self.assertWarns(DeprecationWarning): - qe = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=RealAmplitudes(), optimizer=optimizer, quantum_instance=qe) - result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=1) - - def test_bobyqa(self): - """BOBYQA optimizer test.""" - try: - optimizer = BOBYQA(maxiter=150) - self._optimize(optimizer) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - @unittest.skipIf( - # NB: numpy.__version__ may contain letters, e.g. "1.26.0b1" - tuple(map(int, numpy.__version__.split(".")[:2])) >= (1, 24), - "scikit's SnobFit currently incompatible with NumPy 1.24.0.", - ) - def test_snobfit(self): - """SNOBFIT optimizer test.""" - try: - optimizer = SNOBFIT(maxiter=100, maxfail=100, maxmp=20) - self._optimize(optimizer) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - @unittest.skipIf( - # NB: numpy.__version__ may contain letters, e.g. "1.26.0b1" - tuple(map(int, numpy.__version__.split(".")[:2])) >= (1, 24), - "scikit's SnobFit currently incompatible with NumPy 1.24.0.", - ) - @data((None,), ([(-1, 1), (None, None)],)) - @unpack - def test_snobfit_missing_bounds(self, bounds): - """SNOBFIT optimizer test with missing bounds.""" - try: - optimizer = SNOBFIT() - with self.assertRaises(ValueError): - optimizer.minimize( - fun=lambda _: 1, # using dummy function (never called) - x0=[0.1, 0.1], # dummy initial point - bounds=bounds, - ) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - def test_imfil(self): - """IMFIL test.""" - try: - optimizer = IMFIL(maxiter=100) - self._optimize(optimizer) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/optimizers/test_spsa.py b/test/python/algorithms/optimizers/test_spsa.py deleted file mode 100644 index dafa8c44b435..000000000000 --- a/test/python/algorithms/optimizers/test_spsa.py +++ /dev/null @@ -1,293 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# 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. - -"""Tests for the SPSA optimizer.""" - -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt, data - -import numpy as np - -from qiskit.algorithms.optimizers import SPSA, QNSPSA -from qiskit.circuit.library import PauliTwoDesign -from qiskit.primitives import Estimator, Sampler -from qiskit.providers.basicaer import StatevectorSimulatorPy -from qiskit.opflow import I, Z, StateFn, MatrixExpectation -from qiskit.utils import algorithm_globals - - -@ddt -class TestSPSA(QiskitAlgorithmsTestCase): - """Tests for the SPSA optimizer.""" - - def setUp(self): - super().setUp() - np.random.seed(12) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 12 - - # @slow_test - @data("spsa", "2spsa", "qnspsa") - def test_pauli_two_design(self, method): - """Test SPSA on the Pauli two-design example.""" - circuit = PauliTwoDesign(3, reps=1, seed=1) - parameters = list(circuit.parameters) - with self.assertWarns(DeprecationWarning): - obs = Z ^ Z ^ I - expr = ~StateFn(obs) @ StateFn(circuit) - - initial_point = np.array( - [0.82311034, 0.02611798, 0.21077064, 0.61842177, 0.09828447, 0.62013131] - ) - - def objective(x): - return expr.bind_parameters(dict(zip(parameters, x))).eval().real - - settings = {"maxiter": 100, "blocking": True, "allowed_increase": 0} - - if method == "2spsa": - settings["second_order"] = True - settings["regularization"] = 0.01 - expected_nfev = settings["maxiter"] * 5 + 1 - elif method == "qnspsa": - settings["fidelity"] = QNSPSA.get_fidelity(circuit) - settings["regularization"] = 0.001 - settings["learning_rate"] = 0.05 - settings["perturbation"] = 0.05 - - expected_nfev = settings["maxiter"] * 7 + 1 - else: - expected_nfev = settings["maxiter"] * 3 + 1 - - if method == "qnspsa": - spsa = QNSPSA(**settings) - else: - spsa = SPSA(**settings) - - with self.assertWarns(DeprecationWarning): - result = spsa.optimize(circuit.num_parameters, objective, initial_point=initial_point) - - with self.subTest("check final accuracy"): - self.assertLess(result[1], -0.95) # final loss - - with self.subTest("check number of function calls"): - self.assertEqual(result[2], expected_nfev) # function evaluations - - def test_recalibrate_at_optimize(self): - """Test SPSA calibrates anew upon each optimization run, if no autocalibration is set.""" - - def objective(x): - return -(x**2) - - spsa = SPSA(maxiter=1) - _ = spsa.minimize(objective, x0=np.array([0.5])) - - self.assertIsNone(spsa.learning_rate) - self.assertIsNone(spsa.perturbation) - - def test_learning_rate_perturbation_as_iterators(self): - """Test the learning rate and perturbation can be callables returning iterators.""" - - def get_learning_rate(): - def learning_rate(): - x = 0.99 - while True: - x *= x - yield x - - return learning_rate - - def get_perturbation(): - def perturbation(): - x = 0.99 - while True: - x *= x - yield max(x, 0.01) - - return perturbation - - def objective(x): - return (np.linalg.norm(x) - 2) ** 2 - - spsa = SPSA(learning_rate=get_learning_rate(), perturbation=get_perturbation()) - result = spsa.minimize(objective, np.array([0.5, 0.5])) - - self.assertAlmostEqual(np.linalg.norm(result.x), 2, places=2) - - def test_learning_rate_perturbation_as_arrays(self): - """Test the learning rate and perturbation can be arrays.""" - - learning_rate = np.linspace(1, 0, num=100, endpoint=False) ** 2 - perturbation = 0.01 * np.ones(100) - - def objective(x): - return (np.linalg.norm(x) - 2) ** 2 - - spsa = SPSA(learning_rate=learning_rate, perturbation=perturbation) - result = spsa.minimize(objective, x0=np.array([0.5, 0.5])) - - self.assertAlmostEqual(np.linalg.norm(result.x), 2, places=2) - - def test_termination_checker(self): - """Test the termination_callback""" - - def objective(x): - return np.linalg.norm(x) + np.random.rand(1) - - class TerminationChecker: - """Example termination checker""" - - def __init__(self): - self.values = [] - - def __call__(self, nfev, point, fvalue, stepsize, accepted) -> bool: - self.values.append(fvalue) - - if len(self.values) > 10: - return True - return False - - maxiter = 400 - spsa = SPSA(maxiter=maxiter, termination_checker=TerminationChecker()) - result = spsa.minimize(objective, x0=[0.5, 0.5]) - - self.assertLess(result.nit, maxiter) - - def test_callback(self): - """Test using the callback.""" - - def objective(x): - return (np.linalg.norm(x) - 2) ** 2 - - history = {"nfevs": [], "points": [], "fvals": [], "updates": [], "accepted": []} - - def callback(nfev, point, fval, update, accepted): - history["nfevs"].append(nfev) - history["points"].append(point) - history["fvals"].append(fval) - history["updates"].append(update) - history["accepted"].append(accepted) - - maxiter = 10 - spsa = SPSA(maxiter=maxiter, learning_rate=0.01, perturbation=0.01, callback=callback) - _ = spsa.minimize(objective, x0=np.array([0.5, 0.5])) - - expected_types = [int, np.ndarray, float, float, bool] - for i, (key, values) in enumerate(history.items()): - self.assertTrue(all(isinstance(value, expected_types[i]) for value in values)) - self.assertEqual(len(history[key]), maxiter) - - @data(1, 2, 3, 4) - def test_estimate_stddev(self, max_evals_grouped): - """Test the estimate_stddev - See https://github.com/Qiskit/qiskit-nature/issues/797""" - - def objective(x): - if len(x.shape) == 2: - return np.array([sum(x_i) for x_i in x]) - return sum(x) - - point = np.ones(5) - result = SPSA.estimate_stddev(objective, point, avg=10, max_evals_grouped=max_evals_grouped) - self.assertAlmostEqual(result, 0) - - def test_qnspsa_fidelity_deprecation(self): - """Test using a backend and expectation converter in get_fidelity warns.""" - ansatz = PauliTwoDesign(2, reps=1, seed=2) - - with self.assertWarns(DeprecationWarning): - QNSPSA.get_fidelity(ansatz, backend=StatevectorSimulatorPy()) - with self.assertWarns(DeprecationWarning): - QNSPSA.get_fidelity(ansatz, expectation=MatrixExpectation()) - - # No warning when used correctly. - QNSPSA.get_fidelity(ansatz) - - def test_qnspsa_fidelity_primitives(self): - """Test the primitives can be used in get_fidelity.""" - ansatz = PauliTwoDesign(2, reps=1, seed=2) - initial_point = np.random.random(ansatz.num_parameters) - - with self.subTest(msg="pass as kwarg"): - fidelity = QNSPSA.get_fidelity(ansatz, sampler=Sampler()) - result = fidelity(initial_point, initial_point) - - self.assertAlmostEqual(result[0], 1) - - # this test can be removed once backend and expectation are removed - with self.subTest(msg="pass positionally"): - fidelity = QNSPSA.get_fidelity(ansatz, Sampler()) - result = fidelity(initial_point, initial_point) - - self.assertAlmostEqual(result[0], 1) - - def test_qnspsa_max_evals_grouped(self): - """Test using max_evals_grouped with QNSPSA.""" - circuit = PauliTwoDesign(3, reps=1, seed=1) - num_parameters = circuit.num_parameters - - with self.assertWarns(DeprecationWarning): - obs = Z ^ Z ^ I - - estimator = Estimator(options={"seed": 12}) - - initial_point = np.array( - [0.82311034, 0.02611798, 0.21077064, 0.61842177, 0.09828447, 0.62013131] - ) - - def objective(x): - x = np.reshape(x, (-1, num_parameters)).tolist() - n = len(x) - return estimator.run(n * [circuit], n * [obs.primitive], x).result().values.real - - fidelity = QNSPSA.get_fidelity(circuit) - optimizer = QNSPSA(fidelity) - optimizer.maxiter = 1 - optimizer.learning_rate = 0.05 - optimizer.perturbation = 0.05 - optimizer.set_max_evals_grouped(50) # greater than 1 - - result = optimizer.minimize(objective, initial_point) - - with self.subTest("check final accuracy"): - self.assertAlmostEqual(result.fun[0], 0.473, places=3) - - with self.subTest("check number of function calls"): - expected_nfev = 8 # 7 * maxiter + 1 - self.assertEqual(result.nfev, expected_nfev) - - def test_point_sample(self): - """Test point sample function in QNSPSA""" - - def fidelity(x, _y): - x = np.asarray(x) - return np.ones_like(x, dtype=float) # some float - - def objective(x): - return x - - def get_perturbation(): - def perturbation(): - while True: - yield 1 - - return perturbation - - qnspsa = QNSPSA(fidelity, maxiter=1, learning_rate=0.1, perturbation=get_perturbation()) - initial_point = 1.0 - result = qnspsa.minimize(objective, initial_point) - - expected_nfev = 8 # 7 * maxiter + 1 - self.assertEqual(result.nfev, expected_nfev) diff --git a/test/python/algorithms/optimizers/test_umda.py b/test/python/algorithms/optimizers/test_umda.py deleted file mode 100644 index 26952881de88..000000000000 --- a/test/python/algorithms/optimizers/test_umda.py +++ /dev/null @@ -1,98 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. - -"""Tests for the UMDA optimizer.""" - -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from scipy.optimize import rosen - -from qiskit.algorithms.optimizers.umda import UMDA -from qiskit.utils import algorithm_globals - - -class TestUMDA(QiskitAlgorithmsTestCase): - """Tests for the UMDA optimizer.""" - - def test_get_set(self): - """Test if getters and setters work as expected""" - umda = UMDA(maxiter=1, size_gen=20) - umda.disp = True - umda.size_gen = 30 - umda.alpha = 0.6 - umda.maxiter = 100 - - self.assertTrue(umda.disp) - self.assertEqual(umda.size_gen, 30) - self.assertEqual(umda.alpha, 0.6) - self.assertEqual(umda.maxiter, 100) - - def test_settings(self): - """Test if the settings display works well""" - umda = UMDA(maxiter=1, size_gen=20) - umda.disp = True - umda.size_gen = 30 - umda.alpha = 0.6 - umda.maxiter = 100 - - set_ = { - "maxiter": 100, - "alpha": 0.6, - "size_gen": 30, - "callback": None, - } - - self.assertEqual(umda.settings, set_) - - def test_minimize(self): - """optimize function test""" - # UMDA is volatile so we need to set the seeds for the execution - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 52 - - optimizer = UMDA(maxiter=1000, size_gen=100) - x_0 = [1.3, 0.7, 1.5] - res = optimizer.minimize(rosen, x_0) - - self.assertIsNotNone(res.fun) - self.assertEqual(len(res.x), len(x_0)) - np.testing.assert_array_almost_equal(res.x, [1.0] * len(x_0), decimal=2) - - def test_callback(self): - """Test the callback.""" - - def objective(x): - return np.linalg.norm(x) - 1 - - nfevs, parameters, fvals = [], [], [] - - def store_history(*args): - nfevs.append(args[0]) - parameters.append(args[1]) - fvals.append(args[2]) - - optimizer = UMDA(maxiter=1, callback=store_history) - _ = optimizer.minimize(objective, x0=np.arange(5)) - - self.assertEqual(len(nfevs), 1) - self.assertIsInstance(nfevs[0], int) - - self.assertEqual(len(parameters), 1) - self.assertIsInstance(parameters[0], np.ndarray) - self.assertEqual(parameters[0].size, 5) - - self.assertEqual(len(fvals), 1) - self.assertIsInstance(fvals[0], float) diff --git a/test/python/algorithms/optimizers/utils/__init__.py b/test/python/algorithms/optimizers/utils/__init__.py deleted file mode 100644 index f3adc3e3b4da..000000000000 --- a/test/python/algorithms/optimizers/utils/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -"""Tests for Optimizer Utils.""" diff --git a/test/python/algorithms/optimizers/utils/test_learning_rate.py b/test/python/algorithms/optimizers/utils/test_learning_rate.py deleted file mode 100644 index 52acdbf98aaa..000000000000 --- a/test/python/algorithms/optimizers/utils/test_learning_rate.py +++ /dev/null @@ -1,54 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Tests for LearningRate.""" - -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from qiskit.algorithms.optimizers.optimizer_utils import LearningRate - - -class TestLearningRate(QiskitAlgorithmsTestCase): - """Tests for the LearningRate class.""" - - def setUp(self): - super().setUp() - np.random.seed(12) - self.initial_point = np.array([1, 1, 1, 1, 0]) - - def objective(self, x): - """Objective Function for the tests""" - return (np.linalg.norm(x) - 1) ** 2 - - def test_learning_rate(self): - """ - Tests if the learning rate is initialized properly for each kind of input: - float, list and iterator. - """ - constant_learning_rate_input = 0.01 - list_learning_rate_input = [0.01 * n for n in range(10)] - generator_learning_rate_input = lambda: (el for el in list_learning_rate_input) - - with self.subTest("Check constant learning rate."): - constant_learning_rate = LearningRate(learning_rate=constant_learning_rate_input) - for _ in range(5): - self.assertEqual(constant_learning_rate_input, next(constant_learning_rate)) - - with self.subTest("Check learning rate list."): - list_learning_rate = LearningRate(learning_rate=list_learning_rate_input) - for i in range(5): - self.assertEqual(list_learning_rate_input[i], next(list_learning_rate)) - - with self.subTest("Check learning rate generator."): - generator_learning_rate = LearningRate(generator_learning_rate_input) - for i in range(5): - self.assertEqual(list_learning_rate_input[i], next(generator_learning_rate)) diff --git a/test/python/algorithms/state_fidelities/__init__.py b/test/python/algorithms/state_fidelities/__init__.py deleted file mode 100644 index d8b7d587c4cc..000000000000 --- a/test/python/algorithms/state_fidelities/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Tests for the primitive-based fidelity interfaces.""" diff --git a/test/python/algorithms/state_fidelities/test_compute_uncompute.py b/test/python/algorithms/state_fidelities/test_compute_uncompute.py deleted file mode 100644 index f3b106d9b5d7..000000000000 --- a/test/python/algorithms/state_fidelities/test_compute_uncompute.py +++ /dev/null @@ -1,264 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Tests for Fidelity.""" - -import unittest - -import numpy as np - -from qiskit.circuit import QuantumCircuit, ParameterVector -from qiskit.circuit.library import RealAmplitudes -from qiskit.primitives import Sampler -from qiskit.algorithms.state_fidelities import ComputeUncompute -from qiskit.test import QiskitTestCase - - -class TestComputeUncompute(QiskitTestCase): - """Test Compute-Uncompute Fidelity class""" - - def setUp(self): - super().setUp() - parameters = ParameterVector("x", 2) - - rx_rotations = QuantumCircuit(2) - rx_rotations.rx(parameters[0], 0) - rx_rotations.rx(parameters[1], 1) - - ry_rotations = QuantumCircuit(2) - ry_rotations.ry(parameters[0], 0) - ry_rotations.ry(parameters[1], 1) - - plus = QuantumCircuit(2) - plus.h([0, 1]) - - zero = QuantumCircuit(2) - - rx_rotation = QuantumCircuit(2) - rx_rotation.rx(parameters[0], 0) - rx_rotation.h(1) - - self._circuit = [rx_rotations, ry_rotations, plus, zero, rx_rotation] - self._sampler = Sampler() - self._left_params = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) - self._right_params = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) - - def test_1param_pair(self): - """test for fidelity with one pair of parameters""" - fidelity = ComputeUncompute(self._sampler) - job = fidelity.run( - self._circuit[0], self._circuit[1], self._left_params[0], self._right_params[0] - ) - result = job.result() - np.testing.assert_allclose(result.fidelities, np.array([1.0])) - - def test_1param_pair_local(self): - """test for fidelity with one pair of parameters""" - fidelity = ComputeUncompute(self._sampler, local=True) - job = fidelity.run( - self._circuit[0], self._circuit[1], self._left_params[0], self._right_params[0] - ) - result = job.result() - np.testing.assert_allclose(result.fidelities, np.array([1.0])) - - def test_local(self): - """test difference between local and global fidelity""" - fidelity_global = ComputeUncompute(self._sampler, local=False) - fidelity_local = ComputeUncompute(self._sampler, local=True) - fidelities = [] - for fidelity in [fidelity_global, fidelity_local]: - job = fidelity.run(self._circuit[2], self._circuit[3]) - result = job.result() - fidelities.append(result.fidelities[0]) - np.testing.assert_allclose(fidelities, np.array([0.25, 0.5]), atol=1e-16) - - def test_4param_pairs(self): - """test for fidelity with four pairs of parameters""" - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - job = fidelity.run( - [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params - ) - results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) - - def test_symmetry(self): - """test for fidelity with the same circuit""" - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - job_1 = fidelity.run( - [self._circuit[0]] * n, [self._circuit[0]] * n, self._left_params, self._right_params - ) - job_2 = fidelity.run( - [self._circuit[0]] * n, [self._circuit[0]] * n, self._right_params, self._left_params - ) - results_1 = job_1.result() - results_2 = job_2.result() - np.testing.assert_allclose(results_1.fidelities, results_2.fidelities, atol=1e-16) - - def test_no_params(self): - """test for fidelity without parameters""" - fidelity = ComputeUncompute(self._sampler) - job = fidelity.run([self._circuit[2]], [self._circuit[3]]) - results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([0.25]), atol=1e-16) - - job = fidelity.run([self._circuit[2]], [self._circuit[3]], [], []) - results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([0.25]), atol=1e-16) - - def test_left_param(self): - """test for fidelity with only left parameters""" - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - job = fidelity.run( - [self._circuit[1]] * n, [self._circuit[3]] * n, values_1=self._left_params - ) - results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) - - def test_right_param(self): - """test for fidelity with only right parameters""" - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - job = fidelity.run( - [self._circuit[3]] * n, [self._circuit[1]] * n, values_2=self._left_params - ) - results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) - - def test_not_set_circuits(self): - """test for fidelity with no circuits.""" - fidelity = ComputeUncompute(self._sampler) - with self.assertRaises(TypeError): - job = fidelity.run( - circuits_1=None, - circuits_2=None, - values_1=self._left_params, - values_2=self._right_params, - ) - job.result() - - def test_circuit_mismatch(self): - """test for fidelity with different number of left/right circuits.""" - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - with self.assertRaises(ValueError): - job = fidelity.run( - [self._circuit[0]] * n, - [self._circuit[1]] * (n + 1), - self._left_params, - self._right_params, - ) - job.result() - - def test_asymmetric_params(self): - """test for fidelity when the 2 circuits have different number of - left/right parameters.""" - - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - right_params = [[p] for p in self._right_params[:, 0]] - job = fidelity.run( - [self._circuit[0]] * n, [self._circuit[4]] * n, self._left_params, right_params - ) - result = job.result() - np.testing.assert_allclose(result.fidelities, np.array([0.5, 0.25, 0.25, 0.0]), atol=1e-16) - - def test_input_format(self): - """test for different input format variations""" - - fidelity = ComputeUncompute(self._sampler) - circuit = RealAmplitudes(2) - values = np.random.random(circuit.num_parameters) - shift = np.ones_like(values) * 0.01 - - # lists of circuits, lists of numpy arrays - job = fidelity.run([circuit], [circuit], [values], [values + shift]) - result_1 = job.result() - - # lists of circuits, lists of lists - shift_val = values + shift - job = fidelity.run([circuit], [circuit], [values.tolist()], [shift_val.tolist()]) - result_2 = job.result() - - # circuits, lists - shift_val = values + shift - job = fidelity.run(circuit, circuit, values.tolist(), shift_val.tolist()) - result_3 = job.result() - - # circuits, np.arrays - job = fidelity.run(circuit, circuit, values, values + shift) - result_4 = job.result() - - np.testing.assert_allclose(result_1.fidelities, result_2.fidelities, atol=1e-16) - np.testing.assert_allclose(result_1.fidelities, result_3.fidelities, atol=1e-16) - np.testing.assert_allclose(result_1.fidelities, result_4.fidelities, atol=1e-16) - - def test_input_measurements(self): - """test for fidelity with measurements on input circuits""" - fidelity = ComputeUncompute(self._sampler) - circuit_1 = self._circuit[0] - circuit_1.measure_all() - circuit_2 = self._circuit[1] - circuit_2.measure_all() - - job = fidelity.run(circuit_1, circuit_2, self._left_params[0], self._right_params[0]) - result = job.result() - np.testing.assert_allclose(result.fidelities, np.array([1.0])) - - def test_options(self): - """Test fidelity's run options""" - sampler_shots = Sampler(options={"shots": 1024}) - - with self.subTest("sampler"): - # Only options in sampler - fidelity = ComputeUncompute(sampler_shots) - options = fidelity.options - job = fidelity.run(self._circuit[2], self._circuit[3]) - result = job.result() - self.assertEqual(options.__dict__, {"shots": 1024}) - self.assertEqual(result.options.__dict__, {"shots": 1024}) - - with self.subTest("fidelity init"): - # Fidelity default options override sampler - # options and add new fields - fidelity = ComputeUncompute(sampler_shots, options={"shots": 2048, "dummy": 100}) - options = fidelity.options - job = fidelity.run(self._circuit[2], self._circuit[3]) - result = job.result() - self.assertEqual(options.__dict__, {"shots": 2048, "dummy": 100}) - self.assertEqual(result.options.__dict__, {"shots": 2048, "dummy": 100}) - - with self.subTest("fidelity update"): - # Update fidelity options - fidelity = ComputeUncompute(sampler_shots, options={"shots": 2048, "dummy": 100}) - fidelity.update_default_options(shots=100) - options = fidelity.options - job = fidelity.run(self._circuit[2], self._circuit[3]) - result = job.result() - self.assertEqual(options.__dict__, {"shots": 100, "dummy": 100}) - self.assertEqual(result.options.__dict__, {"shots": 100, "dummy": 100}) - - with self.subTest("fidelity run"): - # Run options override fidelity options - fidelity = ComputeUncompute(sampler_shots, options={"shots": 2048, "dummy": 100}) - job = fidelity.run(self._circuit[2], self._circuit[3], shots=50, dummy=None) - options = fidelity.options - result = job.result() - # Only default + sampler options. Not run. - self.assertEqual(options.__dict__, {"shots": 2048, "dummy": 100}) - self.assertEqual(result.options.__dict__, {"shots": 50, "dummy": None}) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_amplitude_estimators.py b/test/python/algorithms/test_amplitude_estimators.py deleted file mode 100644 index ea0a1af099ee..000000000000 --- a/test/python/algorithms/test_amplitude_estimators.py +++ /dev/null @@ -1,724 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# 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. - -"""Test the quantum amplitude estimation algorithm.""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from ddt import ddt, idata, data, unpack -from qiskit import QuantumRegister, QuantumCircuit, BasicAer -from qiskit.circuit.library import QFT, GroverOperator -from qiskit.utils import QuantumInstance -from qiskit.algorithms import ( - AmplitudeEstimation, - MaximumLikelihoodAmplitudeEstimation, - IterativeAmplitudeEstimation, - FasterAmplitudeEstimation, - EstimationProblem, -) -from qiskit.quantum_info import Operator, Statevector -from qiskit.primitives import Sampler - - -class BernoulliStateIn(QuantumCircuit): - """A circuit preparing sqrt(1 - p)|0> + sqrt(p)|1>.""" - - def __init__(self, probability): - super().__init__(1) - angle = 2 * np.arcsin(np.sqrt(probability)) - self.ry(angle, 0) - - -class BernoulliGrover(QuantumCircuit): - """The Grover operator corresponding to the Bernoulli A operator.""" - - def __init__(self, probability): - super().__init__(1, global_phase=np.pi) - self.angle = 2 * np.arcsin(np.sqrt(probability)) - self.ry(2 * self.angle, 0) - - def power(self, power, matrix_power=False): - if matrix_power: - return super().power(power, True) - - powered = QuantumCircuit(1) - powered.ry(power * 2 * self.angle, 0) - return powered - - -class SineIntegral(QuantumCircuit): - r"""Construct the A operator to approximate the integral - - \int_0^1 \sin^2(x) d x - - with a specified number of qubits. - """ - - def __init__(self, num_qubits): - qr_state = QuantumRegister(num_qubits, "state") - qr_objective = QuantumRegister(1, "obj") - super().__init__(qr_state, qr_objective) - - # prepare 1/sqrt{2^n} sum_x |x>_n - self.h(qr_state) - - # apply the sine/cosine term - self.ry(2 * 1 / 2 / 2**num_qubits, qr_objective[0]) - for i, qubit in enumerate(qr_state): - self.cry(2 * 2**i / 2**num_qubits, qubit, qr_objective[0]) - - -@ddt -class TestBernoulli(QiskitAlgorithmsTestCase): - """Tests based on the Bernoulli A operator. - - This class tests - * the estimation result - * the constructed circuits - """ - - def setUp(self): - super().setUp() - - with self.assertWarns(DeprecationWarning): - self._statevector = QuantumInstance( - backend=BasicAer.get_backend("statevector_simulator"), - seed_simulator=2, - seed_transpiler=2, - ) - - self._sampler = Sampler(options={"seed": 2}) - - def qasm(shots=100): - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - backend=BasicAer.get_backend("qasm_simulator"), - shots=shots, - seed_simulator=2, - seed_transpiler=2, - ) - return qi - - self._qasm = qasm - - def sampler_shots(shots=100): - return Sampler(options={"shots": shots, "seed": 2}) - - self._sampler_shots = sampler_shots - - @idata( - [ - [0.2, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.2}], - [0.49, AmplitudeEstimation(3), {"estimation": 0.5, "mle": 0.49}], - [0.2, MaximumLikelihoodAmplitudeEstimation([0, 1, 2]), {"estimation": 0.2}], - [0.49, MaximumLikelihoodAmplitudeEstimation(3), {"estimation": 0.49}], - [0.2, IterativeAmplitudeEstimation(0.1, 0.1), {"estimation": 0.2}], - [0.49, IterativeAmplitudeEstimation(0.001, 0.01), {"estimation": 0.49}], - [0.2, FasterAmplitudeEstimation(0.1, 3, rescale=False), {"estimation": 0.2}], - [0.12, FasterAmplitudeEstimation(0.1, 2, rescale=False), {"estimation": 0.12}], - ] - ) - @unpack - def test_statevector(self, prob, qae, expect): - """statevector test""" - - problem = EstimationProblem(BernoulliStateIn(prob), 0, BernoulliGrover(prob)) - - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._statevector - result = qae.estimate(problem) - - self._statevector.reset_execution_results() - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [0.2, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.2}], - [0.49, AmplitudeEstimation(3), {"estimation": 0.5, "mle": 0.49}], - [0.2, MaximumLikelihoodAmplitudeEstimation([0, 1, 2]), {"estimation": 0.2}], - [0.49, MaximumLikelihoodAmplitudeEstimation(3), {"estimation": 0.49}], - [0.2, IterativeAmplitudeEstimation(0.1, 0.1), {"estimation": 0.2}], - [0.49, IterativeAmplitudeEstimation(0.001, 0.01), {"estimation": 0.49}], - [0.2, FasterAmplitudeEstimation(0.1, 3, rescale=False), {"estimation": 0.199}], - [0.12, FasterAmplitudeEstimation(0.1, 2, rescale=False), {"estimation": 0.12}], - ] - ) - @unpack - def test_sampler(self, prob, qae, expect): - """sampler test""" - qae.sampler = self._sampler - problem = EstimationProblem(BernoulliStateIn(prob), 0, BernoulliGrover(prob)) - - result = qae.estimate(problem) - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [0.2, 100, AmplitudeEstimation(4), {"estimation": 0.14644, "mle": 0.193888}], - [0.0, 1000, AmplitudeEstimation(2), {"estimation": 0.0, "mle": 0.0}], - [ - 0.2, - 100, - MaximumLikelihoodAmplitudeEstimation([0, 1, 2, 4, 8]), - {"estimation": 0.199606}, - ], - [0.8, 10, IterativeAmplitudeEstimation(0.1, 0.05), {"estimation": 0.811711}], - [0.2, 1000, FasterAmplitudeEstimation(0.1, 3, rescale=False), {"estimation": 0.198640}], - [ - 0.12, - 100, - FasterAmplitudeEstimation(0.01, 3, rescale=False), - {"estimation": 0.119037}, - ], - ] - ) - @unpack - def test_qasm(self, prob, shots, qae, expect): - """qasm test""" - - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._qasm(shots) - problem = EstimationProblem(BernoulliStateIn(prob), [0], BernoulliGrover(prob)) - result = qae.estimate(problem) - - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [0.2, 100, AmplitudeEstimation(4), {"estimation": 0.14644, "mle": 0.198783}], - [0.0, 1000, AmplitudeEstimation(2), {"estimation": 0.0, "mle": 0.0}], - [ - 0.2, - 100, - MaximumLikelihoodAmplitudeEstimation([0, 1, 2, 4, 8]), - {"estimation": 0.200308}, - ], - [0.8, 10, IterativeAmplitudeEstimation(0.1, 0.05), {"estimation": 0.811711}], - [0.2, 1000, FasterAmplitudeEstimation(0.1, 3, rescale=False), {"estimation": 0.198640}], - [ - 0.12, - 100, - FasterAmplitudeEstimation(0.01, 3, rescale=False), - {"estimation": 0.120017}, - ], - ] - ) - @unpack - def test_sampler_with_shots(self, prob, shots, qae, expect): - """sampler with shots test""" - qae.sampler = self._sampler_shots(shots) - problem = EstimationProblem(BernoulliStateIn(prob), [0], BernoulliGrover(prob)) - - result = qae.estimate(problem) - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @data(True, False) - def test_qae_circuit(self, efficient_circuit): - """Test circuits resulting from canonical amplitude estimation. - - Build the circuit manually and from the algorithm and compare the resulting unitaries. - """ - prob = 0.5 - - problem = EstimationProblem(BernoulliStateIn(prob), objective_qubits=[0]) - for m in [2, 5]: - qae = AmplitudeEstimation(m) - angle = 2 * np.arcsin(np.sqrt(prob)) - - # manually set up the inefficient AE circuit - qr_eval = QuantumRegister(m, "a") - qr_objective = QuantumRegister(1, "q") - circuit = QuantumCircuit(qr_eval, qr_objective) - - # initial Hadamard gates - for i in range(m): - circuit.h(qr_eval[i]) - - # A operator - circuit.ry(angle, qr_objective) - - if efficient_circuit: - qae.grover_operator = BernoulliGrover(prob) - for power in range(m): - circuit.cry(2 * 2**power * angle, qr_eval[power], qr_objective[0]) - else: - oracle = QuantumCircuit(1) - oracle.z(0) - - state_preparation = QuantumCircuit(1) - state_preparation.ry(angle, 0) - grover_op = GroverOperator(oracle, state_preparation) - for power in range(m): - circuit.compose( - grover_op.power(2**power).control(), - qubits=[qr_eval[power], qr_objective[0]], - inplace=True, - ) - - # fourier transform - iqft = QFT(m, do_swaps=False).inverse().reverse_bits() - circuit.append(iqft.to_instruction(), qr_eval) - - actual_circuit = qae.construct_circuit(problem, measurement=False) - - self.assertEqual(Operator(circuit), Operator(actual_circuit)) - - @data(True, False) - def test_iqae_circuits(self, efficient_circuit): - """Test circuits resulting from iterative amplitude estimation. - - Build the circuit manually and from the algorithm and compare the resulting unitaries. - """ - prob = 0.5 - problem = EstimationProblem(BernoulliStateIn(prob), objective_qubits=[0]) - - for k in [2, 5]: - qae = IterativeAmplitudeEstimation(0.01, 0.05) - angle = 2 * np.arcsin(np.sqrt(prob)) - - # manually set up the inefficient AE circuit - q_objective = QuantumRegister(1, "q") - circuit = QuantumCircuit(q_objective) - - # A operator - circuit.ry(angle, q_objective) - - if efficient_circuit: - qae.grover_operator = BernoulliGrover(prob) - circuit.ry(2 * k * angle, q_objective[0]) - - else: - oracle = QuantumCircuit(1) - oracle.z(0) - state_preparation = QuantumCircuit(1) - state_preparation.ry(angle, 0) - grover_op = GroverOperator(oracle, state_preparation) - for _ in range(k): - circuit.compose(grover_op, inplace=True) - - actual_circuit = qae.construct_circuit(problem, k, measurement=False) - self.assertEqual(Operator(circuit), Operator(actual_circuit)) - - @data(True, False) - def test_mlae_circuits(self, efficient_circuit): - """Test the circuits constructed for MLAE""" - prob = 0.5 - problem = EstimationProblem(BernoulliStateIn(prob), objective_qubits=[0]) - - for k in [2, 5]: - qae = MaximumLikelihoodAmplitudeEstimation(k) - angle = 2 * np.arcsin(np.sqrt(prob)) - - # compute all the circuits used for MLAE - circuits = [] - - # 0th power - q_objective = QuantumRegister(1, "q") - circuit = QuantumCircuit(q_objective) - circuit.ry(angle, q_objective) - circuits += [circuit] - - # powers of 2 - for power in range(k): - q_objective = QuantumRegister(1, "q") - circuit = QuantumCircuit(q_objective) - - # A operator - circuit.ry(angle, q_objective) - - # Q^(2^j) operator - if efficient_circuit: - qae.grover_operator = BernoulliGrover(prob) - circuit.ry(2 * 2**power * angle, q_objective[0]) - - else: - oracle = QuantumCircuit(1) - oracle.z(0) - state_preparation = QuantumCircuit(1) - state_preparation.ry(angle, 0) - grover_op = GroverOperator(oracle, state_preparation) - for _ in range(2**power): - circuit.compose(grover_op, inplace=True) - circuits += [circuit] - - actual_circuits = qae.construct_circuits(problem, measurement=False) - - for actual, expected in zip(actual_circuits, circuits): - self.assertEqual(Operator(actual), Operator(expected)) - - -@ddt -class TestSineIntegral(QiskitAlgorithmsTestCase): - """Tests based on the A operator to integrate sin^2(x). - - This class tests - * the estimation result - * the confidence intervals - """ - - def setUp(self): - super().setUp() - - with self.assertWarns(DeprecationWarning): - self._statevector = QuantumInstance( - backend=BasicAer.get_backend("statevector_simulator"), - seed_simulator=123, - seed_transpiler=41, - ) - - self._sampler = Sampler(options={"seed": 123}) - - def qasm(shots=100): - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - backend=BasicAer.get_backend("qasm_simulator"), - shots=shots, - seed_simulator=7192, - seed_transpiler=90000, - ) - return qi - - self._qasm = qasm - - def sampler_shots(shots=100): - return Sampler(options={"shots": shots, "seed": 7192}) - - self._sampler_shots = sampler_shots - - @idata( - [ - [2, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.270290}], - [4, MaximumLikelihoodAmplitudeEstimation(4), {"estimation": 0.272675}], - [3, IterativeAmplitudeEstimation(0.1, 0.1), {"estimation": 0.272082}], - [3, FasterAmplitudeEstimation(0.01, 1), {"estimation": 0.272082}], - ] - ) - @unpack - def test_statevector(self, n, qae, expect): - """Statevector end-to-end test""" - # construct factories for A and Q - # qae.state_preparation = SineIntegral(n) - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._statevector - # result = qae.run(self._statevector) - result = qae.estimate(estimation_problem) - - self._statevector.reset_execution_results() - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [2, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.2702}], - [4, MaximumLikelihoodAmplitudeEstimation(4), {"estimation": 0.2725}], - [3, IterativeAmplitudeEstimation(0.1, 0.1), {"estimation": 0.2721}], - [3, FasterAmplitudeEstimation(0.01, 1), {"estimation": 0.2792}], - ] - ) - @unpack - def test_sampler(self, n, qae, expect): - """sampler end-to-end test""" - # construct factories for A and Q - # qae.state_preparation = SineIntegral(n) - qae.sampler = self._sampler - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - - result = qae.estimate(estimation_problem) - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [4, 100, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.281196}], - [3, 10, MaximumLikelihoodAmplitudeEstimation(2), {"estimation": 0.256878}], - [3, 1000, IterativeAmplitudeEstimation(0.01, 0.01), {"estimation": 0.271790}], - [3, 1000, FasterAmplitudeEstimation(0.1, 4), {"estimation": 0.274168}], - ] - ) - @unpack - def test_qasm(self, n, shots, qae, expect): - """QASM simulator end-to-end test.""" - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._qasm(shots) - result = qae.estimate(estimation_problem) - - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [4, 1000, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.2636}], - [3, 10, MaximumLikelihoodAmplitudeEstimation(2), {"estimation": 0.2904}], - [3, 1000, IterativeAmplitudeEstimation(0.01, 0.01), {"estimation": 0.2706}], - [3, 1000, FasterAmplitudeEstimation(0.1, 4), {"estimation": 0.2764}], - ] - ) - @unpack - def test_sampler_with_shots(self, n, shots, qae, expect): - """Sampler with shots end-to-end test.""" - # construct factories for A and Q - qae.sampler = self._sampler_shots(shots) - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - - result = qae.estimate(estimation_problem) - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [ - AmplitudeEstimation(3), - "mle", - { - "likelihood_ratio": (0.2494734, 0.3003771), - "fisher": (0.2486176, 0.2999286), - "observed_fisher": (0.2484562, 0.3000900), - }, - ], - [ - MaximumLikelihoodAmplitudeEstimation(3), - "estimation", - { - "likelihood_ratio": (0.2598794, 0.2798536), - "fisher": (0.2584889, 0.2797018), - "observed_fisher": (0.2659279, 0.2722627), - }, - ], - ] - ) - @unpack - def test_confidence_intervals(self, qae, key, expect): - """End-to-end test for all confidence intervals.""" - n = 3 - - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._statevector - # statevector simulator - result = qae.estimate(estimation_problem) - - self._statevector.reset_execution_results() - methods = ["lr", "fi", "oi"] # short for likelihood_ratio, fisher, observed_fisher - alphas = [0.1, 0.00001, 0.9] # alpha shouldn't matter in statevector - for alpha, method in zip(alphas, methods): - confint = qae.compute_confidence_interval(result, alpha, method) - # confidence interval based on statevector should be empty, as we are sure of the result - self.assertAlmostEqual(confint[1] - confint[0], 0.0) - self.assertAlmostEqual(confint[0], getattr(result, key)) - - # qasm simulator - shots = 100 - alpha = 0.01 - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._qasm(shots) - result = qae.estimate(estimation_problem) - - for method, expected_confint in expect.items(): - confint = qae.compute_confidence_interval(result, alpha, method) - np.testing.assert_array_almost_equal(confint, expected_confint) - self.assertTrue(confint[0] <= getattr(result, key) <= confint[1]) - - def test_iqae_confidence_intervals(self): - """End-to-end test for the IQAE confidence interval.""" - n = 3 - expected_confint = (0.1984050, 0.3511015) - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - - with self.assertWarns(DeprecationWarning): - qae = IterativeAmplitudeEstimation(0.1, 0.01, quantum_instance=self._statevector) - # statevector simulator - result = qae.estimate(estimation_problem) - - self._statevector.reset_execution_results() - confint = result.confidence_interval - # confidence interval based on statevector should be empty, as we are sure of the result - self.assertAlmostEqual(confint[1] - confint[0], 0.0) - self.assertAlmostEqual(confint[0], result.estimation) - - # qasm simulator - shots = 100 - - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._qasm(shots) - result = qae.estimate(estimation_problem) - - confint = result.confidence_interval - np.testing.assert_array_almost_equal(confint, expected_confint) - self.assertTrue(confint[0] <= result.estimation <= confint[1]) - - -class TestAmplitudeEstimation(QiskitAlgorithmsTestCase): - """Specific tests for canonical AE.""" - - def test_warns_if_good_state_set(self): - """Check AE warns if is_good_state is set.""" - circuit = QuantumCircuit(1) - problem = EstimationProblem(circuit, objective_qubits=[0], is_good_state=lambda x: True) - - qae = AmplitudeEstimation(num_eval_qubits=1, sampler=Sampler()) - - with self.assertWarns(Warning): - _ = qae.estimate(problem) - - -@ddt -class TestFasterAmplitudeEstimation(QiskitAlgorithmsTestCase): - """Specific tests for Faster AE.""" - - def setUp(self): - super().setUp() - self._sampler = Sampler(options={"seed": 2}) - - def test_rescaling(self): - """Test the rescaling.""" - amplitude = 0.8 - scaling = 0.25 - circuit = QuantumCircuit(1) - circuit.ry(2 * np.arcsin(amplitude), 0) - problem = EstimationProblem(circuit, objective_qubits=[0]) - - rescaled = problem.rescale(scaling) - rescaled_amplitude = Statevector.from_instruction(rescaled.state_preparation).data[3] - - self.assertAlmostEqual(scaling * amplitude, rescaled_amplitude) - - def test_run_without_rescaling(self): - """Run Faster AE without rescaling if the amplitude is in [0, 1/4].""" - # construct estimation problem - prob = 0.11 - a_op = QuantumCircuit(1) - a_op.ry(2 * np.arcsin(np.sqrt(prob)), 0) - problem = EstimationProblem(a_op, objective_qubits=[0]) - - # construct algo without rescaling - backend = BasicAer.get_backend("statevector_simulator") - - with self.assertWarns(DeprecationWarning): - fae = FasterAmplitudeEstimation(0.1, 1, rescale=False, quantum_instance=backend) - - # run the algo - result = fae.estimate(problem) - - # assert the result is correct - self.assertAlmostEqual(result.estimation, prob) - - # assert no rescaling was used - theta = np.mean(result.theta_intervals[-1]) - value_without_scaling = np.sin(theta) ** 2 - self.assertAlmostEqual(result.estimation, value_without_scaling) - - def test_sampler_run_without_rescaling(self): - """Run Faster AE without rescaling if the amplitude is in [0, 1/4].""" - # construct estimation problem - prob = 0.11 - a_op = QuantumCircuit(1) - a_op.ry(2 * np.arcsin(np.sqrt(prob)), 0) - problem = EstimationProblem(a_op, objective_qubits=[0]) - - # construct algo without rescaling - fae = FasterAmplitudeEstimation(0.1, 1, rescale=False, sampler=self._sampler) - - # run the algo - result = fae.estimate(problem) - - # assert the result is correct - self.assertAlmostEqual(result.estimation, prob, places=2) - - # assert no rescaling was used - theta = np.mean(result.theta_intervals[-1]) - value_without_scaling = np.sin(theta) ** 2 - self.assertAlmostEqual(result.estimation, value_without_scaling) - - def test_rescaling_with_custom_grover_raises(self): - """Test that the rescaling option fails if a custom Grover operator is used.""" - prob = 0.8 - a_op = BernoulliStateIn(prob) - q_op = BernoulliGrover(prob) - problem = EstimationProblem(a_op, objective_qubits=[0], grover_operator=q_op) - - # construct algo without rescaling - backend = BasicAer.get_backend("statevector_simulator") - with self.assertWarns(DeprecationWarning): - fae = FasterAmplitudeEstimation(0.1, 1, quantum_instance=backend) - - # run the algo - with self.assertWarns(Warning): - _ = fae.estimate(problem) - - @data(("statevector_simulator", 0.2), ("qasm_simulator", 0.199440)) - @unpack - def test_good_state(self, backend_str, expect): - """Test with a good state function.""" - - def is_good_state(bitstr): - return bitstr[1] == "1" - - # construct the estimation problem where the second qubit is ignored - a_op = QuantumCircuit(2) - a_op.ry(2 * np.arcsin(np.sqrt(0.2)), 0) - - # oracle only affects first qubit - oracle = QuantumCircuit(2) - oracle.z(0) - - # reflect only on first qubit - q_op = GroverOperator(oracle, a_op, reflection_qubits=[0]) - - # but we measure both qubits (hence both are objective qubits) - problem = EstimationProblem( - a_op, objective_qubits=[0, 1], grover_operator=q_op, is_good_state=is_good_state - ) - - # construct algo - with self.assertWarns(DeprecationWarning): - backend = QuantumInstance( - BasicAer.get_backend(backend_str), seed_simulator=2, seed_transpiler=2 - ) - # cannot use rescaling with a custom grover operator - - with self.assertWarns(DeprecationWarning): - fae = FasterAmplitudeEstimation(0.01, 5, rescale=False, quantum_instance=backend) - - # run the algo - result = fae.estimate(problem) - - # assert the result is correct - self.assertAlmostEqual(result.estimation, expect, places=5) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_aux_ops_evaluator.py b/test/python/algorithms/test_aux_ops_evaluator.py deleted file mode 100644 index bff0b07c09c9..000000000000 --- a/test/python/algorithms/test_aux_ops_evaluator.py +++ /dev/null @@ -1,197 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. -"""Tests evaluator of auxiliary operators for algorithms.""" - -import unittest -import warnings -from typing import Tuple, Union - -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from ddt import ddt, data - -from qiskit.algorithms.list_or_dict import ListOrDict -from qiskit.providers import Backend -from qiskit.quantum_info import Statevector -from qiskit.algorithms import eval_observables -from qiskit import BasicAer, QuantumCircuit -from qiskit.circuit.library import EfficientSU2 -from qiskit.opflow import ( - PauliSumOp, - X, - Z, - I, - ExpectationFactory, - OperatorBase, - ExpectationBase, - StateFn, -) -from qiskit.utils import QuantumInstance, algorithm_globals - - -@ddt -class TestAuxOpsEvaluator(QiskitAlgorithmsTestCase): - """Tests evaluator of auxiliary operators for algorithms.""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - with self.assertWarns(DeprecationWarning): - self.h2_op = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - - self.threshold = 1e-8 - self.backend_names = ["statevector_simulator", "qasm_simulator"] - - def get_exact_expectation(self, ansatz: QuantumCircuit, observables: ListOrDict[OperatorBase]): - """ - Calculates the exact expectation to be used as an expected result for unit tests. - """ - if isinstance(observables, dict): - observables_list = list(observables.values()) - else: - observables_list = observables - - # the exact value is a list of (mean, variance) where we expect 0 variance - exact = [ - (Statevector(ansatz).expectation_value(observable), 0) - for observable in observables_list - ] - - if isinstance(observables, dict): - return dict(zip(observables.keys(), exact)) - - return exact - - def _run_test( - self, - expected_result: ListOrDict[Tuple[complex, complex]], - quantum_state: Union[QuantumCircuit, Statevector], - decimal: int, - expectation: ExpectationBase, - observables: ListOrDict[OperatorBase], - quantum_instance: Union[QuantumInstance, Backend], - ): - - with self.assertWarns(DeprecationWarning): - result = eval_observables( - quantum_instance, quantum_state, observables, expectation, self.threshold - ) - - if isinstance(observables, dict): - np.testing.assert_equal(list(result.keys()), list(expected_result.keys())) - np.testing.assert_array_almost_equal( - list(result.values()), list(expected_result.values()), decimal=decimal - ) - else: - np.testing.assert_array_almost_equal(result, expected_result, decimal=decimal) - - @data( - [ - PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]), - PauliSumOp.from_list([("II", 2.0)]), - ], - [ - PauliSumOp.from_list([("ZZ", 2.0)]), - ], - { - "op1": PauliSumOp.from_list([("II", 2.0)]), - "op2": PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]), - }, - { - "op1": PauliSumOp.from_list([("ZZ", 2.0)]), - }, - ) - def test_eval_observables(self, observables: ListOrDict[OperatorBase]): - """Tests evaluator of auxiliary operators for algorithms.""" - - ansatz = EfficientSU2(2) - parameters = np.array( - [1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0], - dtype=float, - ) - - bound_ansatz = ansatz.assign_parameters(parameters) - expected_result = self.get_exact_expectation(bound_ansatz, observables) - - for backend_name in self.backend_names: - shots = 4096 if backend_name == "qasm_simulator" else 1 - decimal = ( - 1 if backend_name == "qasm_simulator" else 6 - ) # to accommodate for qasm being imperfect - with self.subTest(msg=f"Test {backend_name} backend."): - backend = BasicAer.get_backend(backend_name) - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - shots=shots, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - expectation = ExpectationFactory.build( - operator=self.h2_op, - backend=quantum_instance, - ) - - with self.subTest(msg="Test QuantumCircuit."): - self._run_test( - expected_result, - bound_ansatz, - decimal, - expectation, - observables, - quantum_instance, - ) - - with self.subTest(msg="Test QuantumCircuit with Backend."): - self._run_test( - expected_result, - bound_ansatz, - decimal, - expectation, - observables, - backend, - ) - - with self.subTest(msg="Test Statevector."): - statevector = Statevector(bound_ansatz) - self._run_test( - expected_result, - statevector, - decimal, - expectation, - observables, - quantum_instance, - ) - with self.assertWarns(DeprecationWarning): - with self.subTest(msg="Test StateFn."): - statefn = StateFn(bound_ansatz) - self._run_test( - expected_result, - statefn, - decimal, - expectation, - observables, - quantum_instance, - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_backendv1.py b/test/python/algorithms/test_backendv1.py deleted file mode 100644 index d0a544834c72..000000000000 --- a/test/python/algorithms/test_backendv1.py +++ /dev/null @@ -1,148 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# 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. - -"""Test Providers that support BackendV1 interface""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit import QuantumCircuit -from qiskit.providers.fake_provider import FakeProvider -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.algorithms import VQE, Grover, AmplificationProblem -from qiskit.opflow import X, Z, I -from qiskit.algorithms.optimizers import SPSA -from qiskit.circuit.library import TwoLocal, EfficientSU2 -from qiskit.utils.mitigation import CompleteMeasFitter - - -class TestBackendV1(QiskitAlgorithmsTestCase): - """test BackendV1 interface""" - - def setUp(self): - super().setUp() - self._provider = FakeProvider() - self._qasm = self._provider.get_backend("fake_qasm_simulator") - self.seed = 50 - - def test_vqe_qasm(self): - """Test the VQE on QASM simulator.""" - optimizer = SPSA(maxiter=300, last_avg=5) - wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - with self.assertWarns(DeprecationWarning): - h2_op = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - qasm_simulator = QuantumInstance( - self._qasm, shots=1024, seed_simulator=self.seed, seed_transpiler=self.seed - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=qasm_simulator, - ) - result = vqe.compute_minimum_eigenvalue(operator=h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) - - def test_run_circuit_oracle(self): - """Test execution with a quantum circuit oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - self._provider.get_backend("fake_vigo"), seed_simulator=12, seed_transpiler=32 - ) - - with self.assertWarns(DeprecationWarning): - grover = Grover(quantum_instance=qi) - result = grover.amplify(problem) - - self.assertIn(result.top_measurement, ["11"]) - - def test_run_circuit_oracle_single_experiment_backend(self): - """Test execution with a quantum circuit oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - backend = self._provider.get_backend("fake_vigo") - backend._configuration.max_experiments = 1 - - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance(backend, seed_simulator=12, seed_transpiler=32) - - with self.assertWarns(DeprecationWarning): - grover = Grover(quantum_instance=qi) - result = grover.amplify(problem) - - self.assertIn(result.top_measurement, ["11"]) - - def test_measurement_error_mitigation_with_vqe(self): - """measurement error mitigation test with vqe""" - try: - from qiskit.providers.aer import noise - except ImportError as ex: - self.skipTest(f"Package doesn't appear to be installed. Error: '{str(ex)}'") - return - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - backend = self._qasm - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=167, - seed_transpiler=167, - noise_model=noise_model, - measurement_error_mitigation_cls=CompleteMeasFitter, - ) - h2_hamiltonian = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - - optimizer = SPSA(maxiter=200) - ansatz = EfficientSU2(2, reps=1) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=ansatz, optimizer=optimizer, quantum_instance=quantum_instance) - result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) - - self.assertGreater(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_backendv2.py b/test/python/algorithms/test_backendv2.py deleted file mode 100644 index 0987a631f690..000000000000 --- a/test/python/algorithms/test_backendv2.py +++ /dev/null @@ -1,102 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# 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. - -"""Test Providers that support BackendV2 interface""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit import QuantumCircuit -from qiskit.providers.fake_provider import FakeProvider -from qiskit.providers.fake_provider.fake_backend_v2 import FakeBackendSimple -from qiskit.utils import QuantumInstance -from qiskit.algorithms import VQE, Grover, AmplificationProblem -from qiskit.opflow import X, Z, I -from qiskit.algorithms.optimizers import SPSA -from qiskit.circuit.library import TwoLocal - - -class TestBackendV2(QiskitAlgorithmsTestCase): - """test BackendV2 interface""" - - def setUp(self): - super().setUp() - self._provider = FakeProvider() - self._qasm = FakeBackendSimple() - self.seed = 50 - - def test_vqe_qasm(self): - """Test the VQE on QASM simulator.""" - optimizer = SPSA(maxiter=300, last_avg=5) - wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - with self.assertWarns(DeprecationWarning): - h2_op = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - qasm_simulator = QuantumInstance( - self._qasm, shots=1024, seed_simulator=self.seed, seed_transpiler=self.seed - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=qasm_simulator, - ) - result = vqe.compute_minimum_eigenvalue(operator=h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) - - def test_run_circuit_oracle(self): - """Test execution with a quantum circuit oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - self._provider.get_backend("fake_yorktown"), seed_simulator=12, seed_transpiler=32 - ) - - with self.assertWarns(DeprecationWarning): - grover = Grover(quantum_instance=qi) - result = grover.amplify(problem) - - self.assertIn(result.top_measurement, ["11"]) - - def test_run_circuit_oracle_single_experiment_backend(self): - """Test execution with a quantum circuit oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - backend = self._provider.get_backend("fake_yorktown") - backend._configuration.max_experiments = 1 - - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - self._provider.get_backend("fake_yorktown"), seed_simulator=12, seed_transpiler=32 - ) - - with self.assertWarns(DeprecationWarning): - grover = Grover(quantum_instance=qi) - result = grover.amplify(problem) - - self.assertIn(result.top_measurement, ["11"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_entangler_map.py b/test/python/algorithms/test_entangler_map.py deleted file mode 100644 index 56dd5100c64f..000000000000 --- a/test/python/algorithms/test_entangler_map.py +++ /dev/null @@ -1,68 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""Test Entangler Map""" - -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit.utils import get_entangler_map, validate_entangler_map - - -class TestEntanglerMap(QiskitAlgorithmsTestCase): - """Test Entangler Map""" - - def test_map_type_linear(self): - """,ap type linear test""" - ref_map = [[0, 1], [1, 2], [2, 3]] - entangler_map = get_entangler_map("linear", 4) - - for (ref_src, ref_targ), (exp_src, exp_targ) in zip(ref_map, entangler_map): - self.assertEqual(ref_src, exp_src) - self.assertEqual(ref_targ, exp_targ) - - def test_map_type_full(self): - """map type full test""" - ref_map = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]] - entangler_map = get_entangler_map("full", 4) - - for (ref_src, ref_targ), (exp_src, exp_targ) in zip(ref_map, entangler_map): - self.assertEqual(ref_src, exp_src) - self.assertEqual(ref_targ, exp_targ) - - def test_validate_entangler_map(self): - """validate entangler map test""" - valid_map = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]] - self.assertTrue(validate_entangler_map(valid_map, 4)) - - valid_map_2 = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3], [3, 2]] - self.assertTrue(validate_entangler_map(valid_map_2, 4, True)) - - invalid_map = [[0, 4], [4, 2], [0, 3], [1, 2], [1, 3], [2, 3]] - with self.assertRaises(ValueError): - validate_entangler_map(invalid_map, 4) - - invalid_map_2 = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3], [3, 2]] - with self.assertRaises(ValueError): - validate_entangler_map(invalid_map_2, 4) - - wrong_type_map = {0: [1, 2, 3], 1: [2, 3]} - with self.assertRaises(TypeError): - validate_entangler_map(wrong_type_map, 4) - - wrong_type_map_2 = [(0, 1), (0, 2), (0, 3)] - with self.assertRaises(TypeError): - validate_entangler_map(wrong_type_map_2, 4) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_grover.py b/test/python/algorithms/test_grover.py deleted file mode 100644 index 0ba6cbaccf04..000000000000 --- a/test/python/algorithms/test_grover.py +++ /dev/null @@ -1,402 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""Test Grover's algorithm.""" - -import itertools -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt, idata, unpack - -from qiskit import BasicAer, QuantumCircuit -from qiskit.algorithms import AmplificationProblem, Grover -from qiskit.circuit.library import GroverOperator, PhaseOracle -from qiskit.primitives import Sampler -from qiskit.quantum_info import Operator, Statevector -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.utils.optionals import HAS_TWEEDLEDUM - - -@ddt -class TestAmplificationProblem(QiskitAlgorithmsTestCase): - """Test the amplification problem.""" - - def setUp(self): - super().setUp() - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - self._expected_grover_op = GroverOperator(oracle=oracle) - - @data("oracle_only", "oracle_and_stateprep") - def test_groverop_getter(self, kind): - """Test the default construction of the Grover operator.""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - - if kind == "oracle_only": - problem = AmplificationProblem(oracle, is_good_state=["11"]) - expected = GroverOperator(oracle) - else: - stateprep = QuantumCircuit(2) - stateprep.ry(0.2, [0, 1]) - problem = AmplificationProblem( - oracle, state_preparation=stateprep, is_good_state=["11"] - ) - expected = GroverOperator(oracle, stateprep) - - self.assertEqual(Operator(expected), Operator(problem.grover_operator)) - - @data("list_str", "list_int", "statevector", "callable") - def test_is_good_state(self, kind): - """Test is_good_state works on different input types.""" - if kind == "list_str": - is_good_state = ["01", "11"] - elif kind == "list_int": - is_good_state = [1] # means bitstr[1] == '1' - elif kind == "statevector": - is_good_state = Statevector(np.array([0, 1, 0, 1]) / np.sqrt(2)) - else: - - def is_good_state(bitstr): - # same as ``bitstr in ['01', '11']`` - return bitstr[1] == "1" - - possible_states = [ - "".join(list(map(str, item))) for item in itertools.product([0, 1], repeat=2) - ] - - oracle = QuantumCircuit(2) - problem = AmplificationProblem(oracle, is_good_state=is_good_state) - - expected = [state in ["01", "11"] for state in possible_states] - actual = [problem.is_good_state(state) for state in possible_states] - - self.assertListEqual(expected, actual) - - -@ddt -class TestGrover(QiskitAlgorithmsTestCase): - """Test for the functionality of Grover""" - - def setUp(self): - super().setUp() - with self.assertWarns(DeprecationWarning): - self.statevector = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), seed_simulator=12, seed_transpiler=32 - ) - self.qasm = QuantumInstance( - BasicAer.get_backend("qasm_simulator"), seed_simulator=12, seed_transpiler=32 - ) - self._sampler = Sampler() - self._sampler_with_shots = Sampler(options={"shots": 1024, "seed": 123}) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 123 - - @unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum required for this test") - @data("ideal", "shots", False) - def test_implicit_phase_oracle_is_good_state(self, use_sampler): - """Test implicit default for is_good_state with PhaseOracle.""" - grover = self._prepare_grover(use_sampler) - oracle = PhaseOracle("x & y") - problem = AmplificationProblem(oracle) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "11") - - @idata(itertools.product(["ideal", "shots", False], [[1, 2, 3], None, 2])) - @unpack - def test_iterations_with_good_state(self, use_sampler, iterations): - """Test the algorithm with different iteration types and with good state""" - grover = self._prepare_grover(use_sampler, iterations) - problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "111") - - @idata(itertools.product(["shots", False], [[1, 2, 3], None, 2])) - @unpack - def test_iterations_with_good_state_sample_from_iterations(self, use_sampler, iterations): - """Test the algorithm with different iteration types and with good state""" - grover = self._prepare_grover(use_sampler, iterations, sample_from_iterations=True) - problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "111") - - @data("ideal", "shots", False) - def test_fixed_iterations_without_good_state(self, use_sampler): - """Test the algorithm with iterations as an int and without good state""" - grover = self._prepare_grover(use_sampler, iterations=2) - problem = AmplificationProblem(Statevector.from_label("111")) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "111") - - @idata(itertools.product(["ideal", "shots", False], [[1, 2, 3], None])) - @unpack - def test_iterations_without_good_state(self, use_sampler, iterations): - """Test the correct error is thrown for none/list of iterations and without good state""" - grover = self._prepare_grover(use_sampler, iterations=iterations) - problem = AmplificationProblem(Statevector.from_label("111")) - - with self.assertRaisesRegex( - TypeError, "An is_good_state function is required with the provided oracle" - ): - if not use_sampler: - with self.assertWarns(DeprecationWarning): - grover.amplify(problem) - else: - grover.amplify(problem) - - @data("ideal", "shots", False) - def test_iterator(self, use_sampler): - """Test running the algorithm on an iterator.""" - - # step-function iterator - def iterator(): - wait, value, count = 3, 1, 0 - while True: - yield value - count += 1 - if count % wait == 0: - value += 1 - - grover = self._prepare_grover(use_sampler, iterations=iterator()) - problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "111") - - @data("ideal", "shots", False) - def test_growth_rate(self, use_sampler): - """Test running the algorithm on a growth rate""" - grover = self._prepare_grover(use_sampler, growth_rate=8 / 7) - problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "111") - - @data("ideal", "shots", False) - def test_max_num_iterations(self, use_sampler): - """Test the iteration stops when the maximum number of iterations is reached.""" - - def zero(): - while True: - yield 0 - - grover = self._prepare_grover(use_sampler, iterations=zero()) - n = 5 - problem = AmplificationProblem(Statevector.from_label("1" * n), is_good_state=["1" * n]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(len(result.iterations), 2**n) - - @data("ideal", "shots", False) - def test_max_power(self, use_sampler): - """Test the iteration stops when the maximum power is reached.""" - lam = 10.0 - grover = self._prepare_grover(use_sampler, growth_rate=lam) - problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) - result = grover.amplify(problem) - self.assertEqual(len(result.iterations), 0) - - @data("ideal", "shots", False) - def test_run_circuit_oracle(self, use_sampler): - """Test execution with a quantum circuit oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - grover = self._prepare_grover(use_sampler) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertIn(result.top_measurement, ["11"]) - - @data("ideal", "shots", False) - def test_run_state_vector_oracle(self, use_sampler): - """Test execution with a state vector oracle""" - mark_state = Statevector.from_label("11") - problem = AmplificationProblem(mark_state, is_good_state=["11"]) - grover = self._prepare_grover(use_sampler) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertIn(result.top_measurement, ["11"]) - - @data("ideal", "shots", False) - def test_run_custom_grover_operator(self, use_sampler): - """Test execution with a grover operator oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - grover_op = GroverOperator(oracle) - problem = AmplificationProblem( - oracle=oracle, grover_operator=grover_op, is_good_state=["11"] - ) - grover = self._prepare_grover(use_sampler) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertIn(result.top_measurement, ["11"]) - - def test_optimal_num_iterations(self): - """Test optimal_num_iterations""" - num_qubits = 7 - for num_solutions in range(1, 2**num_qubits): - amplitude = np.sqrt(num_solutions / 2**num_qubits) - expected = round(np.arccos(amplitude) / (2 * np.arcsin(amplitude))) - actual = Grover.optimal_num_iterations(num_solutions, num_qubits) - self.assertEqual(actual, expected) - - def test_construct_circuit(self): - """Test construct_circuit""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - grover = Grover() - constructed = grover.construct_circuit(problem, 2, measurement=False) - - grover_op = GroverOperator(oracle) - expected = QuantumCircuit(2) - expected.h([0, 1]) - expected.compose(grover_op.power(2), inplace=True) - - self.assertTrue(Operator(constructed).equiv(Operator(expected))) - - @data("ideal", "shots", False) - def test_circuit_result(self, use_sampler): - """Test circuit_result""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - # is_good_state=['00'] is intentionally selected to obtain a list of results - problem = AmplificationProblem(oracle, is_good_state=["00"]) - grover = self._prepare_grover(use_sampler, iterations=[1, 2, 3, 4]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - if use_sampler: - for i, dist in enumerate(result.circuit_results): - keys, values = zip(*sorted(dist.items())) - if i in (0, 3): - self.assertTupleEqual(keys, ("11",)) - np.testing.assert_allclose(values, [1], atol=0.2) - else: - self.assertTupleEqual(keys, ("00", "01", "10", "11")) - np.testing.assert_allclose(values, [0.25, 0.25, 0.25, 0.25], atol=0.2) - else: - expected_results = [ - {"11": 1024}, - {"00": 238, "01": 253, "10": 263, "11": 270}, - {"00": 238, "01": 253, "10": 263, "11": 270}, - {"11": 1024}, - ] - self.assertEqual(result.circuit_results, expected_results) - - @data("ideal", "shots", False) - def test_max_probability(self, use_sampler): - """Test max_probability""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - grover = self._prepare_grover(use_sampler) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertAlmostEqual(result.max_probability, 1.0) - - @unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum required for this test") - @data("ideal", "shots", False) - def test_oracle_evaluation(self, use_sampler): - """Test oracle_evaluation for PhaseOracle""" - oracle = PhaseOracle("x1 & x2 & (not x3)") - problem = AmplificationProblem(oracle, is_good_state=oracle.evaluate_bitstring) - grover = self._prepare_grover(use_sampler) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertTrue(result.oracle_evaluation) - self.assertEqual("011", result.top_measurement) - - def test_sampler_setter(self): - """Test sampler setter""" - grover = Grover() - grover.sampler = self._sampler - self.assertEqual(grover.sampler, self._sampler) - - def _prepare_grover( - self, use_sampler, iterations=None, growth_rate=None, sample_from_iterations=False - ): - """Prepare Grover instance for test""" - if use_sampler == "ideal": - grover = Grover( - sampler=self._sampler, - iterations=iterations, - growth_rate=growth_rate, - sample_from_iterations=sample_from_iterations, - ) - elif use_sampler == "shots": - grover = Grover( - sampler=self._sampler_with_shots, - iterations=iterations, - growth_rate=growth_rate, - sample_from_iterations=sample_from_iterations, - ) - else: - with self.assertWarns(DeprecationWarning): - grover = Grover( - quantum_instance=self.qasm, - iterations=iterations, - growth_rate=growth_rate, - sample_from_iterations=sample_from_iterations, - ) - return grover - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_measure_error_mitigation.py b/test/python/algorithms/test_measure_error_mitigation.py deleted file mode 100644 index ed9e972c524e..000000000000 --- a/test/python/algorithms/test_measure_error_mitigation.py +++ /dev/null @@ -1,524 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2023. -# -# 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. - -"""Test Measurement Error Mitigation""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt, data, unpack -import numpy as np -import rustworkx as rx -from qiskit import QuantumCircuit, execute -from qiskit.quantum_info import Pauli -from qiskit.exceptions import QiskitError -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.algorithms import VQE, QAOA -from qiskit.opflow import I, X, Z, PauliSumOp -from qiskit.algorithms.optimizers import SPSA, COBYLA -from qiskit.circuit.library import EfficientSU2 -from qiskit.utils.mitigation import CompleteMeasFitter, TensoredMeasFitter -from qiskit.utils.measurement_error_mitigation import build_measurement_error_mitigation_circuits -from qiskit.utils import optionals - -if optionals.HAS_AER: - # pylint: disable=no-name-in-module - from qiskit import Aer - from qiskit.providers.aer import noise -if optionals.HAS_IGNIS: - # pylint: disable=no-name-in-module - from qiskit.ignis.mitigation.measurement import ( - CompleteMeasFitter as CompleteMeasFitter_IG, - TensoredMeasFitter as TensoredMeasFitter_IG, - ) - - -@ddt -class TestMeasurementErrorMitigation(QiskitAlgorithmsTestCase): - """Test measurement error mitigation.""" - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - @data( - ("CompleteMeasFitter", None, False), - ("TensoredMeasFitter", None, False), - ("TensoredMeasFitter", [[0, 1]], True), - ("TensoredMeasFitter", [[1], [0]], False), - ) - @unpack - def test_measurement_error_mitigation_with_diff_qubit_order( - self, - fitter_str, - mit_pattern, - fails, - ): - """measurement error mitigation with different qubit order""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - fitter_cls = ( - CompleteMeasFitter if fitter_str == "CompleteMeasFitter" else TensoredMeasFitter - ) - backend = Aer.get_backend("aer_simulator") - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=1679, - seed_transpiler=167, - shots=1000, - noise_model=noise_model, - measurement_error_mitigation_cls=fitter_cls, - cals_matrix_refresh_period=0, - mit_pattern=mit_pattern, - ) - - # circuit - qc1 = QuantumCircuit(2, 2) - qc1.h(0) - qc1.cx(0, 1) - qc1.measure(0, 0) - qc1.measure(1, 1) - qc2 = QuantumCircuit(2, 2) - qc2.h(0) - qc2.cx(0, 1) - qc2.measure(1, 0) - qc2.measure(0, 1) - - with self.assertWarns(DeprecationWarning): - if fails: - self.assertRaisesRegex( - QiskitError, - "Each element in the mit pattern should have length 1.", - quantum_instance.execute, - [qc1, qc2], - ) - else: - quantum_instance.execute([qc1, qc2]) - - self.assertGreater(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - - # failure case - qc3 = QuantumCircuit(3, 3) - qc3.h(2) - qc3.cx(1, 2) - qc3.measure(2, 1) - qc3.measure(1, 2) - - with self.assertWarns(DeprecationWarning): - self.assertRaises(QiskitError, quantum_instance.execute, [qc1, qc3]) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - @data(("CompleteMeasFitter", None), ("TensoredMeasFitter", [[0], [1]])) - def test_measurement_error_mitigation_with_vqe(self, config): - """measurement error mitigation test with vqe""" - - fitter_str, mit_pattern = config - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - fitter_cls = ( - CompleteMeasFitter if fitter_str == "CompleteMeasFitter" else TensoredMeasFitter - ) - backend = Aer.get_backend("aer_simulator") - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=167, - seed_transpiler=167, - noise_model=noise_model, - measurement_error_mitigation_cls=fitter_cls, - mit_pattern=mit_pattern, - ) - - optimizer = SPSA(maxiter=200) - ansatz = EfficientSU2(2, reps=1) - - with self.assertWarns(DeprecationWarning): - h2_hamiltonian = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=ansatz, optimizer=optimizer, quantum_instance=quantum_instance) - result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) - - self.assertGreater(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) - - def _get_operator(self, weight_matrix): - """Generate Hamiltonian for the max-cut problem of a graph. - - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - - Returns: - PauliSumOp: operator for the Hamiltonian - float: a constant shift for the obj function. - - """ - num_nodes = weight_matrix.shape[0] - pauli_list = [] - shift = 0 - for i in range(num_nodes): - for j in range(i): - if weight_matrix[i, j] != 0: - x_p = np.zeros(num_nodes, dtype=bool) - z_p = np.zeros(num_nodes, dtype=bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([0.5 * weight_matrix[i, j], Pauli((z_p, x_p))]) - shift -= 0.5 * weight_matrix[i, j] - opflow_list = [(pauli[1].to_label(), pauli[0]) for pauli in pauli_list] - - with self.assertWarns(DeprecationWarning): - return PauliSumOp.from_list(opflow_list), shift - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - def test_measurement_error_mitigation_qaoa(self): - """measurement error mitigation test with QAOA""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 167 - - backend = Aer.get_backend("aer_simulator") - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - initial_point = np.asarray([0.0, 0.0]) - - # Compute first without noise - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA( - optimizer=COBYLA(maxiter=3), - quantum_instance=quantum_instance, - initial_point=initial_point, - ) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - ref_eigenvalue = result.eigenvalue.real - - # compute with noise - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - noise_model=noise_model, - measurement_error_mitigation_cls=CompleteMeasFitter, - shots=10000, - ) - - with self.assertWarns(DeprecationWarning): - - qaoa = QAOA( - optimizer=COBYLA(maxiter=3), - quantum_instance=quantum_instance, - initial_point=initial_point, - ) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, ref_eigenvalue, delta=0.05) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - @unittest.skipUnless(optionals.HAS_IGNIS, "qiskit-ignis is required to run this test") - @data("CompleteMeasFitter", "TensoredMeasFitter") - def test_measurement_error_mitigation_with_diff_qubit_order_ignis(self, fitter_str): - """measurement error mitigation with different qubit order""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - fitter_cls = ( - CompleteMeasFitter_IG if fitter_str == "CompleteMeasFitter" else TensoredMeasFitter_IG - ) - backend = Aer.get_backend("aer_simulator") - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=1679, - seed_transpiler=167, - shots=1000, - noise_model=noise_model, - measurement_error_mitigation_cls=fitter_cls, - cals_matrix_refresh_period=0, - ) - - # circuit - qc1 = QuantumCircuit(2, 2) - qc1.h(0) - qc1.cx(0, 1) - qc1.measure(0, 0) - qc1.measure(1, 1) - qc2 = QuantumCircuit(2, 2) - qc2.h(0) - qc2.cx(0, 1) - qc2.measure(1, 0) - qc2.measure(0, 1) - - if fitter_cls == TensoredMeasFitter_IG: - with self.assertWarnsRegex(DeprecationWarning, r".*ignis.*"): - self.assertRaisesRegex( - QiskitError, - "TensoredMeasFitter doesn't support subset_fitter.", - quantum_instance.execute, - [qc1, qc2], - ) - else: - # this should run smoothly - with self.assertWarnsRegex(DeprecationWarning, r".*ignis.*"): - quantum_instance.execute([qc1, qc2]) - - self.assertGreater(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - - # failure case - qc3 = QuantumCircuit(3, 3) - qc3.h(2) - qc3.cx(1, 2) - qc3.measure(2, 1) - qc3.measure(1, 2) - - self.assertRaises(QiskitError, quantum_instance.execute, [qc1, qc3]) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - @unittest.skipUnless(optionals.HAS_IGNIS, "qiskit-ignis is required to run this test") - @data(("CompleteMeasFitter", None), ("TensoredMeasFitter", [[0], [1]])) - def test_measurement_error_mitigation_with_vqe_ignis(self, config): - """measurement error mitigation test with vqe""" - fitter_str, mit_pattern = config - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - fitter_cls = ( - CompleteMeasFitter_IG if fitter_str == "CompleteMeasFitter" else TensoredMeasFitter_IG - ) - backend = Aer.get_backend("aer_simulator") - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=167, - seed_transpiler=167, - noise_model=noise_model, - measurement_error_mitigation_cls=fitter_cls, - mit_pattern=mit_pattern, - ) - - h2_hamiltonian = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - optimizer = SPSA(maxiter=200) - ansatz = EfficientSU2(2, reps=1) - - with self.assertWarnsRegex(DeprecationWarning): - vqe = VQE(ansatz=ansatz, optimizer=optimizer, quantum_instance=quantum_instance) - result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) - - self.assertGreater(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - @unittest.skipUnless(optionals.HAS_IGNIS, "qiskit-ignis is required to run this test") - def test_calibration_results(self): - """check that results counts are the same with/without error mitigation""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 1679 - np.random.seed(algorithm_globals.random_seed) - - qc = QuantumCircuit(1) - qc.x(0) - - qc_meas = qc.copy() - qc_meas.measure_all() - backend = Aer.get_backend("aer_simulator") - - counts_array = [None, None] - for idx, is_use_mitigation in enumerate([True, False]): - with self.assertWarns(DeprecationWarning): - if is_use_mitigation: - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - shots=1024, - measurement_error_mitigation_cls=CompleteMeasFitter_IG, - ) - with self.assertWarnsRegex(DeprecationWarning, r".*ignis.*"): - counts_array[idx] = quantum_instance.execute(qc_meas).get_counts() - else: - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - shots=1024, - ) - counts_array[idx] = quantum_instance.execute(qc_meas).get_counts() - self.assertEqual( - counts_array[0], counts_array[1], msg="Counts different with/without fitter." - ) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - def test_circuit_modified(self): - """tests that circuits don't get modified on QI execute with error mitigation - as per issue #7449 - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 1679 - np.random.seed(algorithm_globals.random_seed) - - circuit = QuantumCircuit(1) - circuit.x(0) - circuit.measure_all() - - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - Aer.get_backend("aer_simulator"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - shots=1024, - measurement_error_mitigation_cls=CompleteMeasFitter, - ) - # The error happens on transpiled circuits since "execute" was changing the input array - # Non transpiled circuits didn't have a problem because a new transpiled array was created - # internally. - circuits_ref = qi.transpile(circuit) # always returns a new array - circuits_input = circuits_ref.copy() - - with self.assertWarns(DeprecationWarning): - _ = qi.execute(circuits_input, had_transpiled=True) - self.assertEqual(circuits_ref, circuits_input, msg="Transpiled circuit array modified.") - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - def test_tensor_subset_fitter(self): - """Test the subset fitter method of the tensor fitter.""" - - # Construct a noise model where readout has errors of different strengths. - noise_model = noise.NoiseModel() - # big error - read_err0 = noise.errors.readout_error.ReadoutError([[0.90, 0.10], [0.25, 0.75]]) - # ideal - read_err1 = noise.errors.readout_error.ReadoutError([[1.00, 0.00], [0.00, 1.00]]) - # small error - read_err2 = noise.errors.readout_error.ReadoutError([[0.98, 0.02], [0.03, 0.97]]) - noise_model.add_readout_error(read_err0, (0,)) - noise_model.add_readout_error(read_err1, (1,)) - noise_model.add_readout_error(read_err2, (2,)) - - mit_pattern = [[idx] for idx in range(3)] - backend = Aer.get_backend("aer_simulator") - backend.set_options(seed_simulator=123) - - with self.assertWarns(DeprecationWarning): - mit_circuits = build_measurement_error_mitigation_circuits( - [0, 1, 2], - TensoredMeasFitter, - backend, - backend_config={}, - compile_config={}, - mit_pattern=mit_pattern, - ) - result = execute(mit_circuits[0], backend, noise_model=noise_model).result() - fitter = TensoredMeasFitter(result, mit_pattern=mit_pattern) - - cal_matrices = fitter.cal_matrices - - # Check that permutations and permuted subsets match. - for subset in [[1, 0], [1, 2], [0, 2], [2, 0, 1]]: - with self.subTest(subset=subset): - with self.assertWarns(DeprecationWarning): - new_fitter = fitter.subset_fitter(subset) - - for idx, qubit in enumerate(subset): - self.assertTrue(np.allclose(new_fitter.cal_matrices[idx], cal_matrices[qubit])) - - self.assertRaisesRegex( - QiskitError, - "Qubit 3 is not in the mit pattern", - fitter.subset_fitter, - [0, 2, 3], - ) - - # Test that we properly correct a circuit with permuted measurements. - circuit = QuantumCircuit(3, 3) - circuit.x(range(3)) - circuit.measure(1, 0) - circuit.measure(2, 1) - circuit.measure(0, 2) - - result = execute( - circuit, backend, noise_model=noise_model, shots=1000, seed_simulator=0 - ).result() - with self.subTest(subset=subset): - with self.assertWarns(DeprecationWarning): - new_result = fitter.subset_fitter([1, 2, 0]).filter.apply(result) - - # The noisy result should have a poor 111 state, the mit. result should be good. - self.assertTrue(result.get_counts()["111"] < 800) - self.assertTrue(new_result.get_counts()["111"] > 990) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_numpy_eigen_solver.py b/test/python/algorithms/test_numpy_eigen_solver.py deleted file mode 100644 index 36d2b66148d0..000000000000 --- a/test/python/algorithms/test_numpy_eigen_solver.py +++ /dev/null @@ -1,210 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# 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. - -"""Test NumPy Eigen solver""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt - -from qiskit.algorithms import NumPyEigensolver -from qiskit.opflow import PauliSumOp, X, Y, Z - - -@ddt -class TestNumPyEigensolver(QiskitAlgorithmsTestCase): - """Test NumPy Eigen solver""" - - def setUp(self): - super().setUp() - with self.assertWarns(DeprecationWarning): - self.qubit_op = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("ZI", 0.39793742484318045), - ("IZ", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - - def test_ce(self): - """Test basics""" - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=[]) - - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - - def test_ce_k4(self): - """Test for k=4 eigenvalues""" - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver(k=4) - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=[]) - - self.assertEqual(len(result.eigenvalues), 4) - self.assertEqual(len(result.eigenstates), 4) - self.assertEqual(result.eigenvalues.dtype, np.float64) - np.testing.assert_array_almost_equal( - result.eigenvalues, [-1.85727503, -1.24458455, -0.88272215, -0.22491125] - ) - - def test_ce_k4_filtered(self): - """Test for k=4 eigenvalues with filter""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return v >= -1 - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver(k=4, filter_criterion=criterion) - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=[]) - - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(len(result.eigenstates), 2) - self.assertEqual(result.eigenvalues.dtype, np.float64) - np.testing.assert_array_almost_equal(result.eigenvalues, [-0.88272215, -0.22491125]) - - def test_ce_k4_filtered_empty(self): - """Test for k=4 eigenvalues with filter always returning False""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return False - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver(k=4, filter_criterion=criterion) - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=[]) - self.assertEqual(len(result.eigenvalues), 0) - self.assertEqual(len(result.eigenstates), 0) - - @data(X, Y, Z) - def test_ce_k1_1q(self, op): - """Test for 1 qubit operator""" - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver(k=1) - result = algo.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal(result.eigenvalues, [-1]) - - @data(X, Y, Z) - def test_ce_k2_1q(self, op): - """Test for 1 qubit operator""" - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver(k=2) - result = algo.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal(result.eigenvalues, [-1, 1]) - - def test_aux_operators_list(self): - """Test list-based aux_operators.""" - - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=aux_ops) - - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 1) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - - with self.assertWarns(DeprecationWarning): - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=extra_ops) - - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 1) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][0], 0, places=6) - self.assertIsNone(result.aux_operator_eigenvalues[0][2], None) - self.assertEqual(result.aux_operator_eigenvalues[0][3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[0][3][1], 0.0) - - def test_aux_operators_dict(self): - """Test dict-based aux_operators.""" - - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=aux_ops) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 1) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - - with self.assertWarns(DeprecationWarning): - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=extra_ops) - - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 1) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 3) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operator_eigenvalues[0]["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operator_eigenvalues[0].keys()) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["zero_operator"][1], 0.0) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_numpy_minimum_eigen_solver.py b/test/python/algorithms/test_numpy_minimum_eigen_solver.py deleted file mode 100644 index 8f50d5738338..000000000000 --- a/test/python/algorithms/test_numpy_minimum_eigen_solver.py +++ /dev/null @@ -1,277 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# 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. - -"""Test NumPy Minimum Eigensolver""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import ddt, data - -from qiskit.algorithms import NumPyMinimumEigensolver -from qiskit.opflow import PauliSumOp, X, Y, Z - - -@ddt -class TestNumPyMinimumEigensolver(QiskitAlgorithmsTestCase): - """Test NumPy Minimum Eigensolver""" - - def setUp(self): - super().setUp() - with self.assertWarns(DeprecationWarning): - self.qubit_op = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("ZI", 0.39793742484318045), - ("IZ", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - - self.aux_ops_list = [aux_op1, aux_op2] - self.aux_ops_dict = {"aux_op1": aux_op1, "aux_op2": aux_op2} - - def test_cme(self): - """Basic test""" - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_list - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[1], [0, 0]) - - def test_cme_reuse(self): - """Test reuse""" - # Start with no operator or aux_operators, give via compute method - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertEqual(result.eigenvalue.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalue, -1.85727503) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Add aux_operators and go again - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_list - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[1], [0, 0]) - - # "Remove" aux_operators and go again - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=[]) - - self.assertEqual(result.eigenvalue.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalue, -1.85727503) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Set aux_operators and go again - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_list - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[1], [0, 0]) - - # Finally just set one of aux_operators and main operator, remove aux_operators - - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.aux_ops_list[0], aux_operators=[] - ) - - self.assertAlmostEqual(result.eigenvalue, 2 + 0j) - self.assertIsNone(result.aux_operator_eigenvalues) - - def test_cme_filter(self): - """Basic test""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return v >= -0.5 - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver(filter_criterion=criterion) - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_list - ) - - self.assertAlmostEqual(result.eigenvalue, -0.22491125 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[1], [0, 0]) - - def test_cme_filter_empty(self): - """Test with filter always returning False""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return False - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver(filter_criterion=criterion) - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_list - ) - - self.assertEqual(result.eigenvalue, None) - self.assertEqual(result.eigenstate, None) - self.assertEqual(result.aux_operator_eigenvalues, None) - - @data(X, Y, Z) - def test_cme_1q(self, op): - """Test for 1 qubit operator""" - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=op) - - self.assertAlmostEqual(result.eigenvalue, -1) - - def test_cme_aux_ops_dict(self): - """Test dictionary compatibility of aux_operators""" - # Start with an empty dictionary - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators={}) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Add aux_operators dictionary and go again - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_dict - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op1"], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op2"], [0, 0]) - - # Add None and zero operators and go again - extra_ops = {"None_op": None, "zero_op": 0, **self.aux_ops_dict} - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=extra_ops - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 3) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op1"], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op2"], [0, 0]) - self.assertEqual(result.aux_operator_eigenvalues["zero_op"], (0.0, 0)) - - def test_aux_operators_list(self): - """Test list-based aux_operators.""" - - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=aux_ops) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=extra_ops - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0, places=6) - self.assertIsNone(result.aux_operator_eigenvalues[2], None) - self.assertEqual(result.aux_operator_eigenvalues[3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[3][1], 0.0) - - def test_aux_operators_dict(self): - """Test dict-based aux_operators.""" - - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=aux_ops) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=extra_ops - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 3) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operator_eigenvalues["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operator_eigenvalues.keys()) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["zero_operator"][1], 0.0) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_observables_evaluator.py b/test/python/algorithms/test_observables_evaluator.py deleted file mode 100644 index d4482c9467b5..000000000000 --- a/test/python/algorithms/test_observables_evaluator.py +++ /dev/null @@ -1,189 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Tests evaluator of auxiliary operators for algorithms.""" - -from __future__ import annotations -import unittest -import warnings -from typing import Tuple - -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from ddt import ddt, data - -from qiskit.algorithms.list_or_dict import ListOrDict -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.algorithms import estimate_observables -from qiskit.primitives import Estimator -from qiskit.quantum_info import Statevector, SparsePauliOp -from qiskit import QuantumCircuit -from qiskit.circuit.library import EfficientSU2 -from qiskit.opflow import PauliSumOp -from qiskit.utils import algorithm_globals - - -@ddt -class TestObservablesEvaluator(QiskitAlgorithmsTestCase): - """Tests evaluator of auxiliary operators for algorithms.""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - self.threshold = 1e-8 - - def get_exact_expectation( - self, ansatz: QuantumCircuit, observables: ListOrDict[BaseOperator | PauliSumOp] - ): - """ - Calculates the exact expectation to be used as an expected result for unit tests. - """ - if isinstance(observables, dict): - observables_list = list(observables.values()) - else: - observables_list = observables - # the exact value is a list of (mean, (variance, shots)) where we expect 0 variance and - # 0 shots - exact = [ - (Statevector(ansatz).expectation_value(observable), {}) - for observable in observables_list - ] - - if isinstance(observables, dict): - return dict(zip(observables.keys(), exact)) - - return exact - - def _run_test( - self, - expected_result: ListOrDict[Tuple[complex, complex]], - quantum_state: QuantumCircuit, - decimal: int, - observables: ListOrDict[BaseOperator | PauliSumOp], - estimator: Estimator, - ): - result = estimate_observables(estimator, quantum_state, observables, None, self.threshold) - - if isinstance(observables, dict): - np.testing.assert_equal(list(result.keys()), list(expected_result.keys())) - means = [element[0] for element in result.values()] - expected_means = [element[0] for element in expected_result.values()] - np.testing.assert_array_almost_equal(means, expected_means, decimal=decimal) - - vars_and_shots = [element[1] for element in result.values()] - expected_vars_and_shots = [element[1] for element in expected_result.values()] - np.testing.assert_array_equal(vars_and_shots, expected_vars_and_shots) - else: - means = [element[0] for element in result] - expected_means = [element[0] for element in expected_result] - np.testing.assert_array_almost_equal(means, expected_means, decimal=decimal) - - vars_and_shots = [element[1] for element in result] - expected_vars_and_shots = [element[1] for element in expected_result] - np.testing.assert_array_equal(vars_and_shots, expected_vars_and_shots) - - @data( - [ - PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]), - PauliSumOp.from_list([("II", 2.0)]), - ], - [ - PauliSumOp.from_list([("ZZ", 2.0)]), - ], - { - "op1": PauliSumOp.from_list([("II", 2.0)]), - "op2": PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]), - }, - { - "op1": PauliSumOp.from_list([("ZZ", 2.0)]), - }, - [], - {}, - ) - def test_estimate_observables(self, observables: ListOrDict[BaseOperator | PauliSumOp]): - """Tests evaluator of auxiliary operators for algorithms.""" - - ansatz = EfficientSU2(2) - parameters = np.array( - [1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0], - dtype=float, - ) - - bound_ansatz = ansatz.assign_parameters(parameters) - states = bound_ansatz - expected_result = self.get_exact_expectation(bound_ansatz, observables) - estimator = Estimator() - decimal = 6 - self._run_test( - expected_result, - states, - decimal, - observables, - estimator, - ) - - def test_estimate_observables_zero_op(self): - """Tests if a zero operator is handled correctly.""" - ansatz = EfficientSU2(2) - parameters = np.array( - [1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0], - dtype=float, - ) - - bound_ansatz = ansatz.assign_parameters(parameters) - state = bound_ansatz - estimator = Estimator() - observables = [SparsePauliOp(["XX", "YY"]), 0] - result = estimate_observables(estimator, state, observables, None, self.threshold) - expected_result = [(0.015607318055509564, {}), (0.0, {})] - means = [element[0] for element in result] - expected_means = [element[0] for element in expected_result] - np.testing.assert_array_almost_equal(means, expected_means, decimal=0.01) - - vars_and_shots = [element[1] for element in result] - expected_vars_and_shots = [element[1] for element in expected_result] - np.testing.assert_array_equal(vars_and_shots, expected_vars_and_shots) - - def test_estimate_observables_shots(self): - """Tests that variances and shots are returned properly.""" - ansatz = EfficientSU2(2) - parameters = np.array( - [1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0], - dtype=float, - ) - - bound_ansatz = ansatz.assign_parameters(parameters) - state = bound_ansatz - estimator = Estimator(options={"shots": 2048}) - with self.assertWarns(DeprecationWarning): - observables = [PauliSumOp.from_list([("ZZ", 2.0)])] - result = estimate_observables(estimator, state, observables, None, self.threshold) - exact_result = self.get_exact_expectation(bound_ansatz, observables) - expected_result = [(exact_result[0][0], {"variance": 1.0898, "shots": 2048})] - - means = [element[0] for element in result] - expected_means = [element[0] for element in expected_result] - np.testing.assert_array_almost_equal(means, expected_means, decimal=0.01) - - vars_and_shots = [element[1] for element in result] - expected_vars_and_shots = [element[1] for element in expected_result] - for computed, expected in zip(vars_and_shots, expected_vars_and_shots): - self.assertAlmostEqual(computed.pop("variance"), expected.pop("variance"), 2) - self.assertEqual(computed.pop("shots"), expected.pop("shots")) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py deleted file mode 100644 index 9040bc1d6912..000000000000 --- a/test/python/algorithms/test_phase_estimator.py +++ /dev/null @@ -1,665 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# 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. - -"""Test phase estimation""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt, data, unpack -import numpy as np -from qiskit.circuit.library import ZGate, XGate, HGate, IGate -from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector, Operator -from qiskit.synthesis import MatrixExponential, SuzukiTrotter -from qiskit.primitives import Sampler -from qiskit.algorithms import PhaseEstimationScale -from qiskit.algorithms.phase_estimators import ( - PhaseEstimation, - HamiltonianPhaseEstimation, - IterativePhaseEstimation, -) -import qiskit -from qiskit import QuantumCircuit -from qiskit.opflow import ( - H, - X, - Y, - Z, - I, - StateFn, - PauliTrotterEvolution, - MatrixEvolution, - PauliSumOp, -) -from qiskit.test import slow_test - - -@ddt -class TestHamiltonianPhaseEstimation(QiskitAlgorithmsTestCase): - """Tests for obtaining eigenvalues from phase estimation""" - - def hamiltonian_pe( - self, - hamiltonian, - state_preparation=None, - num_evaluation_qubits=6, - backend=None, - evolution=None, - bound=None, - ): - """Run HamiltonianPhaseEstimation and return result with all phases.""" - if backend is None: - backend = qiskit.BasicAer.get_backend("statevector_simulator") - - with self.assertWarns(DeprecationWarning): - quantum_instance = qiskit.utils.QuantumInstance(backend=backend, shots=10000) - - with self.assertWarns(DeprecationWarning): - phase_est = HamiltonianPhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, quantum_instance=quantum_instance - ) - result = phase_est.estimate( - hamiltonian=hamiltonian, - state_preparation=state_preparation, - evolution=evolution, - bound=bound, - ) - return result - - @data(MatrixEvolution(), PauliTrotterEvolution("suzuki", 4)) - def test_pauli_sum_1(self, evolution): - """Two eigenvalues from Pauli sum with X, Z""" - with self.assertWarns(DeprecationWarning): - hamiltonian = 0.5 * X + Z - state_preparation = StateFn(H.to_circuit()) - result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evolution) - - phase_dict = result.filter_phases(0.162, as_float=True) - phases = list(phase_dict.keys()) - phases.sort() - - self.assertAlmostEqual(phases[0], -1.125, delta=0.001) - self.assertAlmostEqual(phases[1], 1.125, delta=0.001) - - @data(MatrixEvolution(), PauliTrotterEvolution("suzuki", 3)) - def test_pauli_sum_2(self, evolution): - """Two eigenvalues from Pauli sum with X, Y, Z""" - with self.assertWarns(DeprecationWarning): - hamiltonian = 0.5 * X + Y + Z - state_preparation = None - result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evolution) - - phase_dict = result.filter_phases(0.1, as_float=True) - phases = list(phase_dict.keys()) - phases.sort() - - self.assertAlmostEqual(phases[0], -1.484, delta=0.001) - self.assertAlmostEqual(phases[1], 1.484, delta=0.001) - - def test_single_pauli_op(self): - """Two eigenvalues from Pauli sum with X, Y, Z""" - hamiltonian = Z - state_preparation = None - with self.assertWarns(DeprecationWarning): - result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=None) - - eigv = result.most_likely_eigenvalue - with self.subTest("First eigenvalue"): - self.assertAlmostEqual(eigv, 1.0, delta=0.001) - - with self.assertWarns(DeprecationWarning): - state_preparation = StateFn(X.to_circuit()) - result = self.hamiltonian_pe(hamiltonian, state_preparation, bound=1.05) - - eigv = result.most_likely_eigenvalue - with self.subTest("Second eigenvalue"): - self.assertAlmostEqual(eigv, -0.98, delta=0.01) - - @slow_test - def test_H2_hamiltonian(self): - """Test H2 hamiltonian""" - with self.assertWarns(DeprecationWarning): - hamiltonian = ( - (-1.0523732457728587 * (I ^ I)) - + (0.3979374248431802 * (I ^ Z)) - + (-0.3979374248431802 * (Z ^ I)) - + (-0.011280104256235324 * (Z ^ Z)) - + (0.18093119978423147 * (X ^ X)) - ) - state_preparation = StateFn((I ^ H).to_circuit()) - evo = PauliTrotterEvolution(trotter_mode="suzuki", reps=4) - with self.assertWarns(DeprecationWarning): - result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evo) - with self.subTest("Most likely eigenvalues"): - self.assertAlmostEqual(result.most_likely_eigenvalue, -1.855, delta=0.001) - with self.subTest("Most likely phase"): - self.assertAlmostEqual(result.phase, 0.5937, delta=0.001) - with self.subTest("All eigenvalues"): - phase_dict = result.filter_phases(0.1) - phases = list(phase_dict.keys()) - self.assertAlmostEqual(phases[0], -0.8979, delta=0.001) - self.assertAlmostEqual(phases[1], -1.8551, delta=0.001) - self.assertAlmostEqual(phases[2], -1.2376, delta=0.001) - - def test_matrix_evolution(self): - """1Q Hamiltonian with MatrixEvolution""" - with self.assertWarns(DeprecationWarning): - hamiltonian = (0.5 * X) + (0.6 * Y) + (0.7 * I) - state_preparation = None - result = self.hamiltonian_pe( - hamiltonian, state_preparation, evolution=MatrixEvolution() - ) - phase_dict = result.filter_phases(0.2, as_float=True) - phases = list(phase_dict.keys()) - self.assertAlmostEqual(phases[0], 1.490, delta=0.001) - self.assertAlmostEqual(phases[1], -0.090, delta=0.001) - - def _setup_from_bound(self, evolution, op_class): - with self.assertWarns(DeprecationWarning): - hamiltonian = 0.5 * X + Y + Z - state_preparation = None - bound = 1.2 * sum(abs(hamiltonian.coeff * coeff) for coeff in hamiltonian.coeffs) - if op_class == "MatrixOp": - hamiltonian = hamiltonian.to_matrix_op() - backend = qiskit.BasicAer.get_backend("statevector_simulator") - - with self.assertWarns(DeprecationWarning): - qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) - with self.assertWarns(DeprecationWarning): - phase_est = HamiltonianPhaseEstimation(num_evaluation_qubits=6, quantum_instance=qi) - - result = phase_est.estimate( - hamiltonian=hamiltonian, - bound=bound, - evolution=evolution, - state_preparation=state_preparation, - ) - return result - - def test_from_bound(self): - """HamiltonianPhaseEstimation with bound""" - with self.assertWarns(DeprecationWarning): - for op_class in ("SummedOp", "MatrixOp"): - result = self._setup_from_bound(MatrixEvolution(), op_class) - cutoff = 0.01 - phases = result.filter_phases(cutoff) - with self.subTest(f"test phases has the correct length: {op_class}"): - self.assertEqual(len(phases), 2) - with self.subTest(f"test scaled phases are correct: {op_class}"): - self.assertEqual(list(phases.keys()), [1.5, -1.5]) - phases = result.filter_phases(cutoff, scaled=False) - with self.subTest(f"test unscaled phases are correct: {op_class}"): - self.assertEqual(list(phases.keys()), [0.25, 0.75]) - - def test_trotter_from_bound(self): - """HamiltonianPhaseEstimation with bound and Trotterization""" - with self.assertWarns(DeprecationWarning): - result = self._setup_from_bound( - PauliTrotterEvolution(trotter_mode="suzuki", reps=3), op_class="SummedOp" - ) - phase_dict = result.filter_phases(0.1) - phases = list(phase_dict.keys()) - with self.subTest("test phases has the correct length"): - self.assertEqual(len(phases), 2) - with self.subTest("test phases has correct values"): - self.assertAlmostEqual(phases[0], 1.5, delta=0.001) - self.assertAlmostEqual(phases[1], -1.5, delta=0.001) - - # sampler tests - def hamiltonian_pe_sampler( - self, - hamiltonian, - state_preparation=None, - num_evaluation_qubits=6, - evolution=None, - bound=None, - uses_opflow=True, - ): - """Run HamiltonianPhaseEstimation and return result with all phases.""" - sampler = Sampler() - phase_est = HamiltonianPhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, sampler=sampler - ) - if uses_opflow: - with self.assertWarns(DeprecationWarning): - result = phase_est.estimate( - hamiltonian=hamiltonian, - state_preparation=state_preparation, - evolution=evolution, - bound=bound, - ) - else: - result = phase_est.estimate( - hamiltonian=hamiltonian, - state_preparation=state_preparation, - evolution=evolution, - bound=bound, - ) - return result - - @data(MatrixExponential(), SuzukiTrotter(reps=4)) - def test_pauli_sum_1_sampler(self, evolution): - """Two eigenvalues from Pauli sum with X, Z""" - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp(SparsePauliOp.from_list([("X", 0.5), ("Z", 1)])) - state_preparation = QuantumCircuit(1).compose(HGate()) - - result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evolution) - phase_dict = result.filter_phases(0.162, as_float=True) - phases = list(phase_dict.keys()) - phases.sort() - - self.assertAlmostEqual(phases[0], -1.125, delta=0.001) - self.assertAlmostEqual(phases[1], 1.125, delta=0.001) - - @data(MatrixExponential(), SuzukiTrotter(reps=3)) - def test_pauli_sum_2_sampler(self, evolution): - """Two eigenvalues from Pauli sum with X, Y, Z""" - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp(SparsePauliOp.from_list([("X", 0.5), ("Z", 1), ("Y", 1)])) - state_preparation = None - - result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evolution) - phase_dict = result.filter_phases(0.1, as_float=True) - phases = list(phase_dict.keys()) - phases.sort() - - self.assertAlmostEqual(phases[0], -1.484, delta=0.001) - self.assertAlmostEqual(phases[1], 1.484, delta=0.001) - - def test_single_pauli_op_sampler(self): - """Two eigenvalues from Pauli sum with X, Y, Z""" - hamiltonian = SparsePauliOp(Pauli("Z")) - state_preparation = None - - result = self.hamiltonian_pe_sampler( - hamiltonian, state_preparation, evolution=None, uses_opflow=False - ) - eigv = result.most_likely_eigenvalue - with self.subTest("First eigenvalue"): - self.assertAlmostEqual(eigv, 1.0, delta=0.001) - - state_preparation = QuantumCircuit(1).compose(XGate()) - - result = self.hamiltonian_pe_sampler( - hamiltonian, state_preparation, bound=1.05, uses_opflow=False - ) - eigv = result.most_likely_eigenvalue - with self.subTest("Second eigenvalue"): - self.assertAlmostEqual(eigv, -0.98, delta=0.01) - - @data( - Statevector(QuantumCircuit(2).compose(IGate()).compose(HGate())), - QuantumCircuit(2).compose(IGate()).compose(HGate()), - ) - def test_H2_hamiltonian_sampler(self, state_preparation): - """Test H2 hamiltonian""" - - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp( - SparsePauliOp.from_list( - [ - ("II", -1.0523732457728587), - ("IZ", 0.3979374248431802), - ("ZI", -0.3979374248431802), - ("ZZ", -0.011280104256235324), - ("XX", 0.18093119978423147), - ] - ) - ) - - evo = SuzukiTrotter(reps=4) - result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evo) - with self.subTest("Most likely eigenvalues"): - self.assertAlmostEqual(result.most_likely_eigenvalue, -1.855, delta=0.001) - with self.subTest("Most likely phase"): - self.assertAlmostEqual(result.phase, 0.5937, delta=0.001) - with self.subTest("All eigenvalues"): - phase_dict = result.filter_phases(0.1) - phases = sorted(phase_dict.keys()) - self.assertAlmostEqual(phases[0], -1.8551, delta=0.001) - self.assertAlmostEqual(phases[1], -1.2376, delta=0.001) - self.assertAlmostEqual(phases[2], -0.8979, delta=0.001) - - def test_matrix_evolution_sampler(self): - """1Q Hamiltonian with MatrixEvolution""" - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp(SparsePauliOp.from_list([("X", 0.5), ("Y", 0.6), ("I", 0.7)])) - state_preparation = None - result = self.hamiltonian_pe_sampler( - hamiltonian, state_preparation, evolution=MatrixExponential() - ) - phase_dict = result.filter_phases(0.2, as_float=True) - phases = sorted(phase_dict.keys()) - self.assertAlmostEqual(phases[0], -0.090, delta=0.001) - self.assertAlmostEqual(phases[1], 1.490, delta=0.001) - - -@ddt -class TestPhaseEstimation(QiskitAlgorithmsTestCase): - """Evolution tests.""" - - def one_phase( - self, - unitary_circuit, - state_preparation=None, - backend_type=None, - phase_estimator=None, - num_iterations=6, - ): - """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, - `state_preparation`. Return the estimated phase as a value in :math:`[0,1)`. - """ - if backend_type is None: - backend_type = "qasm_simulator" - backend = qiskit.BasicAer.get_backend(backend_type) - if phase_estimator is None: - phase_estimator = IterativePhaseEstimation - - with self.assertWarns(DeprecationWarning): - qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) - - with self.assertWarns(DeprecationWarning): - - if phase_estimator == IterativePhaseEstimation: - p_est = IterativePhaseEstimation(num_iterations=num_iterations, quantum_instance=qi) - elif phase_estimator == PhaseEstimation: - p_est = PhaseEstimation(num_evaluation_qubits=6, quantum_instance=qi) - else: - raise ValueError("Unrecognized phase_estimator") - - result = p_est.estimate(unitary=unitary_circuit, state_preparation=state_preparation) - phase = result.phase - return phase - - @data( - (X.to_circuit(), 0.5, "statevector_simulator", IterativePhaseEstimation), - (X.to_circuit(), 0.5, "qasm_simulator", IterativePhaseEstimation), - (None, 0.0, "qasm_simulator", IterativePhaseEstimation), - (X.to_circuit(), 0.5, "qasm_simulator", PhaseEstimation), - (None, 0.0, "qasm_simulator", PhaseEstimation), - (X.to_circuit(), 0.5, "statevector_simulator", PhaseEstimation), - ) - @unpack - def test_qpe_Z(self, state_preparation, expected_phase, backend_type, phase_estimator): - """eigenproblem Z, |0> and |1>""" - unitary_circuit = Z.to_circuit() - with self.assertWarns(DeprecationWarning): - phase = self.one_phase( - unitary_circuit, - state_preparation, - backend_type=backend_type, - phase_estimator=phase_estimator, - ) - self.assertEqual(phase, expected_phase) - - @data( - (H.to_circuit(), 0.0, IterativePhaseEstimation), - ((H @ X).to_circuit(), 0.5, IterativePhaseEstimation), - (H.to_circuit(), 0.0, PhaseEstimation), - ((H @ X).to_circuit(), 0.5, PhaseEstimation), - ) - @unpack - def test_qpe_X_plus_minus(self, state_preparation, expected_phase, phase_estimator): - """eigenproblem X, (|+>, |->)""" - unitary_circuit = X.to_circuit() - with self.assertWarns(DeprecationWarning): - phase = self.one_phase( - unitary_circuit, state_preparation, phase_estimator=phase_estimator - ) - self.assertEqual(phase, expected_phase) - - @data( - (X.to_circuit(), 0.125, IterativePhaseEstimation), - (I.to_circuit(), 0.875, IterativePhaseEstimation), - (X.to_circuit(), 0.125, PhaseEstimation), - (I.to_circuit(), 0.875, PhaseEstimation), - ) - @unpack - def test_qpe_RZ(self, state_preparation, expected_phase, phase_estimator): - """eigenproblem RZ, (|0>, |1>)""" - alpha = np.pi / 2 - unitary_circuit = QuantumCircuit(1) - unitary_circuit.rz(alpha, 0) - with self.assertWarns(DeprecationWarning): - phase = self.one_phase( - unitary_circuit, state_preparation, phase_estimator=phase_estimator - ) - self.assertEqual(phase, expected_phase) - - def test_check_num_iterations(self): - """test check for num_iterations greater than zero""" - unitary_circuit = X.to_circuit() - state_preparation = None - with self.assertRaises(ValueError): - self.one_phase(unitary_circuit, state_preparation, num_iterations=-1) - - def phase_estimation( - self, - unitary_circuit, - state_preparation=None, - num_evaluation_qubits=6, - backend=None, - construct_circuit=False, - ): - """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, - `state_preparation`. Return all results - """ - if backend is None: - backend = qiskit.BasicAer.get_backend("statevector_simulator") - - with self.assertWarns(DeprecationWarning): - qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) - with self.assertWarns(DeprecationWarning): - phase_est = PhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, quantum_instance=qi - ) - if construct_circuit: - pe_circuit = phase_est.construct_circuit(unitary_circuit, state_preparation) - result = phase_est.estimate_from_pe_circuit(pe_circuit, unitary_circuit.num_qubits) - else: - result = phase_est.estimate( - unitary=unitary_circuit, state_preparation=state_preparation - ) - - return result - - @data(True, False) - def test_qpe_Zplus(self, construct_circuit): - """superposition eigenproblem Z, |+>""" - unitary_circuit = Z.to_circuit() - state_preparation = H.to_circuit() # prepare |+> - - with self.assertWarns(DeprecationWarning): - result = self.phase_estimation( - unitary_circuit, - state_preparation, - backend=qiskit.BasicAer.get_backend("statevector_simulator"), - construct_circuit=construct_circuit, - ) - - phases = result.filter_phases(1e-15, as_float=True) - with self.subTest("test phases has correct values"): - self.assertEqual(list(phases.keys()), [0.0, 0.5]) - - with self.subTest("test phases has correct probabilities"): - np.testing.assert_allclose(list(phases.values()), [0.5, 0.5]) - - with self.subTest("test bitstring representation"): - phases = result.filter_phases(1e-15, as_float=False) - self.assertEqual(list(phases.keys()), ["000000", "100000"]) - - # sampler tests - def one_phase_sampler( - self, - unitary_circuit, - state_preparation=None, - phase_estimator=None, - num_iterations=6, - shots=None, - ): - """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, - `state_preparation`. Return the estimated phase as a value in :math:`[0,1)`. - """ - if shots is not None: - options = {"shots": shots} - else: - options = {} - sampler = Sampler(options=options) - if phase_estimator is None: - phase_estimator = IterativePhaseEstimation - if phase_estimator == IterativePhaseEstimation: - p_est = IterativePhaseEstimation(num_iterations=num_iterations, sampler=sampler) - elif phase_estimator == PhaseEstimation: - p_est = PhaseEstimation(num_evaluation_qubits=6, sampler=sampler) - else: - raise ValueError("Unrecognized phase_estimator") - result = p_est.estimate(unitary=unitary_circuit, state_preparation=state_preparation) - phase = result.phase - return phase - - @data( - (QuantumCircuit(1).compose(XGate()), 0.5, None, IterativePhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.5, 1000, IterativePhaseEstimation), - (None, 0.0, 1000, IterativePhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.5, 1000, PhaseEstimation), - (None, 0.0, 1000, PhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.5, None, PhaseEstimation), - ) - @unpack - def test_qpe_Z_sampler(self, state_preparation, expected_phase, shots, phase_estimator): - """eigenproblem Z, |0> and |1>""" - unitary_circuit = QuantumCircuit(1).compose(ZGate()) - phase = self.one_phase_sampler( - unitary_circuit, - state_preparation=state_preparation, - phase_estimator=phase_estimator, - shots=shots, - ) - self.assertEqual(phase, expected_phase) - - @data( - (QuantumCircuit(1).compose(HGate()), 0.0, IterativePhaseEstimation), - (QuantumCircuit(1).compose(HGate()).compose(ZGate()), 0.5, IterativePhaseEstimation), - (QuantumCircuit(1).compose(HGate()), 0.0, PhaseEstimation), - (QuantumCircuit(1).compose(HGate()).compose(ZGate()), 0.5, PhaseEstimation), - ) - @unpack - def test_qpe_X_plus_minus_sampler(self, state_preparation, expected_phase, phase_estimator): - """eigenproblem X, (|+>, |->)""" - unitary_circuit = QuantumCircuit(1).compose(XGate()) - phase = self.one_phase_sampler( - unitary_circuit, - state_preparation, - phase_estimator, - ) - self.assertEqual(phase, expected_phase) - - @data( - (QuantumCircuit(1).compose(XGate()), 0.125, IterativePhaseEstimation), - (QuantumCircuit(1).compose(IGate()), 0.875, IterativePhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.125, PhaseEstimation), - (QuantumCircuit(1).compose(IGate()), 0.875, PhaseEstimation), - ) - @unpack - def test_qpe_RZ_sampler(self, state_preparation, expected_phase, phase_estimator): - """eigenproblem RZ, (|0>, |1>)""" - alpha = np.pi / 2 - unitary_circuit = QuantumCircuit(1) - unitary_circuit.rz(alpha, 0) - phase = self.one_phase_sampler( - unitary_circuit, - state_preparation, - phase_estimator, - ) - self.assertEqual(phase, expected_phase) - - @data( - ((X ^ X).to_circuit(), 0.25, IterativePhaseEstimation), - ((I ^ X).to_circuit(), 0.125, IterativePhaseEstimation), - ((X ^ X).to_circuit(), 0.25, PhaseEstimation), - ((I ^ X).to_circuit(), 0.125, PhaseEstimation), - ) - @unpack - def test_qpe_two_qubit_unitary(self, state_preparation, expected_phase, phase_estimator): - """two qubit unitary T ^ T""" - unitary_circuit = QuantumCircuit(2) - unitary_circuit.t(0) - unitary_circuit.t(1) - phase = self.one_phase_sampler( - unitary_circuit, - state_preparation, - phase_estimator, - ) - self.assertEqual(phase, expected_phase) - - def test_check_num_iterations_sampler(self): - """test check for num_iterations greater than zero""" - unitary_circuit = QuantumCircuit(1).compose(XGate()) - state_preparation = None - with self.assertRaises(ValueError): - self.one_phase_sampler(unitary_circuit, state_preparation, num_iterations=-1) - - def test_phase_estimation_scale_from_operator(self): - """test that PhaseEstimationScale from_pauli_sum works with Operator""" - circ = QuantumCircuit(2) - op = Operator(circ) - scale = PhaseEstimationScale.from_pauli_sum(op) - self.assertEqual(scale._bound, 4.0) - - def phase_estimation_sampler( - self, - unitary_circuit, - sampler: Sampler, - state_preparation=None, - num_evaluation_qubits=6, - construct_circuit=False, - ): - """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, - `state_preparation`. Return all results - """ - phase_est = PhaseEstimation(num_evaluation_qubits=num_evaluation_qubits, sampler=sampler) - if construct_circuit: - pe_circuit = phase_est.construct_circuit(unitary_circuit, state_preparation) - result = phase_est.estimate_from_pe_circuit(pe_circuit, unitary_circuit.num_qubits) - else: - result = phase_est.estimate( - unitary=unitary_circuit, state_preparation=state_preparation - ) - return result - - @data(True, False) - def test_qpe_Zplus_sampler(self, construct_circuit): - """superposition eigenproblem Z, |+>""" - unitary_circuit = QuantumCircuit(1).compose(ZGate()) - state_preparation = QuantumCircuit(1).compose(HGate()) # prepare |+> - sampler = Sampler() - result = self.phase_estimation_sampler( - unitary_circuit, - sampler, - state_preparation, - construct_circuit=construct_circuit, - ) - - phases = result.filter_phases(1e-15, as_float=True) - with self.subTest("test phases has correct values"): - self.assertEqual(list(phases.keys()), [0.0, 0.5]) - - with self.subTest("test phases has correct probabilities"): - np.testing.assert_allclose(list(phases.values()), [0.5, 0.5]) - - with self.subTest("test bitstring representation"): - phases = result.filter_phases(1e-15, as_float=False) - self.assertEqual(list(phases.keys()), ["000000", "100000"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_qaoa.py b/test/python/algorithms/test_qaoa.py deleted file mode 100644 index 5edb525022a3..000000000000 --- a/test/python/algorithms/test_qaoa.py +++ /dev/null @@ -1,410 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# 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. - -"""Test QAOA""" - -import unittest -import warnings -from test.python.algorithms import QiskitAlgorithmsTestCase - -from functools import partial -import math -import numpy as np -from scipy.optimize import minimize as scipy_minimize -from ddt import ddt, idata, unpack -import rustworkx as rx - -from qiskit.algorithms import QAOA -from qiskit.algorithms.optimizers import COBYLA, NELDER_MEAD - -from qiskit.opflow import I, X, Z, PauliSumOp - -from qiskit import BasicAer, QuantumCircuit, QuantumRegister - -from qiskit.circuit import Parameter -from qiskit.quantum_info import Pauli -from qiskit.utils import QuantumInstance, algorithm_globals - -W1 = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) -P1 = 1 -M1 = (I ^ I ^ I ^ X) + (I ^ I ^ X ^ I) + (I ^ X ^ I ^ I) + (X ^ I ^ I ^ I) -S1 = {"0101", "1010"} - - -W2 = np.array( - [ - [0.0, 8.0, -9.0, 0.0], - [8.0, 0.0, 7.0, 9.0], - [-9.0, 7.0, 0.0, -8.0], - [0.0, 9.0, -8.0, 0.0], - ] -) -P2 = 1 -M2 = None -S2 = {"1011", "0100"} - -CUSTOM_SUPERPOSITION = [1 / math.sqrt(15)] * 15 + [0] - - -@ddt -class TestQAOA(QiskitAlgorithmsTestCase): - """Test QAOA with MaxCut.""" - - def setUp(self): - super().setUp() - self.seed = 10598 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - with self.assertWarns(DeprecationWarning): - self.qasm_simulator = QuantumInstance( - BasicAer.get_backend("qasm_simulator"), - shots=4096, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - self.statevector_simulator = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - - @idata( - [ - [W1, P1, M1, S1, False], - [W2, P2, M2, S2, False], - [W1, P1, M1, S1, True], - [W2, P2, M2, S2, True], - ] - ) - @unpack - def test_qaoa(self, w, prob, m, solutions, convert_to_matrix_op): - """QAOA test""" - self.log.debug("Testing %s-step QAOA with MaxCut on graph\n%s", prob, w) - - qubit_op, _ = self._get_operator(w) - - if convert_to_matrix_op: - with self.assertWarns(DeprecationWarning): - qubit_op = qubit_op.to_matrix_op() - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA(COBYLA(), prob, mixer=m, quantum_instance=self.statevector_simulator) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - @idata( - [ - [W1, P1, S1, False], - [W2, P2, S2, False], - [W1, P1, S1, True], - [W2, P2, S2, True], - ] - ) - @unpack - def test_qaoa_qc_mixer(self, w, prob, solutions, convert_to_matrix_op): - """QAOA test with a mixer as a parameterized circuit""" - self.log.debug( - "Testing %s-step QAOA with MaxCut on graph with a mixer as a parameterized circuit\n%s", - prob, - w, - ) - - optimizer = COBYLA() - qubit_op, _ = self._get_operator(w) - if convert_to_matrix_op: - with self.assertWarns(DeprecationWarning): - qubit_op = qubit_op.to_matrix_op() - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - theta = Parameter("θ") - mixer.rx(theta, range(num_qubits)) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA(optimizer, prob, mixer=mixer, quantum_instance=self.statevector_simulator) - - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - def test_qaoa_qc_mixer_many_parameters(self): - """QAOA test with a mixer as a parameterized circuit with the num of parameters > 1.""" - optimizer = COBYLA() - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - for i in range(num_qubits): - theta = Parameter("θ" + str(i)) - mixer.rx(theta, range(num_qubits)) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA(optimizer, reps=2, mixer=mixer, quantum_instance=self.statevector_simulator) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - self.log.debug(x) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, S1) - - def test_qaoa_qc_mixer_no_parameters(self): - """QAOA test with a mixer as a parameterized circuit with zero parameters.""" - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - # just arbitrary circuit - mixer.rx(np.pi / 2, range(num_qubits)) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA(COBYLA(), reps=1, mixer=mixer, quantum_instance=self.statevector_simulator) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - # we just assert that we get a result, it is not meaningful. - self.assertIsNotNone(result.eigenstate) - - def test_change_operator_size(self): - """QAOA change operator size test""" - qubit_op, _ = self._get_operator( - np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) - ) - with self.assertWarns(DeprecationWarning): - qaoa = QAOA(COBYLA(), 1, quantum_instance=self.statevector_simulator) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 4x4"): - self.assertIn(graph_solution, {"0101", "1010"}) - - qubit_op, _ = self._get_operator( - np.array( - [ - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - ] - ) - ) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 6x6"): - self.assertIn(graph_solution, {"010101", "101010"}) - - @idata([[W2, S2, None], [W2, S2, [0.0, 0.0]], [W2, S2, [1.0, 0.8]]]) - @unpack - def test_qaoa_initial_point(self, w, solutions, init_pt): - """Check first parameter value used is initial point as expected""" - qubit_op, _ = self._get_operator(w) - - first_pt = [] - - def cb_callback(eval_count, parameters, mean, std): - nonlocal first_pt - if eval_count == 1: - first_pt = list(parameters) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA( - COBYLA(), - initial_point=init_pt, - callback=cb_callback, - quantum_instance=self.statevector_simulator, - ) - - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - - with self.subTest("Initial Point"): - # If None the preferred random initial point of QAOA variational form - if init_pt is None: - self.assertLess(result.eigenvalue, -0.97) - else: - self.assertListEqual(init_pt, first_pt) - - with self.subTest("Solution"): - self.assertIn(graph_solution, solutions) - - @idata([[W2, None], [W2, [1.0] + 15 * [0.0]], [W2, CUSTOM_SUPERPOSITION]]) - @unpack - def test_qaoa_initial_state(self, w, init_state): - """QAOA initial state test""" - optimizer = COBYLA() - qubit_op, _ = self._get_operator(w) - - init_pt = np.asarray([0.0, 0.0]) # Avoid generating random initial point - - if init_state is None: - initial_state = None - else: - initial_state = QuantumCircuit(QuantumRegister(4, "q")) - initial_state.initialize(init_state, initial_state.qubits) - - zero_init_state = QuantumCircuit(QuantumRegister(qubit_op.num_qubits, "q")) - - with self.assertWarns(DeprecationWarning): - qaoa_zero_init_state = QAOA( - optimizer=optimizer, - initial_state=zero_init_state, - initial_point=init_pt, - quantum_instance=self.statevector_simulator, - ) - qaoa = QAOA( - optimizer=optimizer, - initial_state=initial_state, - initial_point=init_pt, - quantum_instance=self.statevector_simulator, - ) - zero_circuits = qaoa_zero_init_state.construct_circuit(init_pt, qubit_op) - custom_circuits = qaoa.construct_circuit(init_pt, qubit_op) - - self.assertEqual(len(zero_circuits), len(custom_circuits)) - - for zero_circ, custom_circ in zip(zero_circuits, custom_circuits): - - z_length = len(zero_circ.data) - c_length = len(custom_circ.data) - - self.assertGreaterEqual(c_length, z_length) - self.assertTrue(zero_circ.data == custom_circ.data[-z_length:]) - - custom_init_qc = QuantumCircuit(custom_circ.num_qubits) - custom_init_qc.data = custom_circ.data[0 : c_length - z_length] - - if initial_state is None: - original_init_qc = QuantumCircuit(qubit_op.num_qubits) - original_init_qc.h(range(qubit_op.num_qubits)) - else: - original_init_qc = initial_state - - with self.assertWarns(DeprecationWarning): - job_init_state = self.statevector_simulator.execute(original_init_qc) - job_qaoa_init_state = self.statevector_simulator.execute(custom_init_qc) - - statevector_original = job_init_state.get_statevector(original_init_qc) - statevector_custom = job_qaoa_init_state.get_statevector(custom_init_qc) - - self.assertListEqual(statevector_original.tolist(), statevector_custom.tolist()) - - def test_qaoa_random_initial_point(self): - """QAOA random initial point""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA( - optimizer=NELDER_MEAD(disp=True), reps=1, quantum_instance=self.qasm_simulator - ) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - self.assertLess(result.eigenvalue, -0.97) - - def test_qaoa_construct_circuit_update(self): - """Test updating operators with QAOA construct_circuit""" - with self.assertWarns(DeprecationWarning): - qaoa = QAOA() - ref = qaoa.construct_circuit([0, 0], I ^ Z)[0] - circ2 = qaoa.construct_circuit([0, 0], I ^ Z)[0] - self.assertEqual(circ2, ref) - circ3 = qaoa.construct_circuit([0, 0], Z ^ I)[0] - self.assertNotEqual(circ3, ref) - circ4 = qaoa.construct_circuit([0, 0], I ^ Z)[0] - self.assertEqual(circ4, ref) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - with self.assertWarns(DeprecationWarning): - qaoa = QAOA( - optimizer=partial(scipy_minimize, method="Nelder-Mead", options={"maxiter": 2}), - quantum_instance=self.statevector_simulator, - ) - result = qaoa.compute_minimum_eigenvalue(Z) - self.assertEqual(result.cost_function_evals, 4) - - def _get_operator(self, weight_matrix): - """Generate Hamiltonian for the max-cut problem of a graph. - - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - - Returns: - PauliSumOp: operator for the Hamiltonian - float: a constant shift for the obj function. - - """ - num_nodes = weight_matrix.shape[0] - pauli_list = [] - shift = 0 - for i in range(num_nodes): - for j in range(i): - if weight_matrix[i, j] != 0: - x_p = np.zeros(num_nodes, dtype=bool) - z_p = np.zeros(num_nodes, dtype=bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([0.5 * weight_matrix[i, j], Pauli((z_p, x_p))]) - shift -= 0.5 * weight_matrix[i, j] - opflow_list = [(pauli[1].to_label(), pauli[0]) for pauli in pauli_list] - - with self.assertWarns(DeprecationWarning): - return PauliSumOp.from_list(opflow_list), shift - - def _get_graph_solution(self, x: np.ndarray) -> str: - """Get graph solution from binary string. - - Args: - x : binary string as numpy array. - - Returns: - a graph solution as string. - """ - - return "".join([str(int(i)) for i in 1 - x]) - - def _sample_most_likely(self, state_vector): - """Compute the most likely binary string from state vector. - Args: - state_vector (numpy.ndarray or dict): state vector or counts. - - Returns: - numpy.ndarray: binary string as numpy.ndarray of ints. - """ - n = int(np.log2(state_vector.shape[0])) - k = np.argmax(np.abs(state_vector)) - x = np.zeros(n) - for i in range(n): - x[i] = k % 2 - k >>= 1 - return x - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_skip_qobj_validation.py b/test/python/algorithms/test_skip_qobj_validation.py index c5af2b7dc279..4ebf8a0fb7b0 100644 --- a/test/python/algorithms/test_skip_qobj_validation.py +++ b/test/python/algorithms/test_skip_qobj_validation.py @@ -107,7 +107,7 @@ def test_w_noise(self): # Asymmetric readout error on qubit-0 only try: from qiskit.providers.aer.noise import NoiseModel - from qiskit import Aer + from qiskit_aer import Aer self.backend = Aer.get_backend("qasm_simulator") diff --git a/test/python/algorithms/test_validation.py b/test/python/algorithms/test_validation.py deleted file mode 100644 index 90a1b9eed143..000000000000 --- a/test/python/algorithms/test_validation.py +++ /dev/null @@ -1,94 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# 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. - -"""Test Validation""" - -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit.utils.validation import ( - validate_in_set, - validate_min, - validate_min_exclusive, - validate_max, - validate_max_exclusive, - validate_range, - validate_range_exclusive, - validate_range_exclusive_min, - validate_range_exclusive_max, -) - - -class TestValidation(QiskitAlgorithmsTestCase): - """Validation tests.""" - - def test_validate_in_set(self): - """validate in set test""" - test_value = "value1" - with self.assertWarns(DeprecationWarning): - validate_in_set("test_value", test_value, {"value1", "value2"}) - with self.assertRaises(ValueError): - validate_in_set("test_value", test_value, {"value3", "value4"}) - - def test_validate_min(self): - """validate min test""" - test_value = 2.5 - with self.assertWarns(DeprecationWarning): - validate_min("test_value", test_value, -1) - validate_min("test_value", test_value, 2.5) - with self.assertRaises(ValueError): - validate_min("test_value", test_value, 4) - validate_min_exclusive("test_value", test_value, -1) - with self.assertRaises(ValueError): - validate_min_exclusive("test_value", test_value, 2.5) - with self.assertRaises(ValueError): - validate_min_exclusive("test_value", test_value, 4) - - def test_validate_max(self): - """validate max test""" - test_value = 2.5 - with self.assertWarns(DeprecationWarning): - with self.assertRaises(ValueError): - validate_max("test_value", test_value, -1) - validate_max("test_value", test_value, 2.5) - validate_max("test_value", test_value, 4) - with self.assertRaises(ValueError): - validate_max_exclusive("test_value", test_value, -1) - with self.assertRaises(ValueError): - validate_max_exclusive("test_value", test_value, 2.5) - validate_max_exclusive("test_value", test_value, 4) - - def test_validate_range(self): - """validate range test""" - test_value = 2.5 - with self.assertWarns(DeprecationWarning): - with self.assertRaises(ValueError): - validate_range("test_value", test_value, 0, 2) - with self.assertRaises(ValueError): - validate_range("test_value", test_value, 3, 4) - validate_range("test_value", test_value, 2.5, 3) - validate_range_exclusive("test_value", test_value, 0, 3) - with self.assertRaises(ValueError): - validate_range_exclusive("test_value", test_value, 0, 2.5) - validate_range_exclusive("test_value", test_value, 2.5, 3) - validate_range_exclusive_min("test_value", test_value, 0, 3) - with self.assertRaises(ValueError): - validate_range_exclusive_min("test_value", test_value, 2.5, 3) - validate_range_exclusive_min("test_value", test_value, 0, 2.5) - validate_range_exclusive_max("test_value", test_value, 2.5, 3) - with self.assertRaises(ValueError): - validate_range_exclusive_max("test_value", test_value, 0, 2.5) - validate_range_exclusive_max("test_value", test_value, 2.5, 3) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_vqd.py b/test/python/algorithms/test_vqd.py deleted file mode 100644 index e153ba50ddb7..000000000000 --- a/test/python/algorithms/test_vqd.py +++ /dev/null @@ -1,663 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# 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. - -"""Test VQD""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt, unpack - -from qiskit import BasicAer, QuantumCircuit -from qiskit.algorithms import VQD, AlgorithmError -from qiskit.algorithms.optimizers import ( - COBYLA, - L_BFGS_B, - SLSQP, -) -from qiskit.circuit.library import EfficientSU2, RealAmplitudes, TwoLocal -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.opflow import ( - AerPauliExpectation, - I, - MatrixExpectation, - MatrixOp, - PauliExpectation, - PauliSumOp, - PrimitiveOp, - X, - Z, -) - -from qiskit.utils import QuantumInstance, algorithm_globals, has_aer -from qiskit.test import slow_test - - -if has_aer(): - from qiskit import Aer - - -@ddt -class TestVQD(QiskitAlgorithmsTestCase): - """Test VQD""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - self.h2_energy = -1.85727503 - self.h2_energy_excited = [-1.85727503, -1.24458455] - - self.test_results = [-3, -1] - - self.ryrz_wavefunction = TwoLocal( - rotation_blocks=["ry", "rz"], entanglement_blocks="cz", reps=1 - ) - self.ry_wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - with self.assertWarns(DeprecationWarning): - self.h2_op = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - self.test_op = MatrixOp(np.diagflat([3, 5, -1, 0.8, 0.2, 2, 1, -3])).to_pauli_op() - self.qasm_simulator = QuantumInstance( - BasicAer.get_backend("qasm_simulator"), - shots=2048, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - self.statevector_simulator = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - shots=1, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - - @slow_test - def test_basic_aer_statevector(self): - """Test the VQD on BasicAer's statevector simulator.""" - wavefunction = self.ryrz_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=2, - ansatz=wavefunction, - optimizer=COBYLA(), - quantum_instance=QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - basis_gates=["u1", "u2", "u3", "cx", "id"], - coupling_map=[[0, 1]], - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ), - ) - - result = vqd.compute_eigenvalues(operator=self.h2_op) - - with self.subTest(msg="test eigenvalue"): - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=1 - ) - - with self.subTest(msg="test dimension of optimal point"): - self.assertEqual(len(result.optimal_point[-1]), 8) - - with self.subTest(msg="assert cost_function_evals is set"): - self.assertIsNotNone(result.cost_function_evals) - - with self.subTest(msg="assert optimizer_time is set"): - self.assertIsNotNone(result.optimizer_time) - - def test_mismatching_num_qubits(self): - """Ensuring circuit and operator mismatch is caught""" - wavefunction = QuantumCircuit(1) - optimizer = SLSQP(maxiter=50) - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=1, - ansatz=wavefunction, - optimizer=optimizer, - quantum_instance=self.statevector_simulator, - ) - with self.assertRaises(AlgorithmError): - _ = vqd.compute_eigenvalues(operator=self.h2_op) - - @data( - (MatrixExpectation(), 1), - (AerPauliExpectation(), 1), - (PauliExpectation(), 2), - ) - @unpack - def test_construct_circuit(self, expectation, num_circuits): - """Test construct circuits returns QuantumCircuits and the right number of them.""" - try: - wavefunction = EfficientSU2(2, reps=1) - - with self.assertWarns(DeprecationWarning): - vqd = VQD(k=2, ansatz=wavefunction, expectation=expectation) - params = [0] * wavefunction.num_parameters - circuits = vqd.construct_circuit(parameter=params, operator=self.h2_op) - self.assertEqual(len(circuits), num_circuits) - - for circuit in circuits: - self.assertIsInstance(circuit, QuantumCircuit) - - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - return - - def test_missing_varform_params(self): - """Test specifying a variational form with no parameters raises an error.""" - - circuit = QuantumCircuit(self.h2_op.num_qubits) - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=1, ansatz=circuit, quantum_instance=BasicAer.get_backend("statevector_simulator") - ) - with self.assertRaises(RuntimeError): - vqd.compute_eigenvalues(operator=self.h2_op) - - def test_basic_aer_qasm(self): - """Test the VQD on BasicAer's QASM simulator.""" - optimizer = COBYLA(maxiter=1000) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=self.qasm_simulator, - ) - # TODO benchmark this later. - result = vqd.compute_eigenvalues(operator=self.h2_op) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=1 - ) - - @unittest.skipUnless(has_aer(), "qiskit-aer doesn't appear to be installed.") - def test_with_aer_statevector(self): - """Test VQD with Aer's statevector_simulator.""" - backend = Aer.get_backend("aer_simulator_statevector") - wavefunction = self.ry_wavefunction - optimizer = L_BFGS_B() - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=2, - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=quantum_instance, - ) - result = vqd.compute_eigenvalues(operator=self.h2_op) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - - @unittest.skipUnless(has_aer(), "qiskit-aer doesn't appear to be installed.") - def test_with_aer_qasm(self): - """Test VQD with Aer's qasm_simulator.""" - backend = Aer.get_backend("aer_simulator") - optimizer = COBYLA(maxiter=1000) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=2, - ansatz=wavefunction, - optimizer=optimizer, - expectation=PauliExpectation(), - quantum_instance=quantum_instance, - ) - result = vqd.compute_eigenvalues(operator=self.h2_op) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=1 - ) - - @unittest.skipUnless(has_aer(), "qiskit-aer doesn't appear to be installed.") - def test_with_aer_qasm_snapshot_mode(self): - """Test the VQD using Aer's qasm_simulator snapshot mode.""" - - backend = Aer.get_backend("aer_simulator") - optimizer = COBYLA(maxiter=400) - wavefunction = self.ryrz_wavefunction - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - shots=100, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=2, - ansatz=wavefunction, - optimizer=optimizer, - expectation=AerPauliExpectation(), - quantum_instance=quantum_instance, - ) - result = vqd.compute_eigenvalues(operator=self.test_op) - - np.testing.assert_array_almost_equal(result.eigenvalues.real, self.test_results, decimal=1) - - def test_callback(self): - """Test the callback on VQD.""" - history = {"eval_count": [], "parameters": [], "mean": [], "std": [], "step": []} - - def store_intermediate_result(eval_count, parameters, mean, std, step): - history["eval_count"].append(eval_count) - history["parameters"].append(parameters) - history["mean"].append(mean) - history["std"].append(std) - history["step"].append(step) - - optimizer = COBYLA(maxiter=3) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - ansatz=wavefunction, - optimizer=optimizer, - callback=store_intermediate_result, - quantum_instance=self.qasm_simulator, - ) - vqd.compute_eigenvalues(operator=self.h2_op) - - self.assertTrue(all(isinstance(count, int) for count in history["eval_count"])) - self.assertTrue(all(isinstance(mean, float) for mean in history["mean"])) - self.assertTrue(all(isinstance(std, float) for std in history["std"])) - self.assertTrue(all(isinstance(count, int) for count in history["step"])) - for params in history["parameters"]: - self.assertTrue(all(isinstance(param, float) for param in params)) - - ref_eval_count = [1, 2, 3, 1, 2, 3] - ref_mean = [-1.063, -1.457, -1.360, 37.340, 48.543, 28.586] - ref_std = [0.011, 0.010, 0.014, 0.011, 0.010, 0.015] - ref_step = [1, 1, 1, 2, 2, 2] - - np.testing.assert_array_almost_equal(history["eval_count"], ref_eval_count, decimal=0) - np.testing.assert_array_almost_equal(history["mean"], ref_mean, decimal=2) - np.testing.assert_array_almost_equal(history["std"], ref_std, decimal=2) - np.testing.assert_array_almost_equal(history["step"], ref_step, decimal=0) - - def test_reuse(self): - """Test re-using a VQD algorithm instance.""" - - with self.assertWarns(DeprecationWarning): - vqd = VQD(k=1) - - with self.subTest(msg="assert running empty raises AlgorithmError"): - with self.assertWarns(DeprecationWarning), self.assertRaises(AlgorithmError): - _ = vqd.compute_eigenvalues(operator=self.h2_op) - - ansatz = TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - vqd.ansatz = ansatz - - with self.subTest(msg="assert missing operator raises AlgorithmError"): - with self.assertWarns(DeprecationWarning), self.assertRaises(AlgorithmError): - _ = vqd.compute_eigenvalues(operator=self.h2_op) - - with self.assertWarns(DeprecationWarning): - vqd.expectation = MatrixExpectation() - vqd.quantum_instance = self.statevector_simulator - - with self.subTest(msg="assert VQE works once all info is available"): - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(operator=self.h2_op) - np.testing.assert_array_almost_equal(result.eigenvalues.real, self.h2_energy, decimal=2) - - with self.assertWarns(DeprecationWarning): - operator = PrimitiveOp( - np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 2, 0], [0, 0, 0, 3]]) - ) - - with self.subTest(msg="assert minimum eigensolver interface works"): - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(operator=operator) - self.assertAlmostEqual(result.eigenvalues.real[0], -1.0, places=5) - - def test_vqd_optimizer(self): - """Test running same VQD twice to re-use optimizer, then switch optimizer""" - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=2, - optimizer=SLSQP(), - quantum_instance=QuantumInstance(BasicAer.get_backend("statevector_simulator")), - ) - - def run_check(): - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(operator=self.h2_op) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=3 - ) - - run_check() - - with self.subTest("Optimizer re-use"): - run_check() - - with self.subTest("Optimizer replace"): - vqd.optimizer = L_BFGS_B() - run_check() - - @data(MatrixExpectation(), None) - def test_backend_change(self, user_expectation): - """Test that VQE works when backend changes.""" - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=1, - ansatz=TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz"), - optimizer=SLSQP(maxiter=2), - expectation=user_expectation, - quantum_instance=BasicAer.get_backend("statevector_simulator"), - ) - result0 = vqd.compute_eigenvalues(operator=self.h2_op) - if user_expectation is not None: - with self.subTest("User expectation kept."): - self.assertEqual(vqd.expectation, user_expectation) - - with self.assertWarns(DeprecationWarning): - vqd.quantum_instance = BasicAer.get_backend("qasm_simulator") - # works also if no expectation is set, since it will be determined automatically - - result1 = vqd.compute_eigenvalues(operator=self.h2_op) - - if user_expectation is not None: - with self.subTest("Change backend with user expectation, it is kept."): - self.assertEqual(vqd.expectation, user_expectation) - - with self.subTest("Check results."): - self.assertEqual(len(result0.optimal_point), len(result1.optimal_point)) - - def test_set_ansatz_to_none(self): - """Tests that setting the ansatz to None results in the default behavior""" - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=1, - ansatz=self.ryrz_wavefunction, - optimizer=L_BFGS_B(), - quantum_instance=self.statevector_simulator, - ) - vqd.ansatz = None - self.assertIsInstance(vqd.ansatz, RealAmplitudes) - - def test_set_optimizer_to_none(self): - """Tests that setting the optimizer to None results in the default behavior""" - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=1, - ansatz=self.ryrz_wavefunction, - optimizer=L_BFGS_B(), - quantum_instance=self.statevector_simulator, - ) - vqd.optimizer = None - self.assertIsInstance(vqd.optimizer, SLSQP) - - def test_aux_operators_list(self): - """Test list-based aux_operators.""" - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD(k=2, ansatz=wavefunction, quantum_instance=self.statevector_simulator) - - # Start with an empty list - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=[]) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Go again with two auxiliary operators - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2, places=2) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][0], 0, places=2) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=extra_ops) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2, places=2) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][0], 0, places=2) - self.assertEqual(result.aux_operator_eigenvalues[0][2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[0][3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[0][3][1], 0.0) - - def test_aux_operators_dict(self): - """Test dictionary compatibility of aux_operators""" - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD(ansatz=wavefunction, quantum_instance=self.statevector_simulator) - - # Start with an empty dictionary - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators={}) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Go again with two auxiliary operators - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(len(result.eigenstates), 2) - self.assertEqual(result.eigenvalues.dtype, np.complex128) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][0], 0, places=1) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=extra_ops) - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(len(result.eigenstates), 2) - self.assertEqual(result.eigenvalues.dtype, np.complex128) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 3) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operator_eigenvalues[0]["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operator_eigenvalues[0].keys()) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["zero_operator"][1], 0.0) - - def test_aux_operator_std_dev_pauli(self): - """Test non-zero standard deviations of aux operators with PauliExpectation.""" - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - ansatz=wavefunction, - expectation=PauliExpectation(), - initial_point=[ - 1.70256666, - -5.34843975, - -0.39542903, - 5.99477786, - -2.74374986, - -4.85284669, - 0.2442925, - -1.51638917, - ], - optimizer=COBYLA(maxiter=0), - quantum_instance=self.qasm_simulator, - ) - - with self.assertWarns(DeprecationWarning): - # Go again with two auxiliary operators - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2.0, places=1) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][0], 0.0019531249999999445, places=1 - ) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][1], 0.015183867579396111, places=1 - ) - - # Go again with additional None and zero operators - aux_ops = [*aux_ops, None, 0] - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2.0, places=1) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][0], 0.0019531249999999445, places=1 - ) - self.assertEqual(result.aux_operator_eigenvalues[0][2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[0][3][0], 0.0) - # # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][1], 0.01548658094658011, places=1 - ) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][2][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][3][1], 0.0) - - @unittest.skipUnless(has_aer(), "qiskit-aer doesn't appear to be installed.") - def test_aux_operator_std_dev_aer_pauli(self): - """Test non-zero standard deviations of aux operators with AerPauliExpectation.""" - wavefunction = self.ry_wavefunction - with self.assertWarns(DeprecationWarning): - vqd = VQD( - ansatz=wavefunction, - expectation=AerPauliExpectation(), - optimizer=COBYLA(maxiter=0), - quantum_instance=QuantumInstance( - backend=Aer.get_backend("qasm_simulator"), - shots=1, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ), - ) - - with self.assertWarns(DeprecationWarning): - # Go again with two auxiliary operators - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2.0, places=1) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][0], 0.6698863565455391, places=1 - ) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0, places=6) - - # Go again with additional None and zero operators - aux_ops = [*aux_ops, None, 0] - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - self.assertEqual(len(result.aux_operator_eigenvalues[-1]), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2.0, places=6) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][0], 0.6036400943063891, places=6 - ) - self.assertEqual(result.aux_operator_eigenvalues[0][2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[0][3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][2][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][3][1], 0.0) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_vqe.py b/test/python/algorithms/test_vqe.py deleted file mode 100644 index 781728400f2b..000000000000 --- a/test/python/algorithms/test_vqe.py +++ /dev/null @@ -1,856 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# 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. - -"""Test VQE""" - -import logging -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from test.python.transpiler._dummy_passes import DummyAP - -from functools import partial -import numpy as np -from scipy.optimize import minimize as scipy_minimize -from ddt import data, ddt, unpack - -from qiskit import BasicAer, QuantumCircuit -from qiskit.algorithms import VQE, AlgorithmError -from qiskit.algorithms.optimizers import ( - CG, - COBYLA, - L_BFGS_B, - P_BFGS, - QNSPSA, - SLSQP, - SPSA, - TNC, - OptimizerResult, -) -from qiskit.circuit.library import EfficientSU2, RealAmplitudes, TwoLocal -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.opflow import ( - AerPauliExpectation, - Gradient, - I, - MatrixExpectation, - PauliExpectation, - PauliSumOp, - PrimitiveOp, - TwoQubitReduction, - X, - Z, -) -from qiskit.quantum_info import Statevector -from qiskit.transpiler import PassManager, PassManagerConfig -from qiskit.transpiler.preset_passmanagers import level_1_pass_manager -from qiskit.utils import QuantumInstance, algorithm_globals, optionals - -logger = "LocalLogger" - - -class LogPass(DummyAP): - """A dummy analysis pass that logs when executed""" - - def __init__(self, message): - super().__init__() - self.message = message - - def run(self, dag): - logging.getLogger(logger).info(self.message) - - -# pylint: disable=invalid-name, unused-argument -def _mock_optimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: - """A mock of a callable that can be used as minimizer in the VQE.""" - result = OptimizerResult() - result.x = np.zeros_like(x0) - result.fun = fun(result.x) - result.nit = 0 - return result - - -@ddt -class TestVQE(QiskitAlgorithmsTestCase): - """Test VQE""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - self.h2_energy = -1.85727503 - - self.ryrz_wavefunction = TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - self.ry_wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - with self.assertWarns(DeprecationWarning): - self.h2_op = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - self.qasm_simulator = QuantumInstance( - BasicAer.get_backend("qasm_simulator"), - shots=1024, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - self.statevector_simulator = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - shots=1, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - - def test_basic_aer_statevector(self): - """Test the VQE on BasicAer's statevector simulator.""" - wavefunction = self.ryrz_wavefunction - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=L_BFGS_B(), - quantum_instance=QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - basis_gates=["u1", "u2", "u3", "cx", "id"], - coupling_map=[[0, 1]], - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ), - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - with self.subTest(msg="test eigenvalue"): - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy) - - with self.subTest(msg="test dimension of optimal point"): - self.assertEqual(len(result.optimal_point), 16) - - with self.subTest(msg="assert cost_function_evals is set"): - self.assertIsNotNone(result.cost_function_evals) - - with self.subTest(msg="assert optimizer_time is set"): - self.assertIsNotNone(result.optimizer_time) - - def test_circuit_input(self): - """Test running the VQE on a plain QuantumCircuit object.""" - wavefunction = QuantumCircuit(2).compose(EfficientSU2(2)) - optimizer = SLSQP(maxiter=50) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - quantum_instance=self.statevector_simulator, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - @data( - (MatrixExpectation(), 1), - (AerPauliExpectation(), 1), - (PauliExpectation(), 2), - ) - @unpack - def test_construct_circuit(self, expectation, num_circuits): - """Test construct circuits returns QuantumCircuits and the right number of them.""" - try: - wavefunction = EfficientSU2(2, reps=1) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=wavefunction, expectation=expectation) - params = [0] * wavefunction.num_parameters - circuits = vqe.construct_circuit(parameter=params, operator=self.h2_op) - - self.assertEqual(len(circuits), num_circuits) - for circuit in circuits: - self.assertIsInstance(circuit, QuantumCircuit) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - return - - def test_missing_varform_params(self): - """Test specifying a variational form with no parameters raises an error.""" - circuit = QuantumCircuit(self.h2_op.num_qubits) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=circuit, quantum_instance=BasicAer.get_backend("statevector_simulator") - ) - with self.assertRaises(RuntimeError): - vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - @data( - (SLSQP(maxiter=50), 5, 4), - (SPSA(maxiter=150), 2, 2), # max_evals_grouped=n or =2 if n>2 - ) - @unpack - def test_max_evals_grouped(self, optimizer, places, max_evals_grouped): - """VQE Optimizers test""" - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=self.ryrz_wavefunction, - optimizer=optimizer, - max_evals_grouped=max_evals_grouped, - quantum_instance=self.statevector_simulator, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=places) - - def test_basic_aer_qasm(self): - """Test the VQE on BasicAer's QASM simulator.""" - optimizer = SPSA(maxiter=300, last_avg=5) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=self.qasm_simulator, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.86823, places=2) - - def test_qasm_eigenvector_normalized(self): - """Test VQE with qasm_simulator returns normalized eigenvector.""" - wavefunction = self.ry_wavefunction - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=wavefunction, quantum_instance=self.qasm_simulator) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - amplitudes = list(result.eigenstate.values()) - self.assertAlmostEqual(np.linalg.norm(amplitudes), 1.0, places=4) - - @unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") - def test_with_aer_statevector(self): - """Test VQE with Aer's statevector_simulator.""" - from qiskit_aer import AerSimulator - - backend = AerSimulator(method="statevector") - wavefunction = self.ry_wavefunction - optimizer = L_BFGS_B() - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=quantum_instance, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - - @unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") - def test_with_aer_qasm(self): - """Test VQE with Aer's qasm_simulator.""" - from qiskit_aer import AerSimulator - - backend = AerSimulator() - optimizer = SPSA(maxiter=200, last_avg=5) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - expectation=PauliExpectation(), - quantum_instance=quantum_instance, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.86305, places=2) - - @unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") - def test_with_aer_qasm_snapshot_mode(self): - """Test the VQE using Aer's qasm_simulator snapshot mode.""" - from qiskit_aer import AerSimulator - - backend = AerSimulator() - optimizer = L_BFGS_B() - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - shots=1, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - expectation=AerPauliExpectation(), - quantum_instance=quantum_instance, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - - @unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") - @data( - CG(maxiter=1), - L_BFGS_B(maxfun=1), - P_BFGS(maxfun=1, max_processes=0), - SLSQP(maxiter=1), - TNC(maxiter=1), - ) - def test_with_gradient(self, optimizer): - """Test VQE using Gradient().""" - from qiskit_aer import AerSimulator - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=AerSimulator(), - shots=1, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=self.ry_wavefunction, - optimizer=optimizer, - gradient=Gradient(), - expectation=AerPauliExpectation(), - quantum_instance=quantum_instance, - max_evals_grouped=1000, - ) - vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - def test_with_two_qubit_reduction(self): - """Test the VQE using TwoQubitReduction.""" - - with self.assertWarns(DeprecationWarning): - qubit_op = PauliSumOp.from_list( - [ - ("IIII", -0.8105479805373266), - ("IIIZ", 0.17218393261915552), - ("IIZZ", -0.22575349222402472), - ("IZZI", 0.1721839326191556), - ("ZZII", -0.22575349222402466), - ("IIZI", 0.1209126326177663), - ("IZZZ", 0.16892753870087912), - ("IXZX", -0.045232799946057854), - ("ZXIX", 0.045232799946057854), - ("IXIX", 0.045232799946057854), - ("ZXZX", -0.045232799946057854), - ("ZZIZ", 0.16614543256382414), - ("IZIZ", 0.16614543256382414), - ("ZZZZ", 0.17464343068300453), - ("ZIZI", 0.1209126326177663), - ] - ) - tapered_qubit_op = TwoQubitReduction(num_particles=2).convert(qubit_op) - - for simulator in [self.qasm_simulator, self.statevector_simulator]: - with self.subTest(f"Test for {simulator}."), self.assertWarns(DeprecationWarning): - vqe = VQE( - self.ry_wavefunction, - SPSA(maxiter=300, last_avg=5), - quantum_instance=simulator, - ) - result = vqe.compute_minimum_eigenvalue(tapered_qubit_op) - energy = -1.868 if simulator == self.qasm_simulator else self.h2_energy - self.assertAlmostEqual(result.eigenvalue.real, energy, places=2) - - def test_callback(self): - """Test the callback on VQE.""" - history = {"eval_count": [], "parameters": [], "mean": [], "std": []} - - def store_intermediate_result(eval_count, parameters, mean, std): - history["eval_count"].append(eval_count) - history["parameters"].append(parameters) - history["mean"].append(mean) - history["std"].append(std) - - optimizer = COBYLA(maxiter=3) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - callback=store_intermediate_result, - quantum_instance=self.qasm_simulator, - ) - vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertTrue(all(isinstance(count, int) for count in history["eval_count"])) - self.assertTrue(all(isinstance(mean, float) for mean in history["mean"])) - self.assertTrue(all(isinstance(std, float) for std in history["std"])) - for params in history["parameters"]: - self.assertTrue(all(isinstance(param, float) for param in params)) - - def test_reuse(self): - """Test re-using a VQE algorithm instance.""" - - with self.assertWarns(DeprecationWarning): - vqe = VQE() - with self.subTest(msg="assert running empty raises AlgorithmError"): - with self.assertWarns(DeprecationWarning), self.assertRaises(AlgorithmError): - _ = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - ansatz = TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - vqe.ansatz = ansatz - with self.subTest(msg="assert missing operator raises AlgorithmError"): - with self.assertWarns(DeprecationWarning), self.assertRaises(AlgorithmError): - _ = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - with self.assertWarns(DeprecationWarning): - vqe.expectation = MatrixExpectation() - vqe.quantum_instance = self.statevector_simulator - - with self.subTest(msg="assert VQE works once all info is available"), self.assertWarns( - DeprecationWarning - ): - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - with self.assertWarns(DeprecationWarning): - operator = PrimitiveOp( - np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 2, 0], [0, 0, 0, 3]]) - ) - - with self.subTest(msg="assert minimum eigensolver interface works"), self.assertWarns( - DeprecationWarning - ): - result = vqe.compute_minimum_eigenvalue(operator=operator) - self.assertAlmostEqual(result.eigenvalue.real, -1.0, places=5) - - def test_vqe_optimizer(self): - """Test running same VQE twice to re-use optimizer, then switch optimizer""" - with self.assertWarns(DeprecationWarning): - vqe = VQE( - optimizer=SLSQP(), - quantum_instance=QuantumInstance(BasicAer.get_backend("statevector_simulator")), - ) - - def run_check(): - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.85727503, places=5) - - run_check() - - with self.subTest("Optimizer re-use"): - run_check() - - with self.subTest("Optimizer replace"): - vqe.optimizer = L_BFGS_B() - run_check() - - @data(MatrixExpectation(), None) - def test_backend_change(self, user_expectation): - """Test that VQE works when backend changes.""" - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz"), - optimizer=SLSQP(maxiter=2), - expectation=user_expectation, - quantum_instance=BasicAer.get_backend("statevector_simulator"), - ) - result0 = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - if user_expectation is not None: - with self.subTest("User expectation kept."): - self.assertEqual(vqe.expectation, user_expectation) - - # works also if no expectation is set, since it will be determined automatically - with self.assertWarns(DeprecationWarning): - vqe.quantum_instance = BasicAer.get_backend("qasm_simulator") - result1 = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - if user_expectation is not None: - with self.subTest("Change backend with user expectation, it is kept."): - self.assertEqual(vqe.expectation, user_expectation) - - with self.subTest("Check results."): - self.assertEqual(len(result0.optimal_point), len(result1.optimal_point)) - - def test_batch_evaluate_with_qnspsa(self): - """Test batch evaluating with QNSPSA works.""" - ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - - wrapped_backend = BasicAer.get_backend("qasm_simulator") - inner_backend = BasicAer.get_backend("statevector_simulator") - - callcount = {"count": 0} - - def wrapped_run(circuits, **kwargs): - kwargs["callcount"]["count"] += 1 - return inner_backend.run(circuits) - - wrapped_backend.run = partial(wrapped_run, callcount=callcount) - - with self.assertWarns(DeprecationWarning): - fidelity = QNSPSA.get_fidelity(ansatz, backend=wrapped_backend) - qnspsa = QNSPSA(fidelity, maxiter=5) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=ansatz, - optimizer=qnspsa, - max_evals_grouped=100, - quantum_instance=wrapped_backend, - ) - _ = vqe.compute_minimum_eigenvalue(Z ^ Z) - - # 1 calibration + 1 stddev estimation + 1 initial blocking - # + 5 (1 loss + 1 fidelity + 1 blocking) + 1 return loss + 1 VQE eval - expected = 1 + 1 + 1 + 5 * 3 + 1 + 1 - - self.assertEqual(callcount["count"], expected) - - def test_set_ansatz_to_none(self): - """Tests that setting the ansatz to None results in the default behavior""" - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=self.ryrz_wavefunction, - optimizer=L_BFGS_B(), - quantum_instance=self.statevector_simulator, - ) - - vqe.ansatz = None - self.assertIsInstance(vqe.ansatz, RealAmplitudes) - - def test_set_optimizer_to_none(self): - """Tests that setting the optimizer to None results in the default behavior""" - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=self.ryrz_wavefunction, - optimizer=L_BFGS_B(), - quantum_instance=self.statevector_simulator, - ) - - vqe.optimizer = None - self.assertIsInstance(vqe.optimizer, SLSQP) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - optimizer=partial(scipy_minimize, method="L-BFGS-B", options={"maxiter": 2}), - quantum_instance=self.statevector_simulator, - ) - result = vqe.compute_minimum_eigenvalue(Z) - - self.assertEqual(result.cost_function_evals, 20) - - def test_optimizer_callable(self): - """Test passing a optimizer directly as callable.""" - ansatz = RealAmplitudes(1, reps=1) - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=ansatz, - optimizer=_mock_optimizer, - quantum_instance=self.statevector_simulator, - ) - result = vqe.compute_minimum_eigenvalue(Z) - self.assertTrue(np.all(result.optimal_point == np.zeros(ansatz.num_parameters))) - - def test_aux_operators_list(self): - """Test list-based aux_operators.""" - wavefunction = self.ry_wavefunction - - # Start with an empty list - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=wavefunction, quantum_instance=self.statevector_simulator) - - # Start with an empty list - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=[]) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Go again with two auxiliary operators - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=extra_ops) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operator_eigenvalues), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0, places=6) - self.assertEqual(result.aux_operator_eigenvalues[2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[2][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[3][1], 0.0) - - def test_aux_operators_dict(self): - """Test dictionary compatibility of aux_operators""" - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=wavefunction, quantum_instance=self.statevector_simulator) - - # Start with an empty dictionary - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators={}) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Go again with two auxiliary operators - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=extra_ops) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operator_eigenvalues), 3) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operator_eigenvalues["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operator_eigenvalues.keys()) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["zero_operator"][1], 0.0) - - def test_aux_operator_std_dev_pauli(self): - """Test non-zero standard deviations of aux operators with PauliExpectation.""" - wavefunction = self.ry_wavefunction - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - expectation=PauliExpectation(), - optimizer=COBYLA(maxiter=0), - quantum_instance=self.qasm_simulator, - ) - - with self.assertWarns(DeprecationWarning): - # Go again with two auxiliary operators - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0.6796875, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.02534712219145965, places=6) - - # Go again with additional None and zero operators - aux_ops = [*aux_ops, None, 0] - - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0.57421875, places=6) - self.assertEqual(result.aux_operator_eigenvalues[2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[3][0], 0.0) - # # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[1][1], 0.026562146577166837, places=6 - ) - self.assertAlmostEqual(result.aux_operator_eigenvalues[2][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[3][1], 0.0) - - @unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") - def test_aux_operator_std_dev_aer_pauli(self): - """Test non-zero standard deviations of aux operators with AerPauliExpectation.""" - from qiskit_aer import AerSimulator - - wavefunction = self.ry_wavefunction - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - expectation=AerPauliExpectation(), - optimizer=COBYLA(maxiter=0), - quantum_instance=QuantumInstance( - backend=AerSimulator(), - shots=1, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ), - ) - with self.assertWarns(DeprecationWarning): - # Go again with two auxiliary operators - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0.6698863565455391, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0, places=6) - - # Go again with additional None and zero operators - aux_ops = [*aux_ops, None, 0] - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0.6036400943063891, places=6) - self.assertEqual(result.aux_operator_eigenvalues[2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[2][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[3][1], 0.0) - - def test_2step_transpile(self): - """Test the two-step transpiler pass.""" - # count how often the pass for parameterized circuits is called - pre_counter = LogPass("pre_passmanager") - pre_pass = PassManager(pre_counter) - config = PassManagerConfig(basis_gates=["u3", "cx"]) - pre_pass += level_1_pass_manager(config) - - # ... and the pass for bound circuits - bound_counter = LogPass("bound_pass_manager") - bound_pass = PassManager(bound_counter) - - optimizer = SPSA(maxiter=5, learning_rate=0.01, perturbation=0.01) - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=BasicAer.get_backend("statevector_simulator"), - basis_gates=["u3", "cx"], - pass_manager=pre_pass, - bound_pass_manager=bound_pass, - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(optimizer=optimizer, quantum_instance=quantum_instance) - with self.assertLogs(logger, level="INFO") as cm: - _ = vqe.compute_minimum_eigenvalue(Z) - - expected = [ - "pre_passmanager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "pre_passmanager", - "bound_pass_manager", - ] - self.assertEqual([record.message for record in cm.records], expected) - - def test_construct_eigenstate_from_optpoint(self): - """Test constructing the eigenstate from the optimal point, if the default ansatz is used.""" - - # use Hamiltonian yielding more than 11 parameters in the default ansatz - with self.assertWarns(DeprecationWarning): - hamiltonian = Z ^ Z ^ Z - - optimizer = SPSA(maxiter=1, learning_rate=0.01, perturbation=0.01) - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=BasicAer.get_backend("statevector_simulator"), basis_gates=["u3", "cx"] - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(optimizer=optimizer, quantum_instance=quantum_instance) - result = vqe.compute_minimum_eigenvalue(hamiltonian) - - optimal_circuit = vqe.ansatz.assign_parameters(result.optimal_point) - self.assertTrue(Statevector(result.eigenstate).equiv(optimal_circuit)) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/__init__.py b/test/python/algorithms/time_evolvers/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/test/python/algorithms/time_evolvers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. diff --git a/test/python/algorithms/time_evolvers/classical_methods/__init__.py b/test/python/algorithms/time_evolvers/classical_methods/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/test/python/algorithms/time_evolvers/classical_methods/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. diff --git a/test/python/algorithms/time_evolvers/classical_methods/test_scipy_imaginary_evolver.py b/test/python/algorithms/time_evolvers/classical_methods/test_scipy_imaginary_evolver.py deleted file mode 100644 index 5671bf221284..000000000000 --- a/test/python/algorithms/time_evolvers/classical_methods/test_scipy_imaginary_evolver.py +++ /dev/null @@ -1,183 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Test Classical Imaginary Evolver.""" -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import data, ddt, unpack -import numpy as np -from qiskit.algorithms.time_evolvers.time_evolution_problem import TimeEvolutionProblem - -from qiskit.quantum_info.states.statevector import Statevector -from qiskit.quantum_info import SparsePauliOp - -from qiskit import QuantumCircuit -from qiskit.algorithms import SciPyImaginaryEvolver - -from qiskit.opflow import PauliSumOp - - -@ddt -class TestSciPyImaginaryEvolver(QiskitAlgorithmsTestCase): - """Test SciPy Imaginary Evolver.""" - - def create_hamiltonian_lattice(self, num_sites: int) -> SparsePauliOp: - """Creates an Ising Hamiltonian on a lattice.""" - j_const = 0.1 - g_const = -1.0 - - zz_op = ["I" * i + "ZZ" + "I" * (num_sites - i - 2) for i in range(num_sites - 1)] - x_op = ["I" * i + "X" + "I" * (num_sites - i - 1) for i in range(num_sites)] - return SparsePauliOp(zz_op) * j_const + SparsePauliOp(x_op) * g_const - - @data( - (Statevector.from_label("0"), 100, SparsePauliOp("X"), Statevector.from_label("-")), - (Statevector.from_label("0"), 100, SparsePauliOp("-X"), Statevector.from_label("+")), - ) - @unpack - def test_evolve( - self, - initial_state: Statevector, - tau: float, - hamiltonian: SparsePauliOp, - expected_state: Statevector, - ): - """Initializes a classical imaginary evolver and evolves a state to find the ground state. - It compares the solution with the first eigenstate of the hamiltonian. - """ - expected_state_matrix = expected_state.data - - evolution_problem = TimeEvolutionProblem(hamiltonian, tau, initial_state) - classic_evolver = SciPyImaginaryEvolver(num_timesteps=300) - result = classic_evolver.evolve(evolution_problem) - - with self.subTest("Amplitudes"): - np.testing.assert_allclose( - np.absolute(result.evolved_state.data), - np.absolute(expected_state_matrix), - atol=1e-10, - rtol=0, - ) - - with self.subTest("Phases"): - np.testing.assert_allclose( - np.angle(result.evolved_state.data), - np.angle(expected_state_matrix), - atol=1e-10, - rtol=0, - ) - - @data( - ( - Statevector.from_label("0" * 5), - SparsePauliOp.from_sparse_list([("X", [i], 1) for i in range(5)], num_qubits=5), - 5, - ), - (Statevector.from_label("0"), SparsePauliOp("X"), 1), - ) - @unpack - def test_observables( - self, initial_state: Statevector, hamiltonian: SparsePauliOp, nqubits: int - ): - """Tests if the observables are properly evaluated at each timestep.""" - - time_ev = 5.0 - observables = {"Energy": hamiltonian, "Z": SparsePauliOp("Z" * nqubits)} - evolution_problem = TimeEvolutionProblem( - hamiltonian, time_ev, initial_state, aux_operators=observables - ) - - classic_evolver = SciPyImaginaryEvolver(num_timesteps=300) - result = classic_evolver.evolve(evolution_problem) - - z_mean, z_std = result.observables["Z"] - - time_vector = result.times - expected_z = 1 / (np.cosh(time_vector) ** 2 + np.sinh(time_vector) ** 2) - expected_z_std = np.zeros_like(expected_z) - - np.testing.assert_allclose(z_mean, expected_z**nqubits, atol=1e-10, rtol=0) - np.testing.assert_allclose(z_std, expected_z_std, atol=1e-10, rtol=0) - - def test_quantum_circuit_initial_state(self): - """Tests if the system can be evolved with a quantum circuit as an initial state.""" - qc = QuantumCircuit(3) - qc.h(0) - qc.cx(0, range(1, 3)) - - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X" * 3), time=1.0, initial_state=qc - ) - classic_evolver = SciPyImaginaryEvolver(num_timesteps=5) - result = classic_evolver.evolve(evolution_problem) - self.assertEqual(result.evolved_state, Statevector(qc)) - - def test_paulisumop_hamiltonian(self): - """Tests if the hamiltonian can be a PauliSumOp""" - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp.from_list( - [ - ("XI", 1), - ("IX", 1), - ] - ) - observable = PauliSumOp.from_list([("ZZ", 1)]) - evolution_problem = TimeEvolutionProblem( - hamiltonian=hamiltonian, - time=1.0, - initial_state=Statevector.from_label("00"), - aux_operators={"ZZ": observable}, - ) - classic_evolver = SciPyImaginaryEvolver(num_timesteps=5) - result = classic_evolver.evolve(evolution_problem) - expected = 1 / (np.cosh(1.0) ** 2 + np.sinh(1.0) ** 2) - np.testing.assert_almost_equal(result.aux_ops_evaluated["ZZ"][0], expected**2) - - def test_error_time_dependency(self): - """Tests if an error is raised for a time dependent Hamiltonian.""" - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X" * 3), - time=1.0, - initial_state=Statevector.from_label("0" * 3), - t_param=0, - ) - classic_evolver = SciPyImaginaryEvolver(num_timesteps=5) - with self.assertRaises(ValueError): - classic_evolver.evolve(evolution_problem) - - def test_no_time_steps(self): - """Tests if the evolver handles some edge cases related to the number of timesteps.""" - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X"), - time=1.0, - initial_state=Statevector.from_label("0"), - aux_operators={"Energy": SparsePauliOp("X")}, - ) - - with self.subTest("0 timesteps"): - with self.assertRaises(ValueError): - classic_evolver = SciPyImaginaryEvolver(num_timesteps=0) - classic_evolver.evolve(evolution_problem) - - with self.subTest("1 timestep"): - classic_evolver = SciPyImaginaryEvolver(num_timesteps=1) - result = classic_evolver.evolve(evolution_problem) - np.testing.assert_equal(result.times, np.array([0.0, 1.0])) - - with self.subTest("Negative timesteps"): - with self.assertRaises(ValueError): - classic_evolver = SciPyImaginaryEvolver(num_timesteps=-5) - classic_evolver.evolve(evolution_problem) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/classical_methods/test_scipy_real_evolver.py b/test/python/algorithms/time_evolvers/classical_methods/test_scipy_real_evolver.py deleted file mode 100644 index 40fb1e1e1102..000000000000 --- a/test/python/algorithms/time_evolvers/classical_methods/test_scipy_real_evolver.py +++ /dev/null @@ -1,154 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Test Classical Real Evolver.""" -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import data, ddt, unpack -import numpy as np -from qiskit import QuantumCircuit, QuantumRegister -from qiskit.algorithms import SciPyRealEvolver, TimeEvolutionProblem -from qiskit.quantum_info import Statevector, SparsePauliOp - - -def zero(n): - """Auxiliary function to create an initial state on n qubits.""" - qr = QuantumRegister(n) - qc = QuantumCircuit(qr) - return Statevector(qc) - - -def one(n): - """Auxiliary function to create an initial state on n qubits.""" - qr = QuantumRegister(n) - qc = QuantumCircuit(qr) - qc.x(qr) - return Statevector(qc) - - -@ddt -class TestClassicalRealEvolver(QiskitAlgorithmsTestCase): - """Test Classical Real Evolver.""" - - @data( - (one(1), np.pi / 2, SparsePauliOp("X"), -1.0j * zero(1)), - ( - one(1).expand(zero(1)), - np.pi / 2, - SparsePauliOp(["XX", "YY"], [0.5, 0.5]), - -1.0j * zero(1).expand(one(1)), - ), - ( - one(1).expand(zero(1)), - np.pi / 4, - SparsePauliOp(["XX", "YY"], [0.5, 0.5]), - ((one(1).expand(zero(1)) - 1.0j * zero(1).expand(one(1)))) / np.sqrt(2), - ), - (zero(12), np.pi / 2, SparsePauliOp("X" * 12), -1.0j * (one(12))), - ) - @unpack - def test_evolve( - self, - initial_state: Statevector, - time_ev: float, - hamiltonian: SparsePauliOp, - expected_state: Statevector, - ): - """Initializes a classical real evolver and evolves a state.""" - evolution_problem = TimeEvolutionProblem(hamiltonian, time_ev, initial_state) - classic_evolver = SciPyRealEvolver(num_timesteps=1) - result = classic_evolver.evolve(evolution_problem) - - np.testing.assert_allclose( - result.evolved_state.data, - expected_state.data, - atol=1e-10, - rtol=0, - ) - - def test_observables(self): - """Tests if the observables are properly evaluated at each timestep.""" - - initial_state = zero(1) - time_ev = 10.0 - hamiltonian = SparsePauliOp("X") - observables = {"Energy": SparsePauliOp("X"), "Z": SparsePauliOp("Z")} - evolution_problem = TimeEvolutionProblem( - hamiltonian, time_ev, initial_state, aux_operators=observables - ) - classic_evolver = SciPyRealEvolver(num_timesteps=10) - result = classic_evolver.evolve(evolution_problem) - - z_mean, z_std = result.observables["Z"] - - timesteps = z_mean.shape[0] - time_vector = np.linspace(0, time_ev, timesteps) - expected_z = 1 - 2 * (np.sin(time_vector)) ** 2 - expected_z_std = np.zeros_like(expected_z) - - np.testing.assert_allclose(z_mean, expected_z, atol=1e-10, rtol=0) - np.testing.assert_allclose(z_std, expected_z_std, atol=1e-10, rtol=0) - np.testing.assert_equal(time_vector, result.times) - - def test_quantum_circuit_initial_state(self): - """Tests if the system can be evolved with a quantum circuit as an initial state.""" - qc = QuantumCircuit(3) - qc.h(0) - qc.cx(0, range(1, 3)) - - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X" * 3), time=2 * np.pi, initial_state=qc - ) - classic_evolver = SciPyRealEvolver(num_timesteps=500) - result = classic_evolver.evolve(evolution_problem) - np.testing.assert_almost_equal( - result.evolved_state.data, - np.array([1, 0, 0, 0, 0, 0, 0, 1]) / np.sqrt(2), - decimal=10, - ) - - def test_error_time_dependency(self): - """Tests if an error is raised for time dependent hamiltonian.""" - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X" * 3), time=1.0, initial_state=zero(3), t_param=0 - ) - classic_evolver = SciPyRealEvolver(num_timesteps=5) - with self.assertRaises(ValueError): - classic_evolver.evolve(evolution_problem) - - def test_no_time_steps(self): - """Tests if the evolver handles some edge cases related to the number of timesteps.""" - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X"), - time=1.0, - initial_state=zero(1), - aux_operators={"Energy": SparsePauliOp("X")}, - ) - - with self.subTest("0 timesteps"): - with self.assertRaises(ValueError): - classic_evolver = SciPyRealEvolver(num_timesteps=0) - classic_evolver.evolve(evolution_problem) - - with self.subTest("1 timestep"): - classic_evolver = SciPyRealEvolver(num_timesteps=1) - result = classic_evolver.evolve(evolution_problem) - np.testing.assert_equal(result.times, np.array([0.0, 1.0])) - - with self.subTest("Negative timesteps"): - with self.assertRaises(ValueError): - classic_evolver = SciPyRealEvolver(num_timesteps=-5) - classic_evolver.evolve(evolution_problem) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/test_pvqd.py b/test/python/algorithms/time_evolvers/test_pvqd.py deleted file mode 100644 index fc48e7b7159e..000000000000 --- a/test/python/algorithms/time_evolvers/test_pvqd.py +++ /dev/null @@ -1,342 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# 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. - -"""Tests for PVQD.""" -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from functools import partial - -import numpy as np -from ddt import data, ddt, unpack - -from qiskit import QiskitError -from qiskit.algorithms.time_evolvers import TimeEvolutionProblem -from qiskit.algorithms.optimizers import L_BFGS_B, SPSA, GradientDescent, OptimizerResult -from qiskit.algorithms.state_fidelities import ComputeUncompute -from qiskit.algorithms.time_evolvers.pvqd import PVQD -from qiskit.circuit import Gate, Parameter, QuantumCircuit -from qiskit.circuit.library import EfficientSU2 -from qiskit.opflow import PauliSumOp -from qiskit.primitives import Estimator, Sampler -from qiskit.quantum_info import Pauli, SparsePauliOp -from qiskit.test import QiskitTestCase -from qiskit.utils import algorithm_globals - - -# pylint: disable=unused-argument, invalid-name -def gradient_supplied(fun, x0, jac, info): - """A mock optimizer that checks whether the gradient is supported or not.""" - result = OptimizerResult() - result.x = x0 - result.fun = 0 - info["has_gradient"] = jac is not None - - return result - - -class WhatAmI(Gate): - """A custom opaque gate that can be inverted but not decomposed.""" - - def __init__(self, angle): - super().__init__(name="whatami", num_qubits=2, params=[angle]) - - def inverse(self): - return WhatAmI(-self.params[0]) - - -@ddt -class TestPVQD(QiskitAlgorithmsTestCase): - """Tests for the pVQD algorithm.""" - - def setUp(self): - super().setUp() - self.hamiltonian = 0.1 * SparsePauliOp([Pauli("ZZ"), Pauli("IX"), Pauli("XI")]) - self.observable = Pauli("ZZ") - self.ansatz = EfficientSU2(2, reps=1) - self.initial_parameters = np.zeros(self.ansatz.num_parameters) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 123 - - @data(("ising", True, 2), ("pauli", False, None), ("pauli_sum_op", True, 2)) - @unpack - def test_pvqd(self, hamiltonian_type, gradient, num_timesteps): - """Test a simple evolution.""" - time = 0.02 - - if hamiltonian_type == "ising": - hamiltonian = self.hamiltonian - elif hamiltonian_type == "pauli_sum_op": - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp(self.hamiltonian) - else: # hamiltonian_type == "pauli": - hamiltonian = Pauli("XX") - - # parse input arguments - if gradient: - optimizer = GradientDescent(maxiter=1) - else: - optimizer = L_BFGS_B(maxiter=1) - - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - - # run pVQD keeping track of the energy and the magnetization - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - estimator, - optimizer=optimizer, - num_timesteps=num_timesteps, - ) - problem = TimeEvolutionProblem( - hamiltonian, time, aux_operators=[hamiltonian, self.observable] - ) - result = pvqd.evolve(problem) - - self.assertTrue(len(result.fidelities) == 3) - self.assertTrue(np.all(result.times == [0.0, 0.01, 0.02])) - self.assertTrue(np.asarray(result.observables).shape == (3, 2)) - num_parameters = self.ansatz.num_parameters - self.assertTrue( - len(result.parameters) == 3 - and np.all([len(params) == num_parameters for params in result.parameters]) - ) - - def test_step(self): - """Test calling the step method directly.""" - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - estimator, - optimizer=L_BFGS_B(maxiter=100), - ) - - # perform optimization for a timestep of 0, then the optimal parameters are the current - # ones and the fidelity is 1 - theta_next, fidelity = pvqd.step( - self.hamiltonian, - self.ansatz, - self.initial_parameters, - dt=0.0, - initial_guess=np.zeros_like(self.initial_parameters), - ) - - self.assertTrue(np.allclose(theta_next, self.initial_parameters)) - self.assertAlmostEqual(fidelity, 1) - - def test_get_loss(self): - """Test getting the loss function directly.""" - - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - estimator, - use_parameter_shift=False, - ) - - theta = np.ones(self.ansatz.num_parameters) - loss, gradient = pvqd.get_loss( - self.hamiltonian, self.ansatz, dt=0.0, current_parameters=theta - ) - - displacement = np.arange(self.ansatz.num_parameters) - - with self.subTest(msg="check gradient is None"): - self.assertIsNone(gradient) - - with self.subTest(msg="check loss works"): - self.assertGreater(loss(displacement), 0) - self.assertAlmostEqual(loss(np.zeros_like(theta)), 0) - - def test_invalid_num_timestep(self): - """Test raises if the num_timestep is not positive.""" - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - estimator, - optimizer=L_BFGS_B(), - num_timesteps=0, - ) - problem = TimeEvolutionProblem( - self.hamiltonian, time=0.01, aux_operators=[self.hamiltonian, self.observable] - ) - - with self.assertRaises(ValueError): - _ = pvqd.evolve(problem) - - def test_initial_guess_and_observables(self): - """Test doing no optimizations stays at initial guess.""" - initial_guess = np.zeros(self.ansatz.num_parameters) - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - estimator, - optimizer=SPSA(maxiter=0, learning_rate=0.1, perturbation=0.01), - num_timesteps=10, - initial_guess=initial_guess, - ) - problem = TimeEvolutionProblem( - self.hamiltonian, time=0.1, aux_operators=[self.hamiltonian, self.observable] - ) - - result = pvqd.evolve(problem) - - observables = result.aux_ops_evaluated - self.assertEqual(observables[0], 0.1) # expected energy - self.assertEqual(observables[1], 1) # expected magnetization - - def test_zero_parameters(self): - """Test passing an ansatz with zero parameters raises an error.""" - problem = TimeEvolutionProblem(self.hamiltonian, time=0.02) - sampler = Sampler() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity_primitive, - QuantumCircuit(2), - np.array([]), - optimizer=SPSA(maxiter=10, learning_rate=0.1, perturbation=0.01), - ) - - with self.assertRaises(QiskitError): - _ = pvqd.evolve(problem) - - def test_initial_state_raises(self): - """Test passing an initial state raises an error for now.""" - initial_state = QuantumCircuit(2) - initial_state.x(0) - - problem = TimeEvolutionProblem( - self.hamiltonian, - time=0.02, - initial_state=initial_state, - ) - - sampler = Sampler() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - optimizer=SPSA(maxiter=0, learning_rate=0.1, perturbation=0.01), - ) - - with self.assertRaises(NotImplementedError): - _ = pvqd.evolve(problem) - - def test_aux_ops_raises(self): - """Test passing auxiliary operators with no estimator raises an error.""" - - problem = TimeEvolutionProblem( - self.hamiltonian, time=0.02, aux_operators=[self.hamiltonian, self.observable] - ) - - sampler = Sampler() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - optimizer=SPSA(maxiter=0, learning_rate=0.1, perturbation=0.01), - ) - - with self.assertRaises(ValueError): - _ = pvqd.evolve(problem) - - -class TestPVQDUtils(QiskitTestCase): - """Test some utility functions for PVQD.""" - - def setUp(self): - super().setUp() - self.hamiltonian = 0.1 * SparsePauliOp([Pauli("ZZ"), Pauli("IX"), Pauli("XI")]) - self.ansatz = EfficientSU2(2, reps=1) - - def test_gradient_supported(self): - """Test the gradient support is correctly determined.""" - # gradient supported here - wrapped = EfficientSU2(2) # a circuit wrapped into a big instruction - plain = wrapped.decompose() # a plain circuit with already supported instructions - - # gradients not supported on the following circuits - x = Parameter("x") - duplicated = QuantumCircuit(2) - duplicated.rx(x, 0) - duplicated.rx(x, 1) - - needs_chainrule = QuantumCircuit(2) - needs_chainrule.rx(2 * x, 0) - - custom_gate = WhatAmI(x) - unsupported = QuantumCircuit(2) - unsupported.append(custom_gate, [0, 1]) - - tests = [ - (wrapped, True), # tuple: (circuit, gradient support) - (plain, True), - (duplicated, False), - (needs_chainrule, False), - (unsupported, False), - ] - - # used to store the info if a gradient callable is passed into the - # optimizer of not - info = {"has_gradient": None} - optimizer = partial(gradient_supplied, info=info) - - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity=fidelity_primitive, - ansatz=None, - initial_parameters=np.array([]), - estimator=estimator, - optimizer=optimizer, - ) - problem = TimeEvolutionProblem(self.hamiltonian, time=0.01) - for circuit, expected_support in tests: - with self.subTest(circuit=circuit, expected_support=expected_support): - pvqd.ansatz = circuit - pvqd.initial_parameters = np.zeros(circuit.num_parameters) - _ = pvqd.evolve(problem) - self.assertEqual(info["has_gradient"], expected_support) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/test_time_evolution_problem.py b/test/python/algorithms/time_evolvers/test_time_evolution_problem.py deleted file mode 100644 index 1982fb203749..000000000000 --- a/test/python/algorithms/time_evolvers/test_time_evolution_problem.py +++ /dev/null @@ -1,98 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# 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. - -"""Test evolver problem class.""" -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import data, ddt -from numpy.testing import assert_raises -from qiskit import QuantumCircuit -from qiskit.algorithms import TimeEvolutionProblem -from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector -from qiskit.circuit import Parameter -from qiskit.opflow import Y, Z, One, X, Zero, PauliSumOp - - -@ddt -class TestTimeEvolutionProblem(QiskitAlgorithmsTestCase): - """Test evolver problem class.""" - - def test_init_default(self): - """Tests that all default fields are initialized correctly.""" - hamiltonian = Y - time = 2.5 - initial_state = One - - evo_problem = TimeEvolutionProblem(hamiltonian, time, initial_state) - - expected_hamiltonian = Y - expected_time = 2.5 - expected_initial_state = One - expected_aux_operators = None - expected_t_param = None - expected_param_value_dict = None - - self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) - self.assertEqual(evo_problem.time, expected_time) - self.assertEqual(evo_problem.initial_state, expected_initial_state) - self.assertEqual(evo_problem.aux_operators, expected_aux_operators) - self.assertEqual(evo_problem.t_param, expected_t_param) - self.assertEqual(evo_problem.param_value_map, expected_param_value_dict) - - @data(QuantumCircuit(1), Statevector([1, 0])) - def test_init_all(self, initial_state): - """Tests that all fields are initialized correctly.""" - t_parameter = Parameter("t") - with self.assertWarns(DeprecationWarning): - hamiltonian = t_parameter * Z + Y - time = 2 - aux_operators = [X, Y] - param_value_dict = {t_parameter: 3.2} - - evo_problem = TimeEvolutionProblem( - hamiltonian, - time, - initial_state, - aux_operators, - t_param=t_parameter, - param_value_map=param_value_dict, - ) - - with self.assertWarns(DeprecationWarning): - expected_hamiltonian = Y + t_parameter * Z - expected_time = 2 - expected_type = QuantumCircuit - expected_aux_operators = [X, Y] - expected_t_param = t_parameter - expected_param_value_dict = {t_parameter: 3.2} - - with self.assertWarns(DeprecationWarning): - self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) - self.assertEqual(evo_problem.time, expected_time) - self.assertEqual(type(evo_problem.initial_state), expected_type) - self.assertEqual(evo_problem.aux_operators, expected_aux_operators) - self.assertEqual(evo_problem.t_param, expected_t_param) - self.assertEqual(evo_problem.param_value_map, expected_param_value_dict) - - def test_validate_params(self): - """Tests expected errors are thrown on parameters mismatch.""" - param_x = Parameter("x") - with self.subTest(msg="Parameter missing in dict."): - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp(SparsePauliOp([Pauli("X"), Pauli("Y")]), param_x) - evolution_problem = TimeEvolutionProblem(hamiltonian, 2, Zero) - with assert_raises(ValueError): - evolution_problem.validate_params() - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/test_time_evolution_result.py b/test/python/algorithms/time_evolvers/test_time_evolution_result.py deleted file mode 100644 index 26f21ba93627..000000000000 --- a/test/python/algorithms/time_evolvers/test_time_evolution_result.py +++ /dev/null @@ -1,47 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. -"""Class for testing evolution result.""" -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit.algorithms import TimeEvolutionResult -from qiskit.opflow import Zero - - -class TestTimeEvolutionResult(QiskitAlgorithmsTestCase): - """Class for testing evolution result and relevant metadata.""" - - def test_init_state(self): - """Tests that a class is initialized correctly with an evolved_state.""" - evolved_state = Zero - evo_result = TimeEvolutionResult(evolved_state=evolved_state) - - expected_state = Zero - expected_aux_ops_evaluated = None - - self.assertEqual(evo_result.evolved_state, expected_state) - self.assertEqual(evo_result.aux_ops_evaluated, expected_aux_ops_evaluated) - - def test_init_observable(self): - """Tests that a class is initialized correctly with an evolved_observable.""" - evolved_state = Zero - evolved_aux_ops_evaluated = [(5j, 5j), (1.0, 8j), (5 + 1j, 6 + 1j)] - evo_result = TimeEvolutionResult(evolved_state, evolved_aux_ops_evaluated) - - expected_state = Zero - expected_aux_ops_evaluated = [(5j, 5j), (1.0, 8j), (5 + 1j, 6 + 1j)] - - self.assertEqual(evo_result.evolved_state, expected_state) - self.assertEqual(evo_result.aux_ops_evaluated, expected_aux_ops_evaluated) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/test_trotter_qrte.py b/test/python/algorithms/time_evolvers/test_trotter_qrte.py deleted file mode 100644 index b8a14f0affeb..000000000000 --- a/test/python/algorithms/time_evolvers/test_trotter_qrte.py +++ /dev/null @@ -1,273 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# 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. - -"""Test TrotterQRTE.""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt, data, unpack -import numpy as np -from scipy.linalg import expm -from numpy.testing import assert_raises - -from qiskit.algorithms.time_evolvers import TimeEvolutionProblem, TrotterQRTE -from qiskit.primitives import Estimator -from qiskit import QuantumCircuit -from qiskit.circuit.library import ZGate -from qiskit.quantum_info import Statevector, Pauli, SparsePauliOp -from qiskit.utils import algorithm_globals -from qiskit.circuit import Parameter -from qiskit.opflow import PauliSumOp, X, MatrixOp -from qiskit.synthesis import SuzukiTrotter, QDrift - - -@ddt -class TestTrotterQRTE(QiskitAlgorithmsTestCase): - """TrotterQRTE tests.""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - @data( - ( - None, - Statevector([0.29192658 - 0.45464871j, 0.70807342 - 0.45464871j]), - ), - ( - SuzukiTrotter(), - Statevector([0.29192658 - 0.84147098j, 0.0 - 0.45464871j]), - ), - ) - @unpack - def test_trotter_qrte_trotter_single_qubit(self, product_formula, expected_state): - """Test for default TrotterQRTE on a single qubit.""" - with self.assertWarns(DeprecationWarning): - operator = PauliSumOp(SparsePauliOp([Pauli("X"), Pauli("Z")])) - initial_state = QuantumCircuit(1) - time = 1 - evolution_problem = TimeEvolutionProblem(operator, time, initial_state) - - trotter_qrte = TrotterQRTE(product_formula=product_formula) - evolution_result_state_circuit = trotter_qrte.evolve(evolution_problem).evolved_state - - np.testing.assert_array_almost_equal( - Statevector.from_instruction(evolution_result_state_circuit).data, expected_state.data - ) - - @data((SparsePauliOp(["X", "Z"]), None), (SparsePauliOp(["X", "Z"]), Parameter("t"))) - @unpack - def test_trotter_qrte_trotter(self, operator, t_param): - """Test for default TrotterQRTE on a single qubit with auxiliary operators.""" - if not t_param is None: - operator = SparsePauliOp(operator.paulis, np.array([t_param, 1])) - - # LieTrotter with 1 rep - aux_ops = [Pauli("X"), Pauli("Y")] - - initial_state = QuantumCircuit(1) - time = 3 - num_timesteps = 2 - evolution_problem = TimeEvolutionProblem( - operator, time, initial_state, aux_ops, t_param=t_param - ) - estimator = Estimator() - - expected_psi, expected_observables_result = self._get_expected_trotter_qrte( - operator, - time, - num_timesteps, - initial_state, - aux_ops, - t_param, - ) - - expected_evolved_state = Statevector(expected_psi) - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - trotter_qrte = TrotterQRTE(estimator=estimator, num_timesteps=num_timesteps) - evolution_result = trotter_qrte.evolve(evolution_problem) - - np.testing.assert_array_almost_equal( - Statevector.from_instruction(evolution_result.evolved_state).data, - expected_evolved_state.data, - ) - - aux_ops_result = evolution_result.aux_ops_evaluated - expected_aux_ops_result = [ - (expected_observables_result[-1][0], {"variance": 0, "shots": 0}), - (expected_observables_result[-1][1], {"variance": 0, "shots": 0}), - ] - - means = [element[0] for element in aux_ops_result] - expected_means = [element[0] for element in expected_aux_ops_result] - np.testing.assert_array_almost_equal(means, expected_means) - - vars_and_shots = [element[1] for element in aux_ops_result] - expected_vars_and_shots = [element[1] for element in expected_aux_ops_result] - - observables_result = evolution_result.observables - expected_observables_result = [ - [(o, {"variance": 0, "shots": 0}) for o in eor] for eor in expected_observables_result - ] - - means = [sub_element[0] for element in observables_result for sub_element in element] - expected_means = [ - sub_element[0] for element in expected_observables_result for sub_element in element - ] - np.testing.assert_array_almost_equal(means, expected_means) - - for computed, expected in zip(vars_and_shots, expected_vars_and_shots): - self.assertAlmostEqual(computed.pop("variance", 0), expected["variance"], 2) - self.assertEqual(computed.pop("shots", 0), expected["shots"]) - - @data( - ( - PauliSumOp(SparsePauliOp([Pauli("XY"), Pauli("YX")])), - Statevector([-0.41614684 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.90929743 + 0.0j]), - ), - ( - PauliSumOp(SparsePauliOp([Pauli("ZZ"), Pauli("ZI"), Pauli("IZ")])), - Statevector([-0.9899925 - 0.14112001j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j]), - ), - ( - Pauli("YY"), - Statevector([0.54030231 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.84147098j]), - ), - ) - @unpack - def test_trotter_qrte_trotter_two_qubits(self, operator, expected_state): - """Test for TrotterQRTE on two qubits with various types of a Hamiltonian.""" - # LieTrotter with 1 rep - initial_state = QuantumCircuit(2) - evolution_problem = TimeEvolutionProblem(operator, 1, initial_state) - - trotter_qrte = TrotterQRTE() - evolution_result = trotter_qrte.evolve(evolution_problem) - - np.testing.assert_array_almost_equal( - Statevector.from_instruction(evolution_result.evolved_state).data, expected_state.data - ) - - @data( - (QuantumCircuit(1), Statevector([0.23071786 - 0.69436148j, 0.4646314 - 0.49874749j])), - ( - QuantumCircuit(1).compose(ZGate(), [0]), - Statevector([0.23071786 - 0.69436148j, 0.4646314 - 0.49874749j]), - ), - ) - @unpack - def test_trotter_qrte_qdrift(self, initial_state, expected_state): - """Test for TrotterQRTE with QDrift.""" - with self.assertWarns(DeprecationWarning): - operator = PauliSumOp(SparsePauliOp([Pauli("X"), Pauli("Z")])) - time = 1 - evolution_problem = TimeEvolutionProblem(operator, time, initial_state) - - trotter_qrte = TrotterQRTE(product_formula=QDrift(seed=0)) - evolution_result = trotter_qrte.evolve(evolution_problem) - - np.testing.assert_array_almost_equal( - Statevector.from_instruction(evolution_result.evolved_state).data, - expected_state.data, - ) - - @data((Parameter("t"), {}), (None, {Parameter("x"): 2}), (None, None)) - @unpack - def test_trotter_qrte_trotter_param_errors(self, t_param, param_value_dict): - """Test TrotterQRTE with raising errors for parameters.""" - with self.assertWarns(DeprecationWarning): - operator = Parameter("t") * PauliSumOp(SparsePauliOp([Pauli("X")])) + PauliSumOp( - SparsePauliOp([Pauli("Z")]) - ) - initial_state = QuantumCircuit(1) - self._run_error_test(initial_state, operator, None, None, t_param, param_value_dict) - - @data(([Pauli("X"), Pauli("Y")], None)) - @unpack - def test_trotter_qrte_trotter_aux_ops_errors(self, aux_ops, estimator): - """Test TrotterQRTE with raising errors.""" - with self.assertWarns(DeprecationWarning): - operator = PauliSumOp(SparsePauliOp([Pauli("X")])) + PauliSumOp( - SparsePauliOp([Pauli("Z")]) - ) - initial_state = QuantumCircuit(1) - self._run_error_test(initial_state, operator, aux_ops, estimator, None, None) - - @data( - (X, QuantumCircuit(1)), - (MatrixOp([[1, 1], [0, 1]]), QuantumCircuit(1)), - (PauliSumOp(SparsePauliOp([Pauli("X")])) + PauliSumOp(SparsePauliOp([Pauli("Z")])), None), - ( - SparsePauliOp([Pauli("X"), Pauli("Z")], np.array([Parameter("a"), Parameter("b")])), - QuantumCircuit(1), - ), - ) - @unpack - def test_trotter_qrte_trotter_hamiltonian_errors(self, operator, initial_state): - """Test TrotterQRTE with raising errors for evolution problem content.""" - self._run_error_test(initial_state, operator, None, None, None, None) - - @staticmethod - def _run_error_test(initial_state, operator, aux_ops, estimator, t_param, param_value_dict): - time = 1 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - trotter_qrte = TrotterQRTE(estimator=estimator) - with assert_raises(ValueError): - evolution_problem = TimeEvolutionProblem( - operator, - time, - initial_state, - aux_ops, - t_param=t_param, - param_value_map=param_value_dict, - ) - _ = trotter_qrte.evolve(evolution_problem) - - @staticmethod - def _get_expected_trotter_qrte(operator, time, num_timesteps, init_state, observables, t_param): - """Compute reference values for Trotter evolution via exact matrix exponentiation.""" - dt = time / num_timesteps - observables = [obs.to_matrix() for obs in observables] - - psi = Statevector(init_state).data - if t_param is None: - ops = [Pauli(op).to_matrix() * np.real(coeff) for op, coeff in operator.to_list()] - - observable_results = [] - observable_results.append([np.real(np.conj(psi).dot(obs).dot(psi)) for obs in observables]) - - for n in range(num_timesteps): - if t_param is not None: - time_value = (n + 1) * dt - bound = operator.assign_parameters([time_value]) - ops = [Pauli(op).to_matrix() * np.real(coeff) for op, coeff in bound.to_list()] - for op in ops: - psi = expm(-1j * op * dt).dot(psi) - observable_results.append( - [np.real(np.conj(psi).dot(obs).dot(psi)) for obs in observables] - ) - - return psi, observable_results - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/__init__.py b/test/python/algorithms/time_evolvers/variational/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. diff --git a/test/python/algorithms/time_evolvers/variational/solvers/__init__.py b/test/python/algorithms/time_evolvers/variational/solvers/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. diff --git a/test/python/algorithms/time_evolvers/variational/solvers/expected_results/__init__.py b/test/python/algorithms/time_evolvers/variational/solvers/expected_results/__init__.py deleted file mode 100644 index 62ef1f76c6f4..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/expected_results/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. -"""Stores expected results that are lengthy.""" diff --git a/test/python/algorithms/time_evolvers/variational/solvers/expected_results/test_varqte_linear_solver_expected_1.py b/test/python/algorithms/time_evolvers/variational/solvers/expected_results/test_varqte_linear_solver_expected_1.py deleted file mode 100644 index aba6b4006f37..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/expected_results/test_varqte_linear_solver_expected_1.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. -"""Stores expected results that are lengthy.""" -expected_metric_res_1 = [ - [ - 2.50000000e-01 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - -2.77500000e-17 + 0.0j, - 4.85000000e-17 + 0.0j, - 4.77630626e-32 + 0.0j, - ], - [ - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - 4.85334346e-32 + 0.0j, - 4.17500000e-17 + 0.0j, - ], - [ - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 1.38006319e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - ], - [ - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 1.38006319e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - ], - [ - -3.85185989e-33 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - 0.00000000e00 + 0.0j, - 4.85334346e-32 + 0.0j, - -7.00000000e-18 + 0.0j, - ], - [ - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - 4.85334346e-32 + 0.0j, - 4.17500000e-17 + 0.0j, - ], - [ - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 1.38006319e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - ], - [ - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 1.38006319e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - ], - [ - 2.50000000e-01 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - -2.77500000e-17 + 0.0j, - 4.85000000e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - ], - [ - -2.77500000e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - -7.00000000e-18 + 0.0j, - -7.00000000e-18 + 0.0j, - 0.00000000e00 + 0.0j, - 2.50000000e-01 + 0.0j, - -7.00000000e-18 + 0.0j, - -7.00000000e-18 + 0.0j, - -2.77500000e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - 0.00000000e00 + 0.0j, - 4.17500000e-17 + 0.0j, - ], - [ - 4.85000000e-17 + 0.0j, - 4.85334346e-32 + 0.0j, - 1.38006319e-17 + 0.0j, - 1.38006319e-17 + 0.0j, - 4.85334346e-32 + 0.0j, - 4.85334346e-32 + 0.0j, - 1.38006319e-17 + 0.0j, - 1.38006319e-17 + 0.0j, - 4.85000000e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 2.50000000e-01 + 0.0j, - -2.77500000e-17 + 0.0j, - ], - [ - 4.77630626e-32 + 0.0j, - 4.17500000e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 4.17500000e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 4.17500000e-17 + 0.0j, - -2.77500000e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - ], -] diff --git a/test/python/algorithms/time_evolvers/variational/solvers/ode/__init__.py b/test/python/algorithms/time_evolvers/variational/solvers/ode/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/ode/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. diff --git a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_forward_euler_solver.py b/test/python/algorithms/time_evolvers/variational/solvers/ode/test_forward_euler_solver.py deleted file mode 100644 index 802f930931d5..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_forward_euler_solver.py +++ /dev/null @@ -1,47 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Test Forward Euler solver.""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from ddt import ddt, data, unpack -from scipy.integrate import solve_ivp - -from qiskit.algorithms.time_evolvers.variational.solvers.ode.forward_euler_solver import ( - ForwardEulerSolver, -) - - -@ddt -class TestForwardEulerSolver(QiskitAlgorithmsTestCase): - """Test Forward Euler solver.""" - - @unpack - @data((4, 16), (16, 35.52713678800501), (320, 53.261108839604795)) - def test_solve(self, timesteps, expected_result): - """Test Forward Euler solver for a simple ODE.""" - - y0 = [1] - - # pylint: disable=unused-argument - def func(time, y): - return y - - t_span = [0.0, 4.0] - sol1 = solve_ivp(func, t_span, y0, method=ForwardEulerSolver, num_t_steps=timesteps) - np.testing.assert_equal(sol1.y[-1][-1], expected_result) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_ode_function.py b/test/python/algorithms/time_evolvers/variational/solvers/ode/test_ode_function.py deleted file mode 100644 index e680f45063e5..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_ode_function.py +++ /dev/null @@ -1,147 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Test ODE function generator.""" - -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from qiskit.quantum_info import SparsePauliOp -from qiskit.circuit import Parameter -from qiskit.algorithms.time_evolvers.variational.solvers.var_qte_linear_solver import ( - VarQTELinearSolver, -) -from qiskit.algorithms.time_evolvers.variational.solvers.ode.ode_function import ( - OdeFunction, -) -from qiskit.algorithms.time_evolvers.variational import ( - ImaginaryMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 - - -class TestOdeFunctionGenerator(QiskitAlgorithmsTestCase): - """Test ODE function generator.""" - - def test_var_qte_ode_function(self): - """Test ODE function generator.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - - param_dict = {param: np.pi / 4 for param in parameters} - - var_principle = ImaginaryMcLachlanPrinciple() - - t_param = None - linear_solver = None - linear_solver = VarQTELinearSolver( - var_principle, - observable, - ansatz, - parameters, - t_param, - linear_solver, - ) - - time = 2 - ode_function_generator = OdeFunction(linear_solver, t_param=None, param_dict=param_dict) - - qte_ode_function = ode_function_generator.var_qte_ode_function(time, param_dict.values()) - - expected_qte_ode_function = [ - 0.442145, - -0.022081, - 0.106223, - -0.117468, - 0.251233, - 0.321256, - -0.062728, - -0.036209, - -0.509219, - -0.183459, - -0.050739, - -0.093163, - ] - - np.testing.assert_array_almost_equal(expected_qte_ode_function, qte_ode_function) - - def test_var_qte_ode_function_time_param(self): - """Test ODE function generator with time param.""" - t_param = Parameter("t") - - observable = SparsePauliOp( - ["II", "ZZ", "IZ", "ZI", "YY", "XX"], - np.array([t_param, 0.5716, 0.3435, -0.4347, 0.091, 0.091]), - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - - param_dict = {param: np.pi / 4 for param in parameters} - - var_principle = ImaginaryMcLachlanPrinciple() - - time = 2 - - linear_solver = None - varqte_linear_solver = VarQTELinearSolver( - var_principle, - observable, - ansatz, - parameters, - t_param, - linear_solver, - ) - ode_function_generator = OdeFunction( - varqte_linear_solver, t_param=t_param, param_dict=param_dict - ) - - qte_ode_function = ode_function_generator.var_qte_ode_function(time, param_dict.values()) - - expected_qte_ode_function = [ - 0.442145, - -0.022081, - 0.106223, - -0.117468, - 0.251233, - 0.321256, - -0.062728, - -0.036209, - -0.509219, - -0.183459, - -0.050739, - -0.093163, - ] - - np.testing.assert_array_almost_equal(expected_qte_ode_function, qte_ode_function, decimal=5) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_var_qte_ode_solver.py b/test/python/algorithms/time_evolvers/variational/solvers/ode/test_var_qte_ode_solver.py deleted file mode 100644 index 4492b3cc9eaf..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_var_qte_ode_solver.py +++ /dev/null @@ -1,127 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Test solver of ODEs.""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt, data, unpack -import numpy as np - -from qiskit.quantum_info import SparsePauliOp -from qiskit.algorithms.time_evolvers.variational.solvers.ode.forward_euler_solver import ( - ForwardEulerSolver, -) -from qiskit.algorithms.time_evolvers.variational.solvers.var_qte_linear_solver import ( - VarQTELinearSolver, -) -from qiskit.algorithms.time_evolvers.variational.solvers.ode.var_qte_ode_solver import ( - VarQTEOdeSolver, -) -from qiskit.algorithms.time_evolvers.variational.solvers.ode.ode_function import ( - OdeFunction, -) -from qiskit.algorithms.time_evolvers.variational import ( - ImaginaryMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 - - -@ddt -class TestVarQTEOdeSolver(QiskitAlgorithmsTestCase): - """Test solver of ODEs.""" - - @data( - ( - "RK45", - [ - -0.30076755873631345, - -0.8032811383782005, - 1.1674108371914734e-15, - 3.2293849116821145e-16, - 2.541585055586039, - 1.155475184255733, - -2.966331417968169e-16, - 9.604292449638343e-17, - ], - ), - ( - ForwardEulerSolver, - [ - -3.2707e-01, - -8.0960e-01, - 3.4323e-16, - 8.9034e-17, - 2.5290e00, - 1.1563e00, - 3.0227e-16, - -2.2769e-16, - ], - ), - ) - @unpack - def test_run_no_backend(self, ode_solver, expected_result): - """Test ODE solver with no backend.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 1 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - - init_param_values = np.zeros(len(parameters)) - for i in range(ansatz.num_qubits): - init_param_values[-(ansatz.num_qubits + i + 1)] = np.pi / 2 - - param_dict = dict(zip(parameters, init_param_values)) - - var_principle = ImaginaryMcLachlanPrinciple() - - time = 1 - - t_param = None - - linear_solver = None - linear_solver = VarQTELinearSolver( - var_principle, - observable, - ansatz, - parameters, - t_param, - linear_solver, - ) - ode_function_generator = OdeFunction(linear_solver, param_dict, t_param) - - var_qte_ode_solver = VarQTEOdeSolver( - list(param_dict.values()), - ode_function_generator, - ode_solver=ode_solver, - num_timesteps=25, - ) - - result, _, _ = var_qte_ode_solver.run(time) - - np.testing.assert_array_almost_equal(result, expected_result, decimal=4) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/solvers/test_varqte_linear_solver.py b/test/python/algorithms/time_evolvers/variational/solvers/test_varqte_linear_solver.py deleted file mode 100644 index 7b842b95cac0..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/test_varqte_linear_solver.py +++ /dev/null @@ -1,112 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Test solver of linear equations.""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase - -# fmt: off -from test.python.algorithms.time_evolvers.variational.solvers.expected_results.\ - test_varqte_linear_solver_expected_1 import expected_metric_res_1 -# fmt: on - -import numpy as np - -from qiskit.quantum_info import SparsePauliOp -from qiskit.algorithms.time_evolvers.variational import ( - ImaginaryMcLachlanPrinciple, -) -from qiskit.algorithms.time_evolvers.variational.solvers.var_qte_linear_solver import ( - VarQTELinearSolver, -) -from qiskit.circuit.library import EfficientSU2 - - -class TestVarQTELinearSolver(QiskitAlgorithmsTestCase): - """Test solver of linear equations.""" - - def test_solve_lse(self): - """Test SLE solver.""" - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(ansatz.num_qubits): - init_param_values[-(ansatz.num_qubits + i + 1)] = np.pi / 2 - - param_dict = dict(zip(parameters, init_param_values)) - - var_principle = ImaginaryMcLachlanPrinciple() - t_param = None - linear_solver = None - linear_solver = VarQTELinearSolver( - var_principle, - observable, - ansatz, - parameters, - t_param, - linear_solver, - ) - - nat_grad_res, metric_res, grad_res = linear_solver.solve_lse(param_dict) - - expected_nat_grad_res = [ - 3.43500000e-01, - -2.89800000e-01, - 2.43575264e-16, - 1.31792695e-16, - -9.61200000e-01, - -2.89800000e-01, - 1.27493709e-17, - 1.12587456e-16, - 3.43500000e-01, - -2.89800000e-01, - 3.69914720e-17, - 1.95052083e-17, - ] - - expected_grad_res = [ - (0.17174999999999926 - 0j), - (-0.21735000000000085 + 0j), - (4.114902862895087e-17 - 0j), - (4.114902862895087e-17 - 0j), - (-0.24030000000000012 + 0j), - (-0.21735000000000085 + 0j), - (4.114902862895087e-17 - 0j), - (4.114902862895087e-17 - 0j), - (0.17174999999999918 - 0j), - (-0.21735000000000076 + 0j), - (1.7789936190837538e-17 - 0j), - (-8.319872568662832e-17 + 0j), - ] - - np.testing.assert_array_almost_equal(nat_grad_res, expected_nat_grad_res, decimal=4) - np.testing.assert_array_almost_equal(grad_res, expected_grad_res, decimal=4) - np.testing.assert_array_almost_equal(metric_res, expected_metric_res_1, decimal=4) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/test_var_qite.py b/test/python/algorithms/time_evolvers/variational/test_var_qite.py deleted file mode 100644 index b2e431f09f42..000000000000 --- a/test/python/algorithms/time_evolvers/variational/test_var_qite.py +++ /dev/null @@ -1,333 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Test Variational Quantum Imaginary Time Evolution algorithm.""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.algorithms.gradients import LinCombQGT, LinCombEstimatorGradient -from qiskit.primitives import Estimator -from qiskit.quantum_info import SparsePauliOp, Pauli -from qiskit.utils import algorithm_globals -from qiskit.algorithms import TimeEvolutionProblem, VarQITE -from qiskit.algorithms.time_evolvers.variational import ( - ImaginaryMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 -from qiskit.quantum_info import Statevector - - -@ddt -class TestVarQITE(QiskitAlgorithmsTestCase): - """Test Variational Quantum Imaginary Time Evolution algorithm.""" - - def setUp(self): - super().setUp() - self.seed = 11 - np.random.seed(self.seed) - - def test_run_d_1_with_aux_ops(self): - """Test VarQITE for d = 1 and t = 1 with evaluating auxiliary operator and the Forward - Euler solver..""" - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - aux_ops = [Pauli("XX"), Pauli("YZ")] - d = 1 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(len(parameters)): - init_param_values[i] = np.pi / 2 - init_param_values[0] = 1 - time = 1 - - evolution_problem = TimeEvolutionProblem(observable, time, aux_operators=aux_ops) - - thetas_expected = [ - 0.87984606025879, - 2.04681975664763, - 2.68980594039104, - 2.75915988512186, - 2.38796546567171, - 1.78144857115127, - 2.13109162826101, - 1.9259609596416, - ] - - thetas_expected_shots = [ - 0.9392668013702317, - 1.8756706968454864, - 2.6915067128662398, - 2.655420131540562, - 2.174687086978046, - 1.6997059390911056, - 1.8056912289547045, - 1.939353810908912, - ] - - with self.subTest(msg="Test exact backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - estimator = Estimator() - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator) - var_principle = ImaginaryMcLachlanPrinciple(qgt, gradient) - - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=25 - ) - evolution_result = var_qite.evolve(evolution_problem) - - aux_ops = evolution_result.aux_ops_evaluated - - parameter_values = evolution_result.parameter_values[-1] - - expected_aux_ops = (-0.2177982985749799, 0.2556790598588627) - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=2 - ) - - np.testing.assert_array_almost_equal( - [result[0] for result in aux_ops], expected_aux_ops - ) - - with self.subTest(msg="Test shot-based backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - estimator = Estimator(options={"shots": 4096, "seed": self.seed}) - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator) - var_principle = ImaginaryMcLachlanPrinciple(qgt, gradient) - - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=25 - ) - evolution_result = var_qite.evolve(evolution_problem) - - aux_ops = evolution_result.aux_ops_evaluated - - parameter_values = evolution_result.parameter_values[-1] - - expected_aux_ops = (-0.24629853310903974, 0.2518122871921184) - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected_shots[i], decimal=2 - ) - - np.testing.assert_array_almost_equal( - [result[0] for result in aux_ops], expected_aux_ops - ) - - def test_run_d_1_t_7(self): - """Test VarQITE for d = 1 and t = 7 with RK45 ODE solver.""" - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 1 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(len(parameters)): - init_param_values[i] = np.pi / 2 - init_param_values[0] = 1 - var_principle = ImaginaryMcLachlanPrinciple() - - time = 7 - var_qite = VarQITE( - ansatz, init_param_values, var_principle, ode_solver="RK45", num_timesteps=25 - ) - - thetas_expected = [ - 0.828917365718767, - 1.88481074798033, - 3.14111335991238, - 3.14125849601269, - 2.33768562678401, - 1.78670990729437, - 2.04214275514208, - 2.04009918594422, - ] - - self._test_helper(observable, thetas_expected, time, var_qite, 2) - - def test_run_d_2(self): - """Test VarQITE for d = 2 and t = 1 with RK45 ODE solver.""" - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(len(parameters)): - init_param_values[i] = np.pi / 4 - - var_principle = ImaginaryMcLachlanPrinciple() - - time = 1 - var_qite = VarQITE( - ansatz, init_param_values, var_principle, ode_solver="RK45", num_timesteps=25 - ) - - thetas_expected = [ - 1.29495364023786, - 1.08970061333559, - 0.667488228710748, - 0.500122687902944, - 1.4377736672043, - 1.22881086103085, - 0.729773048146251, - 1.01698854755226, - 0.050807780587492, - 0.294828474947149, - 0.839305697704923, - 0.663689581255428, - ] - - self._test_helper(observable, thetas_expected, time, var_qite, 4) - - def test_run_d_1_time_dependent(self): - """Test VarQITE for d = 1 and a time-dependent Hamiltonian with the Forward Euler solver.""" - t_param = Parameter("t") - time = 1 - observable = SparsePauliOp(["I", "Z"], np.array([0, t_param])) - - x, y, z = [Parameter(s) for s in "xyz"] - ansatz = QuantumCircuit(1) - ansatz.rz(x, 0) - ansatz.ry(y, 0) - ansatz.rz(z, 0) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - x_val = 0 - y_val = np.pi / 2 - z_val = 0 - - init_param_values[0] = x_val - init_param_values[1] = y_val - init_param_values[2] = z_val - - evolution_problem = TimeEvolutionProblem(observable, time, t_param=t_param) - - thetas_expected = [1.83881002737137e-18, 2.43224994794434, -3.05311331771918e-18] - - thetas_expected_shots = [1.83881002737137e-18, 2.43224994794434, -3.05311331771918e-18] - - state_expected = Statevector([0.34849948 + 0.0j, 0.93730897 + 0.0j]).to_dict() - # the expected final state is Statevector([0.34849948+0.j, 0.93730897+0.j]) - - with self.subTest(msg="Test exact backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - estimator = Estimator() - var_principle = ImaginaryMcLachlanPrinciple() - - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=100 - ) - evolution_result = var_qite.evolve(evolution_problem) - evolved_state = evolution_result.evolved_state - parameter_values = evolution_result.parameter_values[-1] - - for key, evolved_value in Statevector(evolved_state).to_dict().items(): - # np.allclose works with complex numbers - self.assertTrue(np.allclose(evolved_value, state_expected[key], 1e-02)) - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=2 - ) - - with self.subTest(msg="Test shot-based backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - estimator = Estimator(options={"shots": 4 * 4096, "seed": self.seed}) - var_principle = ImaginaryMcLachlanPrinciple() - - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=100 - ) - - evolution_result = var_qite.evolve(evolution_problem) - - evolved_state = evolution_result.evolved_state - - parameter_values = evolution_result.parameter_values[-1] - - for key, evolved_value in Statevector(evolved_state).to_dict().items(): - # np.allclose works with complex numbers - self.assertTrue(np.allclose(evolved_value, state_expected[key], 1e-02)) - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected_shots[i], decimal=2 - ) - - def _test_helper(self, observable, thetas_expected, time, var_qite, decimal): - evolution_problem = TimeEvolutionProblem(observable, time) - evolution_result = var_qite.evolve(evolution_problem) - parameter_values = evolution_result.parameter_values[-1] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=decimal - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/test_var_qrte.py b/test/python/algorithms/time_evolvers/variational/test_var_qrte.py deleted file mode 100644 index e652125728b0..000000000000 --- a/test/python/algorithms/time_evolvers/variational/test_var_qrte.py +++ /dev/null @@ -1,319 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Test Variational Quantum Real Time Evolution algorithm.""" - -import unittest -import warnings -from test.python.algorithms import QiskitAlgorithmsTestCase - -from ddt import ddt -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter, ParameterVector -from qiskit.algorithms.gradients import LinCombQGT, DerivativeType, LinCombEstimatorGradient -from qiskit.primitives import Estimator -from qiskit.utils import algorithm_globals -from qiskit.quantum_info import SparsePauliOp, Pauli, Statevector -from qiskit.algorithms import TimeEvolutionProblem, VarQRTE -from qiskit.algorithms.time_evolvers.variational import ( - RealMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 - - -@ddt -class TestVarQRTE(QiskitAlgorithmsTestCase): - """Test Variational Quantum Real Time Evolution algorithm.""" - - def setUp(self): - super().setUp() - self.seed = 11 - np.random.seed(self.seed) - - def test_time_dependent_hamiltonian(self): - """Simple test case with a time dependent Hamiltonian.""" - t_param = Parameter("t") - hamiltonian = SparsePauliOp(["Z"], np.array(t_param)) - - x = ParameterVector("x", 3) - circuit = QuantumCircuit(1) - circuit.rz(x[0], 0) - circuit.ry(x[1], 0) - circuit.rz(x[2], 0) - - initial_parameters = np.array([0, np.pi / 2, 0]) - - def expected_state(time): - # possible with pen and paper as the Hamiltonian is diagonal - return 1 / np.sqrt(2) * np.array([np.exp(-0.5j * time**2), np.exp(0.5j * time**2)]) - - final_time = 0.75 - evolution_problem = TimeEvolutionProblem(hamiltonian, t_param=t_param, time=final_time) - estimator = Estimator() - varqrte = VarQRTE(circuit, initial_parameters, estimator=estimator) - - result = varqrte.evolve(evolution_problem) - - final_parameters = result.parameter_values[-1] - final_state = Statevector(circuit.assign_parameters(final_parameters)).to_dict() - final_expected_state = expected_state(final_time) - - for key, expected_value in final_state.items(): - self.assertTrue(np.allclose(final_expected_state[int(key)], expected_value, 1e-02)) - - def test_run_d_1_with_aux_ops(self): - """Test VarQRTE for d = 1 and t = 0.1 with evaluating auxiliary operators and the Forward - Euler solver.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - aux_ops = [Pauli("XX"), Pauli("YZ")] - d = 1 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(len(parameters)): - init_param_values[i] = np.pi / 2 - init_param_values[0] = 1 - - time = 0.1 - - evolution_problem = TimeEvolutionProblem(observable, time, aux_operators=aux_ops) - - thetas_expected = [ - 0.886841151529636, - 1.53852629218265, - 1.57099556659882, - 1.5889216657174, - 1.5996487153364, - 1.57018939515742, - 1.63950719260698, - 1.53853696496673, - ] - - thetas_expected_shots = [ - 0.886975892820015, - 1.53822607733397, - 1.57058096749141, - 1.59023223608564, - 1.60105707043745, - 1.57018042397236, - 1.64010900210835, - 1.53959523034133, - ] - - with self.subTest(msg="Test exact backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - estimator = Estimator() - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - var_principle = RealMcLachlanPrinciple(qgt, gradient) - - var_qrte = VarQRTE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=25 - ) - evolution_result = var_qrte.evolve(evolution_problem) - - aux_ops = evolution_result.aux_ops_evaluated - - parameter_values = evolution_result.parameter_values[-1] - - expected_aux_ops = [0.06836996703935797, 0.7711574493422457] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=2 - ) - - np.testing.assert_array_almost_equal( - [result[0] for result in aux_ops], expected_aux_ops - ) - - with self.subTest(msg="Test shot-based backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - estimator = Estimator(options={"shots": 4 * 4096, "seed": self.seed}) - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - var_principle = RealMcLachlanPrinciple(qgt, gradient) - - var_qrte = VarQRTE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=25 - ) - evolution_result = var_qrte.evolve(evolution_problem) - - aux_ops = evolution_result.aux_ops_evaluated - - parameter_values = evolution_result.parameter_values[-1] - - expected_aux_ops = [ - 0.070436, - 0.777938, - ] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected_shots[i], decimal=2 - ) - - np.testing.assert_array_almost_equal( - [result[0] for result in aux_ops], expected_aux_ops, decimal=2 - ) - - def test_run_d_2(self): - """Test VarQRTE for d = 2 and t = 1 with RK45 ODE solver.""" - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(len(parameters)): - init_param_values[i] = np.pi / 4 - estimator = Estimator() - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - - var_principle = RealMcLachlanPrinciple(qgt, gradient) - - param_dict = dict(zip(parameters, init_param_values)) - - time = 1 - var_qrte = VarQRTE(ansatz, param_dict, var_principle, ode_solver="RK45", num_timesteps=25) - - thetas_expected = [ - 0.348407744196573, - 0.919404626262464, - 1.18189219371626, - 0.771011177789998, - 0.734384256533924, - 0.965289520781899, - 1.14441687204195, - 1.17231927568571, - 1.03014771379412, - 0.867266309056347, - 0.699606368428206, - 0.610788576398685, - ] - - self._test_helper(observable, thetas_expected, time, var_qrte) - - def test_run_d_1_time_dependent(self): - """Test VarQRTE for d = 1 and a time-dependent Hamiltonian with the Forward Euler solver.""" - t_param = Parameter("t") - time = 1 - observable = SparsePauliOp(["I", "Z"], np.array([0, t_param])) - - x, y, z = [Parameter(s) for s in "xyz"] - ansatz = QuantumCircuit(1) - ansatz.rz(x, 0) - ansatz.ry(y, 0) - ansatz.rz(z, 0) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - x_val = 0 - y_val = np.pi / 2 - z_val = 0 - - init_param_values[0] = x_val - init_param_values[1] = y_val - init_param_values[2] = z_val - - evolution_problem = TimeEvolutionProblem(observable, time, t_param=t_param) - - thetas_expected = [1.27675647831902e-18, 1.5707963267949, 0.990000000000001] - - thetas_expected_shots = [0.00534345821469238, 1.56260960200375, 0.990017403734316] - - # the expected final state is Statevector([0.62289306-0.33467034j, 0.62289306+0.33467034j]) - - with self.subTest(msg="Test exact backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - estimator = Estimator() - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - var_principle = RealMcLachlanPrinciple(qgt, gradient) - - var_qrte = VarQRTE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=100 - ) - evolution_result = var_qrte.evolve(evolution_problem) - - parameter_values = evolution_result.parameter_values[-1] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=2 - ) - - with self.subTest(msg="Test shot-based backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - estimator = Estimator(options={"shots": 4 * 4096, "seed": self.seed}) - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - var_principle = RealMcLachlanPrinciple(qgt, gradient) - - var_qrte = VarQRTE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=100 - ) - - evolution_result = var_qrte.evolve(evolution_problem) - - parameter_values = evolution_result.parameter_values[-1] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected_shots[i], decimal=2 - ) - - def _test_helper(self, observable, thetas_expected, time, var_qrte): - evolution_problem = TimeEvolutionProblem(observable, time) - evolution_result = var_qrte.evolve(evolution_problem) - - parameter_values = evolution_result.parameter_values[-1] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal(float(parameter_value), thetas_expected[i], decimal=4) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/test_var_qte.py b/test/python/algorithms/time_evolvers/variational/test_var_qte.py deleted file mode 100644 index 4b92e4e460d0..000000000000 --- a/test/python/algorithms/time_evolvers/variational/test_var_qte.py +++ /dev/null @@ -1,84 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. -"""Test Variational Quantum Real Time Evolution algorithm.""" - -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase -from numpy.testing import assert_raises -from ddt import data, ddt -import numpy as np - -from qiskit.algorithms.time_evolvers.variational.var_qte import VarQTE -from qiskit.circuit import Parameter - - -@ddt -class TestVarQTE(QiskitAlgorithmsTestCase): - """Test Variational Quantum Time Evolution class methods.""" - - def setUp(self): - super().setUp() - self._parameters1 = [Parameter("a"), Parameter("b"), Parameter("c")] - - @data([1.4, 2, 3], np.asarray([1.4, 2, 3])) - def test_create_init_state_param_dict(self, param_values): - """Tests if a correct dictionary is created.""" - expected = dict(zip(self._parameters1, param_values)) - with self.subTest("Parameters values given as a list test."): - result = VarQTE._create_init_state_param_dict(param_values, self._parameters1) - np.testing.assert_equal(result, expected) - with self.subTest("Parameters values given as a dictionary test."): - result = VarQTE._create_init_state_param_dict( - dict(zip(self._parameters1, param_values)), self._parameters1 - ) - np.testing.assert_equal(result, expected) - with self.subTest("Parameters values given as a superset dictionary test."): - expected = dict( - zip( - [self._parameters1[0], self._parameters1[2]], [param_values[0], param_values[2]] - ) - ) - result = VarQTE._create_init_state_param_dict( - dict(zip(self._parameters1, param_values)), - [self._parameters1[0], self._parameters1[2]], - ) - np.testing.assert_equal(result, expected) - - @data([1.4, 2], np.asarray([1.4, 3]), {}, []) - def test_create_init_state_param_dict_errors_list(self, param_values): - """Tests if an error is raised.""" - with assert_raises(ValueError): - _ = VarQTE._create_init_state_param_dict(param_values, self._parameters1) - - @data([1.4, 2], np.asarray([1.4, 3])) - def test_create_init_state_param_dict_errors_subset(self, param_values): - """Tests if an error is raised if subset of parameters provided.""" - param_values_dict = dict(zip([self._parameters1[0], self._parameters1[2]], param_values)) - with assert_raises(ValueError): - _ = VarQTE._create_init_state_param_dict(param_values_dict, self._parameters1) - - @data("s") - def test_create_init_state_param_dict_errors_value(self, param_values): - """Tests if an error is raised if wrong input.""" - with assert_raises(ValueError): - _ = VarQTE._create_init_state_param_dict(param_values, self._parameters1) - - @data(Parameter("x"), 5) - def test_create_init_state_param_dict_errors_type(self, param_values): - """Tests if an error is raised if wrong input type.""" - with assert_raises(TypeError): - _ = VarQTE._create_init_state_param_dict(param_values, self._parameters1) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/__init__.py b/test/python/algorithms/time_evolvers/variational/variational_principles/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/__init__.py b/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/__init__.py deleted file mode 100644 index 62ef1f76c6f4..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. -"""Stores expected results that are lengthy.""" diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected1.py b/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected1.py deleted file mode 100644 index a26cb1b8726b..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected1.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. -"""Stores expected results that are lengthy.""" -expected_bound_metric_tensor_1 = [ - [ - 2.50000000e-01 + 0.0j, - 1.59600000e-33 + 0.0j, - 5.90075760e-18 + 0.0j, - -8.49242405e-19 + 0.0j, - 8.83883476e-02 + 0.0j, - 1.33253788e-17 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.40000000e-17 + 0.0j, - -1.41735435e-01 + 0.0j, - 3.12500000e-02 + 0.0j, - 1.00222087e-01 + 0.0j, - -3.12500000e-02 + 0.0j, - ], - [ - 1.59600000e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - 1.34350288e-17 + 0.0j, - 6.43502884e-18 + 0.0j, - -8.83883476e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - -8.45970869e-02 + 0.0j, - 7.54441738e-02 + 0.0j, - 1.48207521e-01 + 0.0j, - 2.00444174e-01 + 0.0j, - ], - [ - 5.90075760e-18 + 0.0j, - 1.34350288e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -4.41941738e-02 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.19638348e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - -5.14514565e-02 + 0.0j, - 6.89720869e-02 + 0.0j, - 1.04933262e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - ], - [ - -8.49242405e-19 + 0.0j, - 6.43502884e-18 + 0.0j, - -1.38777878e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - -4.41941738e-02 + 0.0j, - -6.25000000e-02 + 0.0j, - 3.12500000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - 5.14514565e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - 7.81250000e-03 + 0.0j, - 1.94162607e-02 + 0.0j, - ], - [ - 8.83883476e-02 + 0.0j, - -8.83883476e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - 2.34375000e-01 + 0.0j, - -1.10485435e-01 + 0.0j, - -2.02014565e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - 1.49547935e-02 + 0.0j, - -2.24896848e-02 + 0.0j, - -1.42172278e-03 + 0.0j, - -1.23822206e-01 + 0.0j, - ], - [ - 1.33253788e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - -6.25000000e-02 + 0.0j, - -1.10485435e-01 + 0.0j, - 2.18750000e-01 + 0.0j, - -2.68082618e-03 + 0.0j, - -1.59099026e-17 + 0.0j, - -1.57197815e-01 + 0.0j, - 2.53331304e-02 + 0.0j, - 9.82311963e-03 + 0.0j, - 1.06138957e-01 + 0.0j, - ], - [ - 6.25000000e-02 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.19638348e-01 + 0.0j, - 3.12500000e-02 + 0.0j, - -2.02014565e-02 + 0.0j, - -2.68082618e-03 + 0.0j, - 2.23881674e-01 + 0.0j, - 1.37944174e-01 + 0.0j, - -3.78033966e-02 + 0.0j, - 1.58423239e-01 + 0.0j, - 1.34535646e-01 + 0.0j, - -5.49651086e-02 + 0.0j, - ], - [ - 1.40000000e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - -4.41941738e-02 + 0.0j, - -1.59099026e-17 + 0.0j, - 1.37944174e-01 + 0.0j, - 2.50000000e-01 + 0.0j, - -2.10523539e-17 + 0.0j, - 1.15574269e-17 + 0.0j, - 9.75412607e-02 + 0.0j, - 5.71383476e-02 + 0.0j, - ], - [ - -1.41735435e-01 + 0.0j, - -8.45970869e-02 + 0.0j, - -5.14514565e-02 + 0.0j, - 5.14514565e-02 + 0.0j, - 1.49547935e-02 + 0.0j, - -1.57197815e-01 + 0.0j, - -3.78033966e-02 + 0.0j, - -2.10523539e-17 + 0.0j, - 1.95283753e-01 + 0.0j, - -3.82941440e-02 + 0.0j, - -6.11392595e-02 + 0.0j, - -4.51588288e-02 + 0.0j, - ], - [ - 3.12500000e-02 + 0.0j, - 7.54441738e-02 + 0.0j, - 6.89720869e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - -2.24896848e-02 + 0.0j, - 2.53331304e-02 + 0.0j, - 1.58423239e-01 + 0.0j, - 1.15574269e-17 + 0.0j, - -3.82941440e-02 + 0.0j, - 2.17629701e-01 + 0.0j, - 1.32431810e-01 + 0.0j, - -1.91961467e-02 + 0.0j, - ], - [ - 1.00222087e-01 + 0.0j, - 1.48207521e-01 + 0.0j, - 1.04933262e-02 + 0.0j, - 7.81250000e-03 + 0.0j, - -1.42172278e-03 + 0.0j, - 9.82311963e-03 + 0.0j, - 1.34535646e-01 + 0.0j, - 9.75412607e-02 + 0.0j, - -6.11392595e-02 + 0.0j, - 1.32431810e-01 + 0.0j, - 1.81683746e-01 + 0.0j, - 7.28902444e-02 + 0.0j, - ], - [ - -3.12500000e-02 + 0.0j, - 2.00444174e-01 + 0.0j, - -6.89720869e-02 + 0.0j, - 1.94162607e-02 + 0.0j, - -1.23822206e-01 + 0.0j, - 1.06138957e-01 + 0.0j, - -5.49651086e-02 + 0.0j, - 5.71383476e-02 + 0.0j, - -4.51588288e-02 + 0.0j, - -1.91961467e-02 + 0.0j, - 7.28902444e-02 + 0.0j, - 2.38616353e-01 + 0.0j, - ], -] diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected2.py b/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected2.py deleted file mode 100644 index 3a46f371787c..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected2.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. -"""Stores expected results that are lengthy.""" -expected_bound_metric_tensor_2 = [ - [ - 2.50000000e-01 + 0.0j, - 1.59600000e-33 + 0.0j, - 5.90075760e-18 + 0.0j, - -8.49242405e-19 + 0.0j, - 8.83883476e-02 + 0.0j, - 1.33253788e-17 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.40000000e-17 + 0.0j, - -1.41735435e-01 + 0.0j, - 3.12500000e-02 + 0.0j, - 1.00222087e-01 + 0.0j, - -3.12500000e-02 + 0.0j, - ], - [ - 1.59600000e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - 1.34350288e-17 + 0.0j, - 6.43502884e-18 + 0.0j, - -8.83883476e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - -8.45970869e-02 + 0.0j, - 7.54441738e-02 + 0.0j, - 1.48207521e-01 + 0.0j, - 2.00444174e-01 + 0.0j, - ], - [ - 5.90075760e-18 + 0.0j, - 1.34350288e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -4.41941738e-02 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.19638348e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - -5.14514565e-02 + 0.0j, - 6.89720869e-02 + 0.0j, - 1.04933262e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - ], - [ - -8.49242405e-19 + 0.0j, - 6.43502884e-18 + 0.0j, - -1.38777878e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - -4.41941738e-02 + 0.0j, - -6.25000000e-02 + 0.0j, - 3.12500000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - 5.14514565e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - 7.81250000e-03 + 0.0j, - 1.94162607e-02 + 0.0j, - ], - [ - 8.83883476e-02 + 0.0j, - -8.83883476e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - 2.34375000e-01 + 0.0j, - -1.10485435e-01 + 0.0j, - -2.02014565e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - 1.49547935e-02 + 0.0j, - -2.24896848e-02 + 0.0j, - -1.42172278e-03 + 0.0j, - -1.23822206e-01 + 0.0j, - ], - [ - 1.33253788e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - -6.25000000e-02 + 0.0j, - -1.10485435e-01 + 0.0j, - 2.18750000e-01 + 0.0j, - -2.68082618e-03 + 0.0j, - -1.59099026e-17 + 0.0j, - -1.57197815e-01 + 0.0j, - 2.53331304e-02 + 0.0j, - 9.82311963e-03 + 0.0j, - 1.06138957e-01 + 0.0j, - ], - [ - 6.25000000e-02 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.19638348e-01 + 0.0j, - 3.12500000e-02 + 0.0j, - -2.02014565e-02 + 0.0j, - -2.68082618e-03 + 0.0j, - 2.23881674e-01 + 0.0j, - 1.37944174e-01 + 0.0j, - -3.78033966e-02 + 0.0j, - 1.58423239e-01 + 0.0j, - 1.34535646e-01 + 0.0j, - -5.49651086e-02 + 0.0j, - ], - [ - 1.40000000e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - -4.41941738e-02 + 0.0j, - -1.59099026e-17 + 0.0j, - 1.37944174e-01 + 0.0j, - 2.50000000e-01 + 0.0j, - -2.10523539e-17 + 0.0j, - 1.15574269e-17 + 0.0j, - 9.75412607e-02 + 0.0j, - 5.71383476e-02 + 0.0j, - ], - [ - -1.41735435e-01 + 0.0j, - -8.45970869e-02 + 0.0j, - -5.14514565e-02 + 0.0j, - 5.14514565e-02 + 0.0j, - 1.49547935e-02 + 0.0j, - -1.57197815e-01 + 0.0j, - -3.78033966e-02 + 0.0j, - -2.10523539e-17 + 0.0j, - 1.95283753e-01 + 0.0j, - -3.82941440e-02 + 0.0j, - -6.11392595e-02 + 0.0j, - -4.51588288e-02 + 0.0j, - ], - [ - 3.12500000e-02 + 0.0j, - 7.54441738e-02 + 0.0j, - 6.89720869e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - -2.24896848e-02 + 0.0j, - 2.53331304e-02 + 0.0j, - 1.58423239e-01 + 0.0j, - 1.15574269e-17 + 0.0j, - -3.82941440e-02 + 0.0j, - 2.17629701e-01 + 0.0j, - 1.32431810e-01 + 0.0j, - -1.91961467e-02 + 0.0j, - ], - [ - 1.00222087e-01 + 0.0j, - 1.48207521e-01 + 0.0j, - 1.04933262e-02 + 0.0j, - 7.81250000e-03 + 0.0j, - -1.42172278e-03 + 0.0j, - 9.82311963e-03 + 0.0j, - 1.34535646e-01 + 0.0j, - 9.75412607e-02 + 0.0j, - -6.11392595e-02 + 0.0j, - 1.32431810e-01 + 0.0j, - 1.81683746e-01 + 0.0j, - 7.28902444e-02 + 0.0j, - ], - [ - -3.12500000e-02 + 0.0j, - 2.00444174e-01 + 0.0j, - -6.89720869e-02 + 0.0j, - 1.94162607e-02 + 0.0j, - -1.23822206e-01 + 0.0j, - 1.06138957e-01 + 0.0j, - -5.49651086e-02 + 0.0j, - 5.71383476e-02 + 0.0j, - -4.51588288e-02 + 0.0j, - -1.91961467e-02 + 0.0j, - 7.28902444e-02 + 0.0j, - 2.38616353e-01 + 0.0j, - ], -] diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected3.py b/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected3.py deleted file mode 100644 index 4790482e0db9..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected3.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. -"""Stores expected results that are lengthy.""" -expected_bound_metric_tensor_3 = [ - [ - -1.21000000e-34 + 0.00e00j, - 1.21000000e-34 + 2.50e-19j, - 1.76776695e-01 - 1.00e-18j, - -1.40000000e-17 + 0.00e00j, - -6.25000000e-02 + 0.00e00j, - 8.83883476e-02 - 1.25e-18j, - 1.69194174e-01 + 2.25e-18j, - 8.83883476e-02 - 2.50e-19j, - -7.27633476e-02 + 0.00e00j, - 9.75412607e-02 + 7.50e-19j, - 1.48398042e-02 - 1.75e-18j, - -9.75412607e-02 + 3.75e-18j, - ], - [ - 1.21000000e-34 + 2.50e-19j, - -1.21000000e-34 + 0.00e00j, - 1.10000000e-34 + 2.75e-18j, - 1.76776695e-01 - 2.25e-18j, - -6.25000000e-02 + 0.00e00j, - -8.83883476e-02 + 4.00e-18j, - 4.41941738e-02 - 1.25e-18j, - 1.76776695e-01 - 2.50e-19j, - 7.27633476e-02 - 7.50e-19j, - -9.75412607e-02 - 7.50e-19j, - 1.10485435e-02 - 7.50e-19j, - 2.74587393e-02 + 2.50e-19j, - ], - [ - 1.76776695e-01 - 1.00e-18j, - 1.10000000e-34 + 2.75e-18j, - -1.25000000e-01 + 0.00e00j, - -1.25000000e-01 + 0.00e00j, - -1.06694174e-01 + 1.25e-18j, - -6.25000000e-02 + 1.75e-18j, - -1.01332521e-01 + 7.50e-19j, - 4.67500000e-17 - 7.50e-19j, - 1.75206304e-02 + 5.00e-19j, - -8.57075215e-02 - 1.00e-18j, - -1.63277304e-01 + 1.00e-18j, - -1.56250000e-02 + 0.00e00j, - ], - [ - -1.40000000e-17 + 0.00e00j, - 1.76776695e-01 - 2.25e-18j, - -1.25000000e-01 + 0.00e00j, - -1.25000000e-01 + 0.00e00j, - 1.83058262e-02 - 1.50e-18j, - -1.50888348e-01 - 1.50e-18j, - -1.01332521e-01 + 2.50e-19j, - -8.83883476e-02 - 1.00e-18j, - -2.28822827e-02 - 1.00e-18j, - -1.16957521e-01 + 1.00e-18j, - -1.97208130e-01 + 0.00e00j, - -1.79457521e-01 + 1.25e-18j, - ], - [ - -6.25000000e-02 + 0.00e00j, - -6.25000000e-02 + 0.00e00j, - -1.06694174e-01 + 1.25e-18j, - 1.83058262e-02 - 1.50e-18j, - -1.56250000e-02 + 0.00e00j, - -2.20970869e-02 - 2.00e-18j, - 1.48992717e-01 - 1.00e-18j, - 2.60000000e-17 - 1.50e-18j, - -6.69614673e-02 - 5.00e-19j, - 2.00051576e-01 + 5.00e-19j, - 1.13640168e-01 + 1.25e-18j, - -4.83780325e-02 - 1.00e-18j, - ], - [ - 8.83883476e-02 - 1.25e-18j, - -8.83883476e-02 + 4.00e-18j, - -6.25000000e-02 + 1.75e-18j, - -1.50888348e-01 - 1.50e-18j, - -2.20970869e-02 - 2.00e-18j, - -3.12500000e-02 + 0.00e00j, - -2.85691738e-02 + 4.25e-18j, - 1.76776695e-01 + 0.00e00j, - 5.52427173e-03 + 1.00e-18j, - -1.29346478e-01 + 5.00e-19j, - -4.81004238e-02 + 4.25e-18j, - 5.27918696e-02 + 2.50e-19j, - ], - [ - 1.69194174e-01 + 2.25e-18j, - 4.41941738e-02 - 1.25e-18j, - -1.01332521e-01 + 7.50e-19j, - -1.01332521e-01 + 2.50e-19j, - 1.48992717e-01 - 1.00e-18j, - -2.85691738e-02 + 4.25e-18j, - -2.61183262e-02 + 0.00e00j, - -6.88900000e-33 + 0.00e00j, - 6.62099510e-02 - 1.00e-18j, - -2.90767610e-02 + 1.75e-18j, - -1.24942505e-01 + 0.00e00j, - -1.72430217e-02 + 2.50e-19j, - ], - [ - 8.83883476e-02 - 2.50e-19j, - 1.76776695e-01 - 2.50e-19j, - 4.67500000e-17 - 7.50e-19j, - -8.83883476e-02 - 1.00e-18j, - 2.60000000e-17 - 1.50e-18j, - 1.76776695e-01 + 0.00e00j, - -6.88900000e-33 + 0.00e00j, - -6.88900000e-33 + 0.00e00j, - 1.79457521e-01 - 1.75e-18j, - -5.33470869e-02 + 2.00e-18j, - -9.56456304e-02 + 3.00e-18j, - -1.32582521e-01 + 2.50e-19j, - ], - [ - -7.27633476e-02 + 0.00e00j, - 7.27633476e-02 - 7.50e-19j, - 1.75206304e-02 + 5.00e-19j, - -2.28822827e-02 - 1.00e-18j, - -6.69614673e-02 - 5.00e-19j, - 5.52427173e-03 + 1.00e-18j, - 6.62099510e-02 - 1.00e-18j, - 1.79457521e-01 - 1.75e-18j, - -5.47162473e-02 + 0.00e00j, - -4.20854047e-02 + 4.00e-18j, - -7.75494553e-02 - 2.50e-18j, - -2.49573723e-02 + 7.50e-19j, - ], - [ - 9.75412607e-02 + 7.50e-19j, - -9.75412607e-02 - 7.50e-19j, - -8.57075215e-02 - 1.00e-18j, - -1.16957521e-01 + 1.00e-18j, - 2.00051576e-01 + 5.00e-19j, - -1.29346478e-01 + 5.00e-19j, - -2.90767610e-02 + 1.75e-18j, - -5.33470869e-02 + 2.00e-18j, - -4.20854047e-02 + 4.00e-18j, - -3.23702991e-02 + 0.00e00j, - -4.70257118e-02 + 0.00e00j, - 1.22539288e-01 - 2.25e-18j, - ], - [ - 1.48398042e-02 - 1.75e-18j, - 1.10485435e-02 - 7.50e-19j, - -1.63277304e-01 + 1.00e-18j, - -1.97208130e-01 + 0.00e00j, - 1.13640168e-01 + 1.25e-18j, - -4.81004238e-02 + 4.25e-18j, - -1.24942505e-01 + 0.00e00j, - -9.56456304e-02 + 3.00e-18j, - -7.75494553e-02 - 2.50e-18j, - -4.70257118e-02 + 0.00e00j, - -6.83162540e-02 + 0.00e00j, - -2.78870598e-02 + 0.00e00j, - ], - [ - -9.75412607e-02 + 3.75e-18j, - 2.74587393e-02 + 2.50e-19j, - -1.56250000e-02 + 0.00e00j, - -1.79457521e-01 + 1.25e-18j, - -4.83780325e-02 - 1.00e-18j, - 5.27918696e-02 + 2.50e-19j, - -1.72430217e-02 + 2.50e-19j, - -1.32582521e-01 + 2.50e-19j, - -2.49573723e-02 + 7.50e-19j, - 1.22539288e-01 - 2.25e-18j, - -2.78870598e-02 + 0.00e00j, - -1.13836467e-02 + 0.00e00j, - ], -] diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/__init__.py b/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py b/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py deleted file mode 100644 index 8bb0f0d7c20d..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py +++ /dev/null @@ -1,115 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Test imaginary McLachlan's variational principle.""" - -import unittest - -# fmt: off -from test.python.algorithms.time_evolvers.variational.variational_principles.expected_results.\ - test_imaginary_mc_lachlan_variational_principle_expected1 import expected_bound_metric_tensor_1 -# fmt: on -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np - -from qiskit.quantum_info import SparsePauliOp -from qiskit.algorithms.time_evolvers.variational import ( - ImaginaryMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 -from qiskit.algorithms.gradients import LinCombEstimatorGradient, DerivativeType -from qiskit.primitives import Estimator - - -class TestImaginaryMcLachlanPrinciple(QiskitAlgorithmsTestCase): - """Test imaginary McLachlan's variational principle.""" - - def test_calc_metric_tensor(self): - """Test calculating a metric tensor.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - param_dict = {param: np.pi / 4 for param in parameters} - var_principle = ImaginaryMcLachlanPrinciple() - - bound_metric_tensor = var_principle.metric_tensor(ansatz, list(param_dict.values())) - - np.testing.assert_almost_equal(bound_metric_tensor, expected_bound_metric_tensor_1) - - def test_calc_calc_evolution_gradient(self): - """Test calculating evolution gradient.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - param_dict = {param: np.pi / 4 for param in parameters} - var_principle = ImaginaryMcLachlanPrinciple() - - bound_evolution_gradient = var_principle.evolution_gradient( - observable, ansatz, list(param_dict.values()), parameters - ) - - expected_evolution_gradient = [ - (0.19308934095957098 - 1.4e-17j), - (0.007027674650099142 - 0j), - (0.03192524520091862 - 0j), - (-0.06810314606309673 - 1e-18j), - (0.07590371669521798 - 7e-18j), - (0.11891968269385343 + 1.5e-18j), - (-0.0012030273438232639 + 0j), - (-0.049885258804562266 + 1.8500000000000002e-17j), - (-0.20178860797540302 - 5e-19j), - (-0.0052269232310933195 + 1e-18j), - (0.022892905637005266 - 3e-18j), - (-0.022892905637005294 + 3.5e-18j), - ] - - np.testing.assert_almost_equal(bound_evolution_gradient, expected_evolution_gradient) - - def test_gradient_setting(self): - """Test reactions to wrong gradient settings..""" - estimator = Estimator() - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - - with self.assertWarns(Warning): - var_principle = ImaginaryMcLachlanPrinciple(gradient=gradient) - - np.testing.assert_equal(var_principle.gradient._derivative_type, DerivativeType.REAL) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/real/__init__.py b/test/python/algorithms/time_evolvers/variational/variational_principles/real/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/real/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py b/test/python/algorithms/time_evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py deleted file mode 100644 index 5a314979d5de..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py +++ /dev/null @@ -1,120 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# 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. - -"""Test real McLachlan's variational principle.""" - -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase - -# fmt: off -from test.python.algorithms.time_evolvers.variational.variational_principles.expected_results.\ - test_imaginary_mc_lachlan_variational_principle_expected2 import expected_bound_metric_tensor_2 -# fmt: on -import numpy as np - -from qiskit.quantum_info import SparsePauliOp -from qiskit.algorithms.time_evolvers.variational import ( - RealMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 -from qiskit.algorithms.gradients import LinCombEstimatorGradient, DerivativeType -from qiskit.primitives import Estimator - - -class TestRealMcLachlanPrinciple(QiskitAlgorithmsTestCase): - """Test real McLachlan's variational principle.""" - - def test_calc_calc_metric_tensor(self): - """Test calculating a metric tensor.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - param_dict = {param: np.pi / 4 for param in parameters} - var_principle = RealMcLachlanPrinciple() - - bound_metric_tensor = var_principle.metric_tensor(ansatz, list(param_dict.values())) - - np.testing.assert_almost_equal( - bound_metric_tensor, expected_bound_metric_tensor_2, decimal=5 - ) - - def test_calc_evolution_gradient(self): - """Test calculating evolution gradient.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - param_dict = {param: np.pi / 4 for param in parameters} - var_principle = RealMcLachlanPrinciple() - - bound_evolution_gradient = var_principle.evolution_gradient( - observable, ansatz, list(param_dict.values()), parameters - ) - - expected_evolution_gradient = [ - (-0.04514911474522546 + 4e-18j), - (0.0963123928027075 - 1.5e-18j), - (0.1365347823673539 - 7e-18j), - (0.004969316401057883 - 4.9999999999999996e-18j), - (-0.003843833929692342 - 4.999999999999998e-19j), - (0.07036988622493834 - 7e-18j), - (0.16560609099860682 - 3.5e-18j), - (0.16674183768051887 + 1e-18j), - (-0.03843296670360974 - 6e-18j), - (0.08891074158680243 - 6e-18j), - (0.06425681697616654 + 7e-18j), - (-0.03172376682078948 - 7e-18j), - ] - - np.testing.assert_almost_equal( - bound_evolution_gradient, expected_evolution_gradient, decimal=5 - ) - - def test_gradient_setting(self): - """Test reactions to wrong gradient settings..""" - estimator = Estimator() - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.REAL) - - with self.assertWarns(Warning): - var_principle = RealMcLachlanPrinciple(gradient=gradient) - - np.testing.assert_equal(var_principle.gradient._derivative_type, DerivativeType.IMAG) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/utils/__init__.py b/test/python/algorithms/utils/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/test/python/algorithms/utils/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. diff --git a/test/python/algorithms/utils/test_validate_bounds.py b/test/python/algorithms/utils/test_validate_bounds.py deleted file mode 100644 index e4cc42ad154f..000000000000 --- a/test/python/algorithms/utils/test_validate_bounds.py +++ /dev/null @@ -1,57 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Test validate bounds.""" - -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from unittest.mock import Mock - -import numpy as np - -from qiskit.algorithms.utils import validate_bounds -from qiskit.utils import algorithm_globals - - -class TestValidateBounds(QiskitAlgorithmsTestCase): - """Test the ``validate_bounds`` utility function.""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - self.bounds = [(-np.pi / 2, np.pi / 2)] - self.ansatz = Mock() - - def test_with_no_ansatz_bounds(self): - """Test with no ansatz bounds.""" - self.ansatz.num_parameters = 1 - self.ansatz.parameter_bounds = None - bounds = validate_bounds(self.ansatz) - self.assertEqual(bounds, [(None, None)]) - - def test_with_ansatz_bounds(self): - """Test with ansatz bounds.""" - self.ansatz.num_parameters = 1 - self.ansatz.parameter_bounds = self.bounds - bounds = validate_bounds(self.ansatz) - self.assertEqual(bounds, self.bounds) - - def test_with_mismatched_num_params(self): - """Test with a mismatched number of parameters and bounds""" - self.ansatz.num_parameters = 2 - self.ansatz.parameter_bounds = self.bounds - with self.assertRaises(ValueError): - _ = validate_bounds(self.ansatz) diff --git a/test/python/algorithms/utils/test_validate_initial_point.py b/test/python/algorithms/utils/test_validate_initial_point.py deleted file mode 100644 index 28854b485fee..000000000000 --- a/test/python/algorithms/utils/test_validate_initial_point.py +++ /dev/null @@ -1,54 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Test validate initial point.""" - -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from unittest.mock import Mock - -import numpy as np - -from qiskit.algorithms.utils import validate_initial_point -from qiskit.utils import algorithm_globals - - -class TestValidateInitialPoint(QiskitAlgorithmsTestCase): - """Test the ``validate_initial_point`` utility function.""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - self.ansatz = Mock() - self.ansatz.num_parameters = 1 - - def test_with_no_initial_point_or_bounds(self): - """Test with no user-defined initial point and no ansatz bounds.""" - self.ansatz.parameter_bounds = None - initial_point = validate_initial_point(None, self.ansatz) - np.testing.assert_array_almost_equal(initial_point, [1.721111]) - - def test_with_no_initial_point(self): - """Test with no user-defined initial point with ansatz bounds.""" - self.ansatz.parameter_bounds = [(-np.pi / 2, np.pi / 2)] - initial_point = validate_initial_point(None, self.ansatz) - np.testing.assert_array_almost_equal(initial_point, [0.430278]) - - def test_with_mismatched_params(self): - """Test with mistmatched parameters and bounds..""" - self.ansatz.parameter_bounds = None - with self.assertRaises(ValueError): - _ = validate_initial_point([1.0, 2.0], self.ansatz) diff --git a/test/python/circuit/library/test_blueprintcircuit.py b/test/python/circuit/library/test_blueprintcircuit.py index 974e4c1ead32..0ccd5c40ebbc 100644 --- a/test/python/circuit/library/test_blueprintcircuit.py +++ b/test/python/circuit/library/test_blueprintcircuit.py @@ -16,8 +16,15 @@ from ddt import ddt, data from qiskit.test.base import QiskitTestCase -from qiskit.circuit import QuantumRegister, Parameter, QuantumCircuit, Gate, Instruction -from qiskit.circuit.library import BlueprintCircuit +from qiskit.circuit import ( + QuantumRegister, + Parameter, + QuantumCircuit, + Gate, + Instruction, + CircuitInstruction, +) +from qiskit.circuit.library import BlueprintCircuit, XGate class MockBlueprint(BlueprintCircuit): @@ -139,6 +146,34 @@ def test_to_gate_and_instruction(self, method): gate = circuit.to_instruction() self.assertIsInstance(gate, Instruction) + def test_build_before_appends(self): + """Test that both forms of direct append (public and semi-public) function correctly.""" + + class DummyBlueprint(BlueprintCircuit): + """Dummy circuit.""" + + def _check_configuration(self, raise_on_failure=True): + return True + + def _build(self): + super()._build() + self.z(0) + + expected = QuantumCircuit(2) + expected.z(0) + expected.x(0) + + qr = QuantumRegister(2, "q") + mock = DummyBlueprint() + mock.add_register(qr) + mock.append(XGate(), [qr[0]], []) + self.assertEqual(expected, mock) + + mock = DummyBlueprint() + mock.add_register(qr) + mock._append(CircuitInstruction(XGate(), (qr[0],), ())) + self.assertEqual(expected, mock) + if __name__ == "__main__": unittest.main() diff --git a/test/python/circuit/library/test_functional_pauli_rotations.py b/test/python/circuit/library/test_functional_pauli_rotations.py index 38f6568f117a..294a2b3282f0 100644 --- a/test/python/circuit/library/test_functional_pauli_rotations.py +++ b/test/python/circuit/library/test_functional_pauli_rotations.py @@ -84,7 +84,7 @@ def test_polynomial_rotations_mutability(self): with self.subTest(msg="missing number of state qubits"): with self.assertRaises(AttributeError): # no state qubits set - print(polynomial_rotations.draw()) + _ = str(polynomial_rotations.draw()) with self.subTest(msg="default setup, just setting number of state qubits"): polynomial_rotations.num_state_qubits = 2 @@ -121,7 +121,7 @@ def test_linear_rotations_mutability(self): with self.subTest(msg="missing number of state qubits"): with self.assertRaises(AttributeError): # no state qubits set - print(linear_rotation.draw()) + _ = str(linear_rotation.draw()) with self.subTest(msg="default setup, just setting number of state qubits"): linear_rotation.num_state_qubits = 2 @@ -171,7 +171,7 @@ def test_piecewise_linear_rotations_mutability(self): with self.subTest(msg="missing number of state qubits"): with self.assertRaises(AttributeError): # no state qubits set - print(pw_linear_rotations.draw()) + _ = str(pw_linear_rotations.draw()) with self.subTest(msg="default setup, just setting number of state qubits"): pw_linear_rotations.num_state_qubits = 2 diff --git a/test/python/circuit/library/test_integer_comparator.py b/test/python/circuit/library/test_integer_comparator.py index 2c6459cf38e3..55bd2b9071af 100644 --- a/test/python/circuit/library/test_integer_comparator.py +++ b/test/python/circuit/library/test_integer_comparator.py @@ -71,13 +71,13 @@ def test_mutability(self): with self.subTest(msg="missing num state qubits and value"): with self.assertRaises(AttributeError): - print(comp.draw()) + _ = str(comp.draw()) comp.num_state_qubits = 2 with self.subTest(msg="missing value"): with self.assertRaises(AttributeError): - print(comp.draw()) + _ = str(comp.draw()) comp.value = 0 comp.geq = True diff --git a/test/python/circuit/library/test_nlocal.py b/test/python/circuit/library/test_nlocal.py index f20377368ed4..62e7912606b8 100644 --- a/test/python/circuit/library/test_nlocal.py +++ b/test/python/circuit/library/test_nlocal.py @@ -378,7 +378,7 @@ def test_pairwise_entanglement_raises(self): # pairwise entanglement is only defined if the entangling gate has 2 qubits with self.assertRaises(ValueError): - print(nlocal.draw()) + _ = str(nlocal.draw()) def test_entanglement_by_list(self): """Test setting the entanglement by list. diff --git a/test/python/circuit/library/test_overlap.py b/test/python/circuit/library/test_overlap.py index 4ac765d54af8..fa5e4af05c0d 100644 --- a/test/python/circuit/library/test_overlap.py +++ b/test/python/circuit/library/test_overlap.py @@ -30,7 +30,7 @@ def test_identity(self): unitary.assign_parameters(np.random.random(size=unitary.num_parameters), inplace=True) overlap = UnitaryOverlap(unitary, unitary) - self.assertTrue(abs(Statevector.from_instruction(overlap)[0] - 1) < 1e-15) + self.assertLess(abs(Statevector.from_instruction(overlap)[0] - 1), 1e-12) def test_parameterized_identity(self): """Test identity is returned""" @@ -40,7 +40,7 @@ def test_parameterized_identity(self): rands = np.random.random(size=unitary.num_parameters) double_rands = np.hstack((rands, rands)) overlap.assign_parameters(double_rands, inplace=True) - self.assertTrue(abs(Statevector.from_instruction(overlap)[0] - 1) < 1e-15) + self.assertLess(abs(Statevector.from_instruction(overlap)[0] - 1), 1e-12) def test_two_parameterized_inputs(self): """Test two parameterized inputs""" @@ -82,6 +82,15 @@ def test_partial_parameterized_inputs2(self): overlap = UnitaryOverlap(unitary1, unitary2) self.assertEqual(overlap.num_parameters, unitary1.num_parameters) + def test_barrier(self): + """Test that barriers on input circuits are well handled""" + unitary1 = EfficientSU2(1, reps=0) + unitary1.barrier() + unitary2 = EfficientSU2(1, reps=1) + unitary2.barrier() + overlap = UnitaryOverlap(unitary1, unitary2) + self.assertEqual(overlap.num_parameters, unitary1.num_parameters + unitary2.num_parameters) + def test_measurements(self): """Test that exception is thrown for measurements""" unitary1 = EfficientSU2(2) diff --git a/test/python/circuit/library/test_permutation.py b/test/python/circuit/library/test_permutation.py index bf4da582b6ad..25bfa0bfae49 100644 --- a/test/python/circuit/library/test_permutation.py +++ b/test/python/circuit/library/test_permutation.py @@ -170,7 +170,6 @@ def test_qpy(self): circuit.cx(0, 1) circuit.append(PermutationGate([1, 2, 0]), [2, 4, 5]) circuit.h(4) - print(circuit) qpy_file = io.BytesIO() dump(circuit, qpy_file) diff --git a/test/python/circuit/library/test_piecewise_chebyshev.py b/test/python/circuit/library/test_piecewise_chebyshev.py index 4b8cb6054e1d..c721fe47ee67 100644 --- a/test/python/circuit/library/test_piecewise_chebyshev.py +++ b/test/python/circuit/library/test_piecewise_chebyshev.py @@ -103,7 +103,7 @@ def f_x_1(x): with self.subTest(msg="missing number of state qubits"): with self.assertRaises(AttributeError): # no state qubits set - print(pw_approximation.draw()) + _ = str(pw_approximation.draw()) with self.subTest(msg="default setup, just setting number of state qubits"): pw_approximation.num_state_qubits = 2 diff --git a/test/python/circuit/library/test_weighted_adder.py b/test/python/circuit/library/test_weighted_adder.py index 7bfe614e0bd6..db33156aa0a2 100644 --- a/test/python/circuit/library/test_weighted_adder.py +++ b/test/python/circuit/library/test_weighted_adder.py @@ -67,7 +67,7 @@ def test_mutability(self): with self.subTest(msg="missing number of state qubits"): with self.assertRaises(AttributeError): - print(adder.draw()) + _ = str(adder.draw()) with self.subTest(msg="default weights"): adder.num_state_qubits = 3 @@ -81,7 +81,7 @@ def test_mutability(self): with self.subTest(msg="mismatching number of state qubits and weights"): with self.assertRaises(ValueError): adder.weights = [0, 1, 2, 3] - print(adder.draw()) + _ = str(adder.draw()) with self.subTest(msg="change all attributes"): adder.num_state_qubits = 4 diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index 1cd66723ecfd..11770d896bc6 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -17,7 +17,7 @@ from ddt import data, ddt from qiskit import BasicAer, ClassicalRegister, QuantumCircuit, QuantumRegister, execute -from qiskit.circuit import Gate, Instruction, Measure, Parameter +from qiskit.circuit import Gate, Instruction, Measure, Parameter, Barrier from qiskit.circuit.bit import Bit from qiskit.circuit.classicalregister import Clbit from qiskit.circuit.exceptions import CircuitError @@ -156,6 +156,8 @@ def test_append_rejects_bad_arguments_opaque(self, bad_arg): qc.append(inst, bad_arg, [0, 1]) with self.assertRaisesRegex(CircuitError, "The amount of clbit arguments"): qc.append(inst, [0, 1], bad_arg) + with self.assertRaisesRegex(CircuitError, "The amount of qubit arguments"): + qc.append(Barrier(4), bad_arg) def test_anding_self(self): """Test that qc &= qc finishes, which can be prone to infinite while-loops. diff --git a/test/python/circuit/test_piecewise_polynomial.py b/test/python/circuit/test_piecewise_polynomial.py index 9bc34179ba63..e84cc35de87b 100644 --- a/test/python/circuit/test_piecewise_polynomial.py +++ b/test/python/circuit/test_piecewise_polynomial.py @@ -101,7 +101,7 @@ def pw_poly(x): with self.subTest(msg="missing number of state qubits"): with self.assertRaises(AttributeError): # no state qubits set - print(pw_polynomial_rotations.draw()) + _ = str(pw_polynomial_rotations.draw()) with self.subTest(msg="default setup, just setting number of state qubits"): pw_polynomial_rotations.num_state_qubits = 2 diff --git a/test/python/circuit/test_singleton.py b/test/python/circuit/test_singleton.py index ffa3f6d076bb..ebc656f8d933 100644 --- a/test/python/circuit/test_singleton.py +++ b/test/python/circuit/test_singleton.py @@ -14,7 +14,7 @@ """ -Tests for singleton gate behavior +Tests for singleton gate and instruction behavior """ import copy @@ -36,15 +36,16 @@ XGate, C4XGate, ) +from qiskit.circuit import Measure, Reset from qiskit.circuit import Clbit, QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit.circuit.singleton import SingletonGate, SingletonInstruction +from qiskit.circuit.singleton import SingletonGate from qiskit.converters import dag_to_circuit, circuit_to_dag from qiskit.test.base import QiskitTestCase -class TestSingletonGate(QiskitTestCase): - """Qiskit SingletonGate tests.""" +class TestSingleton(QiskitTestCase): + """Qiskit SingletonGate and SingletonInstruction tests.""" def test_default_singleton(self): gate = HGate() @@ -325,20 +326,38 @@ def __init__(self, x): self.assertEqual(gate.x, 1) self.assertIsNot(MyAbstractGate(1), MyAbstractGate(1)) - def test_inherit_singleton(self): - class Measure(SingletonInstruction): - def __init__(self): - super().__init__("measure", 1, 1, []) + def test_return_type_singleton_instructions(self): + measure = Measure() + new_measure = Measure() + self.assertIs(measure, new_measure) + self.assertIs(measure.base_class, Measure) + self.assertIsInstance(measure, Measure) + + reset = Reset() + new_reset = Reset() + self.assertIs(reset, new_reset) + self.assertIs(reset.base_class, Reset) + self.assertIsInstance(reset, Reset) + + def test_singleton_instruction_integration(self): + measure = Measure() + reset = Reset() + qc = QuantumCircuit(1, 1) + qc.measure(0, 0) + qc.reset(0) + self.assertIs(qc.data[0].operation, measure) + self.assertIs(qc.data[1].operation, reset) + def test_inherit_singleton_instructions(self): class ESPMeasure(Measure): pass - base = Measure() - esp = ESPMeasure() - self.assertIs(esp, ESPMeasure()) - self.assertIsNot(esp, base) - self.assertIs(base.base_class, Measure) - self.assertIs(esp.base_class, ESPMeasure) + measure_base = Measure() + esp_measure = ESPMeasure() + self.assertIs(esp_measure, ESPMeasure()) + self.assertIsNot(esp_measure, measure_base) + self.assertIs(measure_base.base_class, Measure) + self.assertIs(esp_measure.base_class, ESPMeasure) def test_singleton_with_default(self): # Explicitly setting the label to its default. diff --git a/test/python/compiler/test_assembler.py b/test/python/compiler/test_assembler.py index 2641012857a6..56f7e90b7af8 100644 --- a/test/python/compiler/test_assembler.py +++ b/test/python/compiler/test_assembler.py @@ -27,7 +27,6 @@ from qiskit.pulse import Schedule, Acquire, Play from qiskit.pulse.channels import MemorySlot, AcquireChannel, DriveChannel, MeasureChannel from qiskit.pulse.configuration import Kernel, Discriminator -from qiskit.pulse.library import gaussian from qiskit.qobj import QasmQobj, PulseQobj from qiskit.qobj.utils import MeasLevel, MeasReturnType from qiskit.pulse.macros import measure @@ -1201,7 +1200,7 @@ def test_pulse_name_conflicts_in_other_schedule(self): ch_d0 = pulse.DriveChannel(0) for amp in (0.1, 0.2): sched = Schedule() - sched += Play(gaussian(duration=100, amp=amp, sigma=30, name="my_pulse"), ch_d0) + sched += Play(pulse.Gaussian(duration=100, amp=amp, sigma=30, name="my_pulse"), ch_d0) sched += measure(qubits=[0], backend=backend) << 100 schedules.append(sched) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 9a54bdc7da0b..98db50a28b08 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -87,7 +87,7 @@ from qiskit.test import QiskitTestCase, slow_test from qiskit.tools import parallel from qiskit.transpiler import CouplingMap, Layout, PassManager, TransformationPass -from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.exceptions import TranspilerError, CircuitTooWideForTarget from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements, GateDirection, VF2PostLayout from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager, level_0_pass_manager @@ -987,7 +987,7 @@ def test_check_circuit_width(self): qc = QuantumCircuit(15, 15) - with self.assertRaises(TranspilerError): + with self.assertRaises(CircuitTooWideForTarget): transpile(qc, coupling_map=cmap) @data(0, 1, 2, 3) diff --git a/test/python/dagcircuit/test_collect_blocks.py b/test/python/dagcircuit/test_collect_blocks.py index adcfd2be2eed..2cfae5486083 100644 --- a/test/python/dagcircuit/test_collect_blocks.py +++ b/test/python/dagcircuit/test_collect_blocks.py @@ -914,6 +914,23 @@ def test_split_layers_dagdependency(self): self.assertEqual(len(blocks[2]), 1) self.assertEqual(len(blocks[3]), 1) + def test_block_collapser_register_condition(self): + """Test that BlockCollapser can handle a register being used more than once.""" + qc = QuantumCircuit(1, 2) + qc.x(0).c_if(qc.cregs[0], 0) + qc.y(0).c_if(qc.cregs[0], 1) + + dag = circuit_to_dag(qc) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda _: True, split_blocks=False, min_block_size=1 + ) + dag = BlockCollapser(dag).collapse_to_operation(blocks, lambda circ: circ.to_instruction()) + collapsed_qc = dag_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 1) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 2) + if __name__ == "__main__": unittest.main() diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 9d73ac5a1af0..b539ff3d5da3 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -487,18 +487,6 @@ def test_apply_operation_back(self): self.assertEqual(len(list(self.dag.nodes())), 16) self.assertEqual(len(list(self.dag.edges())), 17) - def test_apply_operation_rejects_none(self): - """Test that the ``apply_operation_*`` methods warn when given ``None``.""" - noop = Instruction("noop", 0, 0, []) - with self.assertWarnsRegex(DeprecationWarning, "Passing 'None'"): - self.dag.apply_operation_back(noop, None, ()) - with self.assertWarnsRegex(DeprecationWarning, "Passing 'None'"): - self.dag.apply_operation_back(noop, (), None) - with self.assertWarnsRegex(DeprecationWarning, "Passing 'None'"): - self.dag.apply_operation_front(noop, None, ()) - with self.assertWarnsRegex(DeprecationWarning, "Passing 'None'"): - self.dag.apply_operation_front(noop, (), None) - def test_edges(self): """Test that DAGCircuit.edges() behaves as expected with ops.""" x_gate = XGate().c_if(*self.condition) diff --git a/test/python/opflow/test_gradients.py b/test/python/opflow/test_gradients.py index b0fee1233bf5..fc82475313a0 100644 --- a/test/python/opflow/test_gradients.py +++ b/test/python/opflow/test_gradients.py @@ -21,12 +21,10 @@ from ddt import ddt, data, idata, unpack from qiskit import QuantumCircuit, QuantumRegister, BasicAer -from qiskit.test import slow_test from qiskit.utils import QuantumInstance from qiskit.exceptions import MissingOptionalLibraryError from qiskit.utils import algorithm_globals -from qiskit.algorithms import VQE -from qiskit.algorithms.optimizers import CG + from qiskit.opflow import ( I, X, @@ -1166,51 +1164,6 @@ def test_gradient_wrapper2(self, backend_type, atol): result = grad(value) self.assertTrue(np.allclose(result, correct_values[i], atol=atol)) - @slow_test - def test_vqe(self): - """Test VQE with gradients""" - - method = "lin_comb" - backend = "qasm_simulator" - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - BasicAer.get_backend(backend), seed_simulator=79, seed_transpiler=2 - ) - - # Define the Hamiltonian - h2_hamiltonian = ( - -1.05 * (I ^ I) + 0.39 * (I ^ Z) - 0.39 * (Z ^ I) - 0.01 * (Z ^ Z) + 0.18 * (X ^ X) - ) - h2_energy = -1.85727503 - - # Define the Ansatz - wavefunction = QuantumCircuit(2) - params = ParameterVector("theta", length=8) - itr = iter(params) - wavefunction.ry(next(itr), 0) - wavefunction.ry(next(itr), 1) - wavefunction.rz(next(itr), 0) - wavefunction.rz(next(itr), 1) - wavefunction.cx(0, 1) - wavefunction.ry(next(itr), 0) - wavefunction.ry(next(itr), 1) - wavefunction.rz(next(itr), 0) - wavefunction.rz(next(itr), 1) - - # Conjugate Gradient algorithm - optimizer = CG(maxiter=10) - - grad = Gradient(grad_method=method) - - # Gradient callable - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, optimizer=optimizer, gradient=grad, quantum_instance=q_instance - ) - result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) - np.testing.assert_almost_equal(result.optimal_value, h2_energy, decimal=0) - def test_qfi_overlap_works_with_bound_parameters(self): """Test all QFI methods work if the circuit contains a gate with bound parameters.""" diff --git a/test/python/passmanager/__init__.py b/test/python/passmanager/__init__.py new file mode 100644 index 000000000000..d5b924250dc4 --- /dev/null +++ b/test/python/passmanager/__init__.py @@ -0,0 +1,54 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023 +# +# 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. + +"""Pass manager test cases.""" + +import contextlib +import logging +import re +from itertools import zip_longest +from logging import getLogger + +from qiskit.test import QiskitTestCase + + +class PassManagerTestCase(QiskitTestCase): + """Test case for the pass manager module.""" + + @contextlib.contextmanager + def assertLogContains(self, expected_lines): + """A context manager that capture pass manager log. + + Args: + expected_lines (List[str]): Expected logs. Each element can be regular expression. + """ + try: + logger = getLogger() + with self.assertLogs(logger=logger, level=logging.DEBUG) as cm: + yield cm + finally: + recorded_lines = cm.output + for i, (expected, recorded) in enumerate(zip_longest(expected_lines, recorded_lines)): + expected = expected or "" + recorded = recorded or "" + if not re.search(expected, recorded): + raise AssertionError( + f"Log didn't match. Mismatch found at line #{i}.\n\n" + f"Expected:\n{self._format_log(expected_lines)}\n" + f"Recorded:\n{self._format_log(recorded_lines)}" + ) + + def _format_log(self, lines): + out = "" + for i, line in enumerate(lines): + out += f"#{i:02d}: {line}\n" + return out diff --git a/test/python/passmanager/test_generic_pass.py b/test/python/passmanager/test_generic_pass.py new file mode 100644 index 000000000000..c0071b966d65 --- /dev/null +++ b/test/python/passmanager/test_generic_pass.py @@ -0,0 +1,143 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023 +# +# 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. +# pylint: disable=missing-class-docstring + +"""Pass manager test cases.""" + +from test.python.passmanager import PassManagerTestCase + +from logging import getLogger + +from qiskit.passmanager import GenericPass +from qiskit.passmanager import PassManagerState, WorkflowStatus, PropertySet +from qiskit.passmanager.compilation_status import RunState + + +class TestGenericPass(PassManagerTestCase): + """Tests for the GenericPass subclass.""" + + def setUp(self): + super().setUp() + + self.state = PassManagerState( + workflow_status=WorkflowStatus(), + property_set=PropertySet(), + ) + + def test_run_task(self): + """Test case: Simple successful task execution.""" + + class Task(GenericPass): + def run(self, passmanager_ir): + return passmanager_ir + + task = Task() + data = "test_data" + expected = [r"Pass: Task - (\d*\.)?\d+ \(ms\)"] + + with self.assertLogContains(expected): + task.execute(passmanager_ir=data, state=self.state) + self.assertEqual(self.state.workflow_status.count, 1) + self.assertIn(task, self.state.workflow_status.completed_passes) + self.assertEqual(self.state.workflow_status.previous_run, RunState.SUCCESS) + + def test_failure_task(self): + """Test case: Log is created regardless of success.""" + + class TestError(Exception): + pass + + class RaiseError(GenericPass): + def run(self, passmanager_ir): + raise TestError() + + task = RaiseError() + data = "test_data" + expected = [r"Pass: RaiseError - (\d*\.)?\d+ \(ms\)"] + + with self.assertLogContains(expected): + with self.assertRaises(TestError): + task.execute(passmanager_ir=data, state=self.state) + self.assertEqual(self.state.workflow_status.count, 0) + self.assertNotIn(task, self.state.workflow_status.completed_passes) + self.assertEqual(self.state.workflow_status.previous_run, RunState.FAIL) + + def test_requires(self): + """Test case: Dependency tasks are run in advance to user provided task.""" + + class TaskA(GenericPass): + def run(self, passmanager_ir): + return passmanager_ir + + class TaskB(GenericPass): + def __init__(self): + super().__init__() + self.requires = [TaskA()] + + def run(self, passmanager_ir): + return passmanager_ir + + task = TaskB() + data = "test_data" + expected = [ + r"Pass: TaskA - (\d*\.)?\d+ \(ms\)", + r"Pass: TaskB - (\d*\.)?\d+ \(ms\)", + ] + with self.assertLogContains(expected): + task.execute(passmanager_ir=data, state=self.state) + self.assertEqual(self.state.workflow_status.count, 2) + + def test_requires_in_list(self): + """Test case: Dependency tasks are not executed multiple times.""" + + class TaskA(GenericPass): + def run(self, passmanager_ir): + return passmanager_ir + + class TaskB(GenericPass): + def __init__(self): + super().__init__() + self.requires = [TaskA()] + + def run(self, passmanager_ir): + return passmanager_ir + + task = TaskB() + data = "test_data" + expected = [ + r"Pass: TaskB - (\d*\.)?\d+ \(ms\)", + ] + self.state.workflow_status.completed_passes.add(task.requires[0]) # already done + with self.assertLogContains(expected): + task.execute(passmanager_ir=data, state=self.state) + self.assertEqual(self.state.workflow_status.count, 1) + + def test_run_with_callable(self): + """Test case: Callable is called after generic pass is run.""" + + # pylint: disable=unused-argument + def test_callable(task, passmanager_ir, property_set, running_time, count): + logger = getLogger() + logger.info("%s is running on %s", task.name(), passmanager_ir) + + class Task(GenericPass): + def run(self, passmanager_ir): + return passmanager_ir + + task = Task() + data = "test_data" + expected = [ + r"Pass: Task - (\d*\.)?\d+ \(ms\)", + r"Task is running on test_data", + ] + with self.assertLogContains(expected): + task.execute(passmanager_ir=data, state=self.state, callback=test_callable) diff --git a/test/python/passmanager/test_passmanager.py b/test/python/passmanager/test_passmanager.py new file mode 100644 index 000000000000..b7ffdbd6029b --- /dev/null +++ b/test/python/passmanager/test_passmanager.py @@ -0,0 +1,126 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023 +# +# 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. +# pylint: disable=missing-class-docstring + +"""Pass manager test cases.""" + +from test.python.passmanager import PassManagerTestCase + +from qiskit.passmanager import GenericPass, BasePassManager +from qiskit.passmanager.flow_controllers import DoWhileController, ConditionalController + + +class RemoveFive(GenericPass): + def run(self, passmanager_ir): + return passmanager_ir.replace("5", "") + + +class AddDigit(GenericPass): + def run(self, passmanager_ir): + return passmanager_ir + "0" + + +class CountDigits(GenericPass): + def run(self, passmanager_ir): + self.property_set["ndigits"] = len(passmanager_ir) + + +class ToyPassManager(BasePassManager): + def _passmanager_frontend(self, input_program, **kwargs): + return str(input_program) + + def _passmanager_backend(self, passmanager_ir, in_program, **kwargs): + return int(passmanager_ir) + + +class TestPassManager(PassManagerTestCase): + def test_single_task(self): + """Test case: Pass manager with a single task.""" + + task = RemoveFive() + data = 12345 + pm = ToyPassManager(task) + expected = [r"Pass: RemoveFive - (\d*\.)?\d+ \(ms\)"] + with self.assertLogContains(expected): + out = pm.run(data) + self.assertEqual(out, 1234) + + def test_property_set(self): + """Test case: Pass manager can access property set.""" + + task = CountDigits() + data = 12345 + pm = ToyPassManager(task) + pm.run(data) + self.assertDictEqual(pm.property_set, {"ndigits": 5}) + + def test_do_while_controller(self): + """Test case: Do while controller that repeats tasks until the condition is met.""" + + def _condition(property_set): + return property_set["ndigits"] < 7 + + controller = DoWhileController([AddDigit(), CountDigits()], do_while=_condition) + data = 12345 + pm = ToyPassManager(controller) + pm.property_set["ndigits"] = 5 + expected = [ + r"Pass: AddDigit - (\d*\.)?\d+ \(ms\)", + r"Pass: CountDigits - (\d*\.)?\d+ \(ms\)", + r"Pass: AddDigit - (\d*\.)?\d+ \(ms\)", + r"Pass: CountDigits - (\d*\.)?\d+ \(ms\)", + ] + with self.assertLogContains(expected): + out = pm.run(data) + self.assertEqual(out, 1234500) + + def test_conditional_controller(self): + """Test case: Conditional controller that run task when the condition is met.""" + + def _condition(property_set): + return property_set["ndigits"] > 6 + + controller = ConditionalController([RemoveFive()], condition=_condition) + data = [123456789, 45654, 36785554] + pm = ToyPassManager([CountDigits(), controller]) + out = pm.run(data) + self.assertListEqual(out, [12346789, 45654, 36784]) + + def test_string_input(self): + """Test case: Running tasks once for a single string input. + + Details: + When the pass manager receives a sequence of input values, + it duplicates itself and run the tasks on each input element in parallel. + If the input is string, this can be accidentally recognized as a sequence. + """ + + class StringPassManager(BasePassManager): + def _passmanager_frontend(self, input_program, **kwargs): + return input_program + + def _passmanager_backend(self, passmanager_ir, in_program, **kwargs): + return passmanager_ir + + class Task(GenericPass): + def run(self, passmanager_ir): + return passmanager_ir + + task = Task() + data = "12345" + pm = StringPassManager(task) + + # Should be run only one time + expected = [r"Pass: Task - (\d*\.)?\d+ \(ms\)"] + with self.assertLogContains(expected): + out = pm.run(data) + self.assertEqual(out, data) diff --git a/test/python/primitives/test_backend_sampler.py b/test/python/primitives/test_backend_sampler.py index 86dc709f8983..971104af9870 100644 --- a/test/python/primitives/test_backend_sampler.py +++ b/test/python/primitives/test_backend_sampler.py @@ -117,7 +117,6 @@ def test_sample_run_multiple_circuits(self, backend): bell = self._circuit[1] sampler = BackendSampler(backend=backend) result = sampler.run([bell, bell, bell]).result() - # print([q.binary_probabilities() for q in result.quasi_dists]) self._compare_probs(result.quasi_dists[0], self._target[1]) self._compare_probs(result.quasi_dists[1], self._target[1]) self._compare_probs(result.quasi_dists[2], self._target[1]) diff --git a/test/python/primitives/test_estimator.py b/test/python/primitives/test_estimator.py index 32c7bfe5b85c..eaa1c7d922fd 100644 --- a/test/python/primitives/test_estimator.py +++ b/test/python/primitives/test_estimator.py @@ -21,7 +21,8 @@ from qiskit.circuit.library import RealAmplitudes from qiskit.exceptions import QiskitError from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator, Estimator, EstimatorResult +from qiskit.primitives import Estimator, EstimatorResult +from qiskit.primitives.base import validation from qiskit.primitives.utils import _observable_key from qiskit.providers import JobV1 from qiskit.quantum_info import Operator, Pauli, PauliList, SparsePauliOp @@ -348,7 +349,6 @@ class TestObservableValidation(QiskitTestCase): @data( ("IXYZ", (SparsePauliOp("IXYZ"),)), (Pauli("IXYZ"), (SparsePauliOp("IXYZ"),)), - (PauliList("IXYZ"), (SparsePauliOp("IXYZ"),)), (SparsePauliOp("IXYZ"), (SparsePauliOp("IXYZ"),)), (PauliSumOp(SparsePauliOp("IXYZ")), (SparsePauliOp("IXYZ"),)), ( @@ -359,10 +359,6 @@ class TestObservableValidation(QiskitTestCase): [Pauli("IXYZ"), Pauli("ZYXI")], (SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")), ), - ( - [PauliList("IXYZ"), PauliList("ZYXI")], - (SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")), - ), ( [SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")], (SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")), @@ -375,19 +371,32 @@ class TestObservableValidation(QiskitTestCase): @unpack def test_validate_observables(self, obsevables, expected): """Test obsevables standardization.""" - self.assertEqual(BaseEstimator._validate_observables(obsevables), expected) + self.assertEqual(validation._validate_observables(obsevables), expected) + + @data( + (PauliList("IXYZ"), (SparsePauliOp("IXYZ"),)), + ( + [PauliList("IXYZ"), PauliList("ZYXI")], + (SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")), + ), + ) + @unpack + def test_validate_observables_deprecated(self, obsevables, expected): + """Test obsevables standardization.""" + with self.assertRaises(DeprecationWarning): + self.assertEqual(validation._validate_observables(obsevables), expected) @data(None, "ERROR") def test_qiskit_error(self, observables): """Test qiskit error if invalid input.""" with self.assertRaises(QiskitError): - BaseEstimator._validate_observables(observables) + validation._validate_observables(observables) @data((), []) def test_value_error(self, observables): """Test value error if no obsevables are provided.""" with self.assertRaises(ValueError): - BaseEstimator._validate_observables(observables) + validation._validate_observables(observables) if __name__ == "__main__": diff --git a/test/python/primitives/test_primitive.py b/test/python/primitives/test_primitive.py index cc60a17abc7e..5ae93df82a51 100644 --- a/test/python/primitives/test_primitive.py +++ b/test/python/primitives/test_primitive.py @@ -19,7 +19,7 @@ from qiskit import QuantumCircuit, pulse, transpile from qiskit.circuit.random import random_circuit -from qiskit.primitives.base.base_primitive import BasePrimitive +from qiskit.primitives.base import validation from qiskit.primitives.utils import _circuit_key from qiskit.providers.fake_provider import FakeAlmaden from qiskit.test import QiskitTestCase @@ -39,19 +39,19 @@ class TestCircuitValidation(QiskitTestCase): @unpack def test_validate_circuits(self, circuits, expected): """Test circuits standardization.""" - self.assertEqual(BasePrimitive._validate_circuits(circuits), expected) + self.assertEqual(validation._validate_circuits(circuits), expected) @data(None, "ERROR", True, 0, 1.0, 1j, [0.0]) def test_type_error(self, circuits): """Test type error if invalid input.""" with self.assertRaises(TypeError): - BasePrimitive._validate_circuits(circuits) + validation._validate_circuits(circuits) @data((), [], "") def test_value_error(self, circuits): """Test value error if no circuits are provided.""" with self.assertRaises(ValueError): - BasePrimitive._validate_circuits(circuits) + validation._validate_circuits(circuits) @ddt @@ -87,9 +87,9 @@ class TestParameterValuesValidation(QiskitTestCase): def test_validate_parameter_values(self, _parameter_values, expected): """Test parameter_values standardization.""" for parameter_values in [_parameter_values, array(_parameter_values)]: # Numpy - self.assertEqual(BasePrimitive._validate_parameter_values(parameter_values), expected) + self.assertEqual(validation._validate_parameter_values(parameter_values), expected) self.assertEqual( - BasePrimitive._validate_parameter_values(None, default=parameter_values), expected + validation._validate_parameter_values(None, default=parameter_values), expected ) @data( @@ -108,12 +108,12 @@ def test_validate_parameter_values(self, _parameter_values, expected): def test_type_error(self, parameter_values): """Test type error if invalid input.""" with self.assertRaises(TypeError): - BasePrimitive._validate_parameter_values(parameter_values) + validation._validate_parameter_values(parameter_values) def test_value_error(self): """Test value error if no parameter_values or default are provided.""" with self.assertRaises(ValueError): - BasePrimitive._validate_parameter_values(None) + validation._validate_parameter_values(None) class TestCircuitKey(QiskitTestCase): diff --git a/test/python/primitives/test_sampler.py b/test/python/primitives/test_sampler.py index 2dc2aac11098..fac9a250a9e9 100644 --- a/test/python/primitives/test_sampler.py +++ b/test/python/primitives/test_sampler.py @@ -93,7 +93,6 @@ def test_sampler_run(self): self.assertIsInstance(job, JobV1) result = job.result() self.assertIsInstance(result, SamplerResult) - # print([q.binary_probabilities() for q in result.quasi_dists]) self._compare_probs(result.quasi_dists, self._target[1]) def test_sample_run_multiple_circuits(self): @@ -103,7 +102,6 @@ def test_sample_run_multiple_circuits(self): bell = self._circuit[1] sampler = Sampler() result = sampler.run([bell, bell, bell]).result() - # print([q.binary_probabilities() for q in result.quasi_dists]) self._compare_probs(result.quasi_dists[0], self._target[1]) self._compare_probs(result.quasi_dists[1], self._target[1]) self._compare_probs(result.quasi_dists[2], self._target[1]) diff --git a/test/python/pulse/test_discrete_pulses.py b/test/python/pulse/test_discrete_pulses.py index 53157d54c5f8..59cb2583f115 100644 --- a/test/python/pulse/test_discrete_pulses.py +++ b/test/python/pulse/test_discrete_pulses.py @@ -28,7 +28,8 @@ def test_constant(self): duration = 10 times = np.arange(0, duration) + 0.5 # to match default midpoint sampling strategy constant_ref = continuous.constant(times, amp=amp) - constant_pulse = library.constant(duration, amp=amp) + with self.assertWarns(DeprecationWarning): + constant_pulse = library.constant(duration, amp=amp) self.assertIsInstance(constant_pulse, Waveform) np.testing.assert_array_almost_equal(constant_pulse.samples, constant_ref) @@ -37,7 +38,8 @@ def test_zero(self): duration = 10 times = np.arange(0, duration) + 0.5 zero_ref = continuous.zero(times) - zero_pulse = library.zero(duration) + with self.assertWarns(DeprecationWarning): + zero_pulse = library.zero(duration) self.assertIsInstance(zero_pulse, Waveform) np.testing.assert_array_almost_equal(zero_pulse.samples, zero_ref) @@ -48,14 +50,16 @@ def test_square(self): duration = 10 times = np.arange(0, duration) + 0.5 square_ref = continuous.square(times, amp=amp, freq=freq) - square_pulse = library.square(duration, amp=amp, freq=freq) + with self.assertWarns(DeprecationWarning): + square_pulse = library.square(duration, amp=amp, freq=freq) self.assertIsInstance(square_pulse, Waveform) np.testing.assert_array_almost_equal(square_pulse.samples, square_ref) # test single cycle cycle_freq = 1.0 / duration square_cycle_ref = continuous.square(times, amp=amp, freq=cycle_freq) - square_cycle_pulse = library.square(duration, amp=amp) + with self.assertWarns(DeprecationWarning): + square_cycle_pulse = library.square(duration, amp=amp) np.testing.assert_array_almost_equal(square_cycle_pulse.samples, square_cycle_ref) def test_sawtooth(self): @@ -65,14 +69,16 @@ def test_sawtooth(self): duration = 10 times = np.arange(0, duration) + 0.5 sawtooth_ref = continuous.sawtooth(times, amp=amp, freq=freq) - sawtooth_pulse = library.sawtooth(duration, amp=amp, freq=freq) + with self.assertWarns(DeprecationWarning): + sawtooth_pulse = library.sawtooth(duration, amp=amp, freq=freq) self.assertIsInstance(sawtooth_pulse, Waveform) np.testing.assert_array_equal(sawtooth_pulse.samples, sawtooth_ref) # test single cycle cycle_freq = 1.0 / duration sawtooth_cycle_ref = continuous.sawtooth(times, amp=amp, freq=cycle_freq) - sawtooth_cycle_pulse = library.sawtooth(duration, amp=amp) + with self.assertWarns(DeprecationWarning): + sawtooth_cycle_pulse = library.sawtooth(duration, amp=amp) np.testing.assert_array_almost_equal(sawtooth_cycle_pulse.samples, sawtooth_cycle_ref) def test_triangle(self): @@ -82,14 +88,16 @@ def test_triangle(self): duration = 10 times = np.arange(0, duration) + 0.5 triangle_ref = continuous.triangle(times, amp=amp, freq=freq) - triangle_pulse = library.triangle(duration, amp=amp, freq=freq) + with self.assertWarns(DeprecationWarning): + triangle_pulse = library.triangle(duration, amp=amp, freq=freq) self.assertIsInstance(triangle_pulse, Waveform) np.testing.assert_array_almost_equal(triangle_pulse.samples, triangle_ref) # test single cycle cycle_freq = 1.0 / duration triangle_cycle_ref = continuous.triangle(times, amp=amp, freq=cycle_freq) - triangle_cycle_pulse = library.triangle(duration, amp=amp) + with self.assertWarns(DeprecationWarning): + triangle_cycle_pulse = library.triangle(duration, amp=amp) np.testing.assert_array_equal(triangle_cycle_pulse.samples, triangle_cycle_ref) def test_cos(self): @@ -100,14 +108,16 @@ def test_cos(self): duration = 10 times = np.arange(0, duration) + 0.5 cos_ref = continuous.cos(times, amp=amp, freq=freq) - cos_pulse = library.cos(duration, amp=amp, freq=freq) + with self.assertWarns(DeprecationWarning): + cos_pulse = library.cos(duration, amp=amp, freq=freq) self.assertIsInstance(cos_pulse, Waveform) np.testing.assert_array_almost_equal(cos_pulse.samples, cos_ref) # test single cycle cycle_freq = 1 / duration cos_cycle_ref = continuous.cos(times, amp=amp, freq=cycle_freq) - cos_cycle_pulse = library.cos(duration, amp=amp) + with self.assertWarns(DeprecationWarning): + cos_cycle_pulse = library.cos(duration, amp=amp) np.testing.assert_array_almost_equal(cos_cycle_pulse.samples, cos_cycle_ref) def test_sin(self): @@ -118,14 +128,16 @@ def test_sin(self): duration = 10 times = np.arange(0, duration) + 0.5 sin_ref = continuous.sin(times, amp=amp, freq=freq) - sin_pulse = library.sin(duration, amp=amp, freq=freq) + with self.assertWarns(DeprecationWarning): + sin_pulse = library.sin(duration, amp=amp, freq=freq) self.assertIsInstance(sin_pulse, Waveform) np.testing.assert_array_equal(sin_pulse.samples, sin_ref) # test single cycle cycle_freq = 1 / duration sin_cycle_ref = continuous.sin(times, amp=amp, freq=cycle_freq) - sin_cycle_pulse = library.sin(duration, amp=amp) + with self.assertWarns(DeprecationWarning): + sin_cycle_pulse = library.sin(duration, amp=amp) np.testing.assert_array_almost_equal(sin_cycle_pulse.samples, sin_cycle_ref) def test_gaussian(self): @@ -138,7 +150,8 @@ def test_gaussian(self): gaussian_ref = continuous.gaussian( times, amp, center, sigma, zeroed_width=2 * (center + 1), rescale_amp=True ) - gaussian_pulse = library.gaussian(duration, amp, sigma) + with self.assertWarns(DeprecationWarning): + gaussian_pulse = library.gaussian(duration, amp, sigma) self.assertIsInstance(gaussian_pulse, Waveform) np.testing.assert_array_almost_equal(gaussian_pulse.samples, gaussian_ref) @@ -150,7 +163,8 @@ def test_gaussian_deriv(self): center = duration / 2 times = np.arange(0, duration) + 0.5 gaussian_deriv_ref = continuous.gaussian_deriv(times, amp, center, sigma) - gaussian_deriv_pulse = library.gaussian_deriv(duration, amp, sigma) + with self.assertWarns(DeprecationWarning): + gaussian_deriv_pulse = library.gaussian_deriv(duration, amp, sigma) self.assertIsInstance(gaussian_deriv_pulse, Waveform) np.testing.assert_array_almost_equal(gaussian_deriv_pulse.samples, gaussian_deriv_ref) @@ -164,7 +178,8 @@ def test_sech(self): sech_ref = continuous.sech( times, amp, center, sigma, zeroed_width=2 * (center + 1), rescale_amp=True ) - sech_pulse = library.sech(duration, amp, sigma) + with self.assertWarns(DeprecationWarning): + sech_pulse = library.sech(duration, amp, sigma) self.assertIsInstance(sech_pulse, Waveform) np.testing.assert_array_almost_equal(sech_pulse.samples, sech_ref) @@ -176,7 +191,8 @@ def test_sech_deriv(self): center = duration / 2 times = np.arange(0, duration) + 0.5 sech_deriv_ref = continuous.sech_deriv(times, amp, center, sigma) - sech_deriv_pulse = library.sech_deriv(duration, amp, sigma) + with self.assertWarns(DeprecationWarning): + sech_deriv_pulse = library.sech_deriv(duration, amp, sigma) self.assertIsInstance(sech_deriv_pulse, Waveform) np.testing.assert_array_almost_equal(sech_deriv_pulse.samples, sech_deriv_ref) @@ -191,7 +207,8 @@ def test_gaussian_square(self): center = duration / 2 times = np.arange(0, duration) + 0.5 gaussian_square_ref = continuous.gaussian_square(times, amp, center, width, sigma) - gaussian_square_pulse = library.gaussian_square(duration, amp, sigma, risefall) + with self.assertWarns(DeprecationWarning): + gaussian_square_pulse = library.gaussian_square(duration, amp, sigma, risefall) self.assertIsInstance(gaussian_square_pulse, Waveform) np.testing.assert_array_almost_equal(gaussian_square_pulse.samples, gaussian_square_ref) @@ -201,13 +218,17 @@ def test_gaussian_square_args(self): sigma = 0.1 duration = 10 # risefall and width consistent: no error - library.gaussian_square(duration, amp, sigma, 2, width=6) + with self.assertWarns(DeprecationWarning): + library.gaussian_square(duration, amp, sigma, 2, width=6) # supply width instead: no error - library.gaussian_square(duration, amp, sigma, width=6) + with self.assertWarns(DeprecationWarning): + library.gaussian_square(duration, amp, sigma, width=6) with self.assertRaises(PulseError): - library.gaussian_square(duration, amp, sigma, width=2, risefall=2) + with self.assertWarns(DeprecationWarning): + library.gaussian_square(duration, amp, sigma, width=2, risefall=2) with self.assertRaises(PulseError): - library.gaussian_square(duration, amp, sigma) + with self.assertWarns(DeprecationWarning): + library.gaussian_square(duration, amp, sigma) def test_drag(self): """Test discrete sampled drag pulse.""" @@ -221,35 +242,7 @@ def test_drag(self): drag_ref = continuous.drag( times, amp, center, sigma, beta=beta, zeroed_width=2 * (center + 1), rescale_amp=True ) - drag_pulse = library.drag(duration, amp, sigma, beta=beta) + with self.assertWarns(DeprecationWarning): + drag_pulse = library.drag(duration, amp, sigma, beta=beta) self.assertIsInstance(drag_pulse, Waveform) np.testing.assert_array_almost_equal(drag_pulse.samples, drag_ref) - - def test_pending_deprecation_warnings(self): - """Test that pending deprecation warnings are raised when the discrete library is used.""" - with self.assertWarns(PendingDeprecationWarning): - library.drag(duration=10, amp=0.5, sigma=0.1, beta=0.1) - with self.assertWarns(PendingDeprecationWarning): - library.gaussian_square(duration=10, amp=0.5, sigma=0.1, risefall=2, width=6) - with self.assertWarns(PendingDeprecationWarning): - library.gaussian(duration=10, amp=0.5, sigma=0.1) - with self.assertWarns(PendingDeprecationWarning): - library.sin(duration=10, amp=0.5) - with self.assertWarns(PendingDeprecationWarning): - library.cos(duration=10, amp=0.5) - with self.assertWarns(PendingDeprecationWarning): - library.sawtooth(duration=10, amp=0.5) - with self.assertWarns(PendingDeprecationWarning): - library.zero(duration=10) - with self.assertWarns(PendingDeprecationWarning): - library.constant(duration=10, amp=0.5) - with self.assertWarns(PendingDeprecationWarning): - library.triangle(duration=10, amp=0.5) - with self.assertWarns(PendingDeprecationWarning): - library.gaussian_deriv(duration=10, amp=0.5, sigma=3) - with self.assertWarns(PendingDeprecationWarning): - library.sech_deriv(duration=10, amp=0.5, sigma=3) - with self.assertWarns(PendingDeprecationWarning): - library.sech(duration=10, amp=0.5, sigma=3) - with self.assertWarns(PendingDeprecationWarning): - library.square(duration=10, amp=0.5) diff --git a/test/python/pulse/test_frames.py b/test/python/pulse/test_frames.py new file mode 100644 index 000000000000..1a9b99e1139f --- /dev/null +++ b/test/python/pulse/test_frames.py @@ -0,0 +1,77 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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. + +"""Test pulse logical elements and frames""" + +from qiskit.pulse import ( + PulseError, + GenericFrame, + QubitFrame, + MeasurementFrame, +) +from qiskit.test import QiskitTestCase + + +class TestFrames(QiskitTestCase): + """Test frames.""" + + def test_generic_frame_initialization(self): + """Test that Frame objects are created correctly""" + frame = GenericFrame(name="frame1") + self.assertEqual(frame.name, "frame1") + self.assertEqual(str(frame), "GenericFrame(frame1)") + + def test_generic_frame_comparison(self): + """Test that GenericFrame objects are compared correctly""" + frame1 = GenericFrame(name="frame1") + + self.assertEqual(frame1, GenericFrame(name="frame1")) + self.assertNotEqual(frame1, GenericFrame(name="frame2")) + self.assertNotEqual(frame1, QubitFrame(3)) + + def test_qubit_frame_initialization(self): + """Test that QubitFrame type frames are created and validated correctly""" + frame = QubitFrame(2) + self.assertEqual(frame.index, 2) + self.assertEqual(str(frame), "QubitFrame(2)") + + with self.assertRaises(PulseError): + QubitFrame(0.5) + with self.assertRaises(PulseError): + QubitFrame(-0.5) + with self.assertRaises(PulseError): + QubitFrame(-1) + + def test_qubit_frame_comparison(self): + """Test the comparison of QubitFrame""" + self.assertEqual(QubitFrame(0), QubitFrame(0)) + self.assertNotEqual(QubitFrame(0), QubitFrame(1)) + self.assertNotEqual(MeasurementFrame(0), QubitFrame(0)) + + def test_measurement_frame_initialization(self): + """Test that MeasurementFrame type frames are created and validated correctly""" + frame = MeasurementFrame(2) + self.assertEqual(frame.index, 2) + self.assertEqual(str(frame), "MeasurementFrame(2)") + + with self.assertRaises(PulseError): + MeasurementFrame(0.5) + with self.assertRaises(PulseError): + MeasurementFrame(-0.5) + with self.assertRaises(PulseError): + MeasurementFrame(-1) + + def test_measurement_frame_comparison(self): + """Test the comparison of measurement frames""" + self.assertEqual(MeasurementFrame(0), MeasurementFrame(0)) + self.assertNotEqual(MeasurementFrame(0), MeasurementFrame(1)) + self.assertNotEqual(MeasurementFrame(0), QubitFrame(0)) diff --git a/test/python/pulse/test_instruction_schedule_map.py b/test/python/pulse/test_instruction_schedule_map.py index 414a1d6566a5..72058ba08303 100644 --- a/test/python/pulse/test_instruction_schedule_map.py +++ b/test/python/pulse/test_instruction_schedule_map.py @@ -350,11 +350,15 @@ def test_schedule_generator(self): def test_func(dur: int): sched = Schedule() - sched += Play(library.constant(int(dur), amp), DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + waveform = library.constant(int(dur), amp) + sched += Play(waveform, DriveChannel(0)) return sched expected_sched = Schedule() - expected_sched += Play(library.constant(dur_val, amp), DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + cons_waveform = library.constant(dur_val, amp) + expected_sched += Play(cons_waveform, DriveChannel(0)) inst_map = InstructionScheduleMap() inst_map.add("f", (0,), test_func) @@ -371,11 +375,15 @@ def test_schedule_generator_supports_parameter_expressions(self): def test_func(dur: ParameterExpression, t_val: int): dur_bound = dur.bind({t_param: t_val}) sched = Schedule() - sched += Play(library.constant(int(float(dur_bound)), amp), DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + waveform = library.constant(int(float(dur_bound)), amp) + sched += Play(waveform, DriveChannel(0)) return sched expected_sched = Schedule() - expected_sched += Play(library.constant(10, amp), DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + cons_waveform = library.constant(10, amp) + expected_sched += Play(cons_waveform, DriveChannel(0)) inst_map = InstructionScheduleMap() inst_map.add("f", (0,), test_func) diff --git a/test/python/pulse/test_mixed_frames.py b/test/python/pulse/test_mixed_frames.py new file mode 100644 index 000000000000..95ebd556d1c0 --- /dev/null +++ b/test/python/pulse/test_mixed_frames.py @@ -0,0 +1,65 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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. + +"""Test pulse logical elements and frames""" + +from qiskit.pulse import ( + Port, + Qubit, + GenericFrame, + MixedFrame, +) +from qiskit.test import QiskitTestCase + + +class TestMixedFrames(QiskitTestCase): + """Test mixed frames.""" + + def test_mixed_frame_initialization(self): + """Test that MixedFrame objects are created correctly""" + frame = GenericFrame("frame1") + qubit = Qubit(1) + mixed_frame = MixedFrame(qubit, frame) + self.assertEqual(mixed_frame.pulse_target, qubit) + self.assertEqual(mixed_frame.frame, frame) + + port = Port("d0") + mixed_frame = MixedFrame(port, frame) + self.assertEqual(mixed_frame.pulse_target, port) + + def test_mixed_frames_comparison(self): + """Test the comparison of various mixed frames""" + self.assertEqual( + MixedFrame(Qubit(1), GenericFrame("a")), + MixedFrame(Qubit(1), GenericFrame("a")), + ) + + self.assertEqual( + MixedFrame(Port("s"), GenericFrame("a")), + MixedFrame(Port("s"), GenericFrame("a")), + ) + + self.assertNotEqual( + MixedFrame(Qubit(1), GenericFrame("a")), + MixedFrame(Qubit(2), GenericFrame("a")), + ) + self.assertNotEqual( + MixedFrame(Qubit(1), GenericFrame("a")), + MixedFrame(Qubit(1), GenericFrame("b")), + ) + + def test_mixed_frame_repr(self): + """Test MixedFrame __repr__""" + frame = GenericFrame("frame1") + qubit = Qubit(1) + mixed_frame = MixedFrame(qubit, frame) + self.assertEqual(str(mixed_frame), f"MixedFrame({qubit},{frame})") diff --git a/test/python/pulse/test_pulse_lib.py b/test/python/pulse/test_pulse_lib.py index 575360f81758..97c9cf332d2c 100644 --- a/test/python/pulse/test_pulse_lib.py +++ b/test/python/pulse/test_pulse_lib.py @@ -189,7 +189,8 @@ def test_gaussian_pulse(self): gauss = Gaussian(duration=25, sigma=4, amp=0.5, angle=np.pi / 2) sample_pulse = gauss.get_waveform() self.assertIsInstance(sample_pulse, Waveform) - pulse_lib_gauss = gaussian(duration=25, sigma=4, amp=0.5j, zero_ends=True).samples + with self.assertWarns(DeprecationWarning): + pulse_lib_gauss = gaussian(duration=25, sigma=4, amp=0.5j, zero_ends=True).samples np.testing.assert_almost_equal(sample_pulse.samples, pulse_lib_gauss) def test_gaussian_square_pulse(self): @@ -197,18 +198,20 @@ def test_gaussian_square_pulse(self): gauss_sq = GaussianSquare(duration=125, sigma=4, amp=0.5, width=100, angle=np.pi / 2) sample_pulse = gauss_sq.get_waveform() self.assertIsInstance(sample_pulse, Waveform) - pulse_lib_gauss_sq = gaussian_square( - duration=125, sigma=4, amp=0.5j, width=100, zero_ends=True - ).samples + with self.assertWarns(DeprecationWarning): + pulse_lib_gauss_sq = gaussian_square( + duration=125, sigma=4, amp=0.5j, width=100, zero_ends=True + ).samples np.testing.assert_almost_equal(sample_pulse.samples, pulse_lib_gauss_sq) gauss_sq = GaussianSquare( duration=125, sigma=4, amp=0.5, risefall_sigma_ratio=3.125, angle=np.pi / 2 ) sample_pulse = gauss_sq.get_waveform() self.assertIsInstance(sample_pulse, Waveform) - pulse_lib_gauss_sq = gaussian_square( - duration=125, sigma=4, amp=0.5j, width=100, zero_ends=True - ).samples + with self.assertWarns(DeprecationWarning): + pulse_lib_gauss_sq = gaussian_square( + duration=125, sigma=4, amp=0.5j, width=100, zero_ends=True + ).samples np.testing.assert_almost_equal(sample_pulse.samples, pulse_lib_gauss_sq) def test_gauss_square_extremes(self): @@ -374,7 +377,8 @@ def test_drag_pulse(self): drag = Drag(duration=25, sigma=4, amp=0.5, beta=1, angle=np.pi / 2) sample_pulse = drag.get_waveform() self.assertIsInstance(sample_pulse, Waveform) - pulse_lib_drag = pl_drag(duration=25, sigma=4, amp=0.5j, beta=1, zero_ends=True).samples + with self.assertWarns(DeprecationWarning): + pulse_lib_drag = pl_drag(duration=25, sigma=4, amp=0.5j, beta=1, zero_ends=True).samples np.testing.assert_almost_equal(sample_pulse.samples, pulse_lib_drag) def test_drag_validation(self): @@ -426,7 +430,8 @@ def test_sin_pulse(self): phase = 0 sin_pulse = Sin(duration=duration, amp=amp, freq=freq, phase=phase) - sin_waveform = sin(duration=duration, amp=amp, freq=freq, phase=phase) + with self.assertWarns(DeprecationWarning): + sin_waveform = sin(duration=duration, amp=amp, freq=freq, phase=phase) np.testing.assert_almost_equal(sin_pulse.get_waveform().samples, sin_waveform.samples) @@ -440,7 +445,8 @@ def test_cos_pulse(self): freq = 0.1 phase = 0 cos_pulse = Cos(duration=duration, amp=amp, freq=freq, phase=phase) - cos_waveform = cos(duration=duration, amp=amp, freq=freq, phase=phase) + with self.assertWarns(DeprecationWarning): + cos_waveform = cos(duration=duration, amp=amp, freq=freq, phase=phase) np.testing.assert_almost_equal(cos_pulse.get_waveform().samples, cos_waveform.samples) shifted_sin_pulse = Sin(duration=duration, amp=amp, freq=freq, phase=phase + np.pi / 2) @@ -457,7 +463,8 @@ def test_square_pulse(self): freq = 0.1 phase = 0.3 square_pulse = Square(duration=duration, amp=amp, freq=freq, phase=phase) - square_waveform = square(duration=duration, amp=amp, freq=freq, phase=phase / 2) + with self.assertWarns(DeprecationWarning): + square_waveform = square(duration=duration, amp=amp, freq=freq, phase=phase / 2) np.testing.assert_almost_equal(square_pulse.get_waveform().samples, square_waveform.samples) @@ -471,7 +478,8 @@ def test_sawtooth_pulse(self): freq = 0.1 phase = 0.5 sawtooth_pulse = Sawtooth(duration=duration, amp=amp, freq=freq, phase=phase) - sawtooth_waveform = sawtooth(duration=duration, amp=amp, freq=freq, phase=phase / 2) + with self.assertWarns(DeprecationWarning): + sawtooth_waveform = sawtooth(duration=duration, amp=amp, freq=freq, phase=phase / 2) # Note that the phase definition in `Sawtooth` was changed compared to `sawtooth` np.testing.assert_almost_equal( sawtooth_pulse.get_waveform().samples, sawtooth_waveform.samples @@ -491,7 +499,8 @@ def test_triangle_pulse(self): freq = 0.1 phase = 0.5 triangle_pulse = Triangle(duration=duration, amp=amp, freq=freq, phase=phase) - triangle_waveform = triangle(duration=duration, amp=amp, freq=freq, phase=phase) + with self.assertWarns(DeprecationWarning): + triangle_waveform = triangle(duration=duration, amp=amp, freq=freq, phase=phase) np.testing.assert_almost_equal( triangle_pulse.get_waveform().samples, triangle_waveform.samples ) @@ -509,7 +518,8 @@ def test_gaussian_deriv_pulse(self): amp = 0.5 sigma = 100 gaussian_deriv_pulse = GaussianDeriv(duration=duration, amp=amp, sigma=sigma) - gaussian_deriv_waveform = gaussian_deriv(duration=duration, amp=amp, sigma=sigma) + with self.assertWarns(DeprecationWarning): + gaussian_deriv_waveform = gaussian_deriv(duration=duration, amp=amp, sigma=sigma) np.testing.assert_almost_equal( gaussian_deriv_pulse.get_waveform().samples, gaussian_deriv_waveform.samples ) @@ -523,12 +533,14 @@ def test_sech_pulse(self): sigma = 10 # Zero ends = True sech_pulse = Sech(duration=duration, amp=amp, sigma=sigma) - sech_waveform = sech(duration=duration, amp=amp, sigma=sigma) + with self.assertWarns(DeprecationWarning): + sech_waveform = sech(duration=duration, amp=amp, sigma=sigma) np.testing.assert_almost_equal(sech_pulse.get_waveform().samples, sech_waveform.samples) # Zero ends = False sech_pulse = Sech(duration=duration, amp=amp, sigma=sigma, zero_ends=False) - sech_waveform = sech(duration=duration, amp=amp, sigma=sigma, zero_ends=False) + with self.assertWarns(DeprecationWarning): + sech_waveform = sech(duration=duration, amp=amp, sigma=sigma, zero_ends=False) np.testing.assert_almost_equal(sech_pulse.get_waveform().samples, sech_waveform.samples) with self.assertRaises(PulseError): @@ -540,7 +552,8 @@ def test_sech_deriv_pulse(self): amp = 0.5 sigma = 10 sech_deriv_pulse = SechDeriv(duration=duration, amp=amp, sigma=sigma) - sech_deriv_waveform = sech_deriv(duration=duration, amp=amp, sigma=sigma) + with self.assertWarns(DeprecationWarning): + sech_deriv_waveform = sech_deriv(duration=duration, amp=amp, sigma=sigma) np.testing.assert_almost_equal( sech_deriv_pulse.get_waveform().samples, sech_deriv_waveform.samples ) @@ -672,17 +685,43 @@ def test_param_validation(self): with self.assertRaises(PulseError): Drag(duration=25, amp=0.5, sigma=-7.8, beta=4, angle=np.pi / 3) - def test_gaussian_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" + def test_class_level_limit_amplitude(self): + """Test that the check for amplitude less than or equal to 1 can + be disabled on the class level. + + Tests for representative examples. + """ with self.assertRaises(PulseError): Gaussian(duration=100, sigma=1.0, amp=1.7, angle=np.pi * 1.1) with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): waveform = Gaussian(duration=100, sigma=1.0, amp=1.7, angle=np.pi * 1.1) self.assertGreater(np.abs(waveform.amp), 1.0) + waveform = GaussianSquare(duration=100, sigma=1.0, amp=1.5, width=10, angle=np.pi / 5) + self.assertGreater(np.abs(waveform.amp), 1.0) + waveform = GaussianSquareDrag(duration=100, sigma=1.0, amp=1.1, beta=0.1, width=10) + self.assertGreater(np.abs(waveform.amp), 1.0) + + def test_class_level_disable_validation(self): + """Test that pulse validation can be disabled on the class level. + + Tests for representative examples. + """ + with self.assertRaises(PulseError): + Gaussian(duration=100, sigma=-1.0, amp=0.5, angle=np.pi * 1.1) + + with patch( + "qiskit.pulse.library.symbolic_pulses.SymbolicPulse.disable_validation", new=True + ): + waveform = Gaussian(duration=100, sigma=-1.0, amp=0.5, angle=np.pi * 1.1) + self.assertLess(waveform.sigma, 0) + waveform = GaussianSquare(duration=100, sigma=1.0, amp=0.5, width=1000, angle=np.pi / 5) + self.assertGreater(waveform.width, waveform.duration) + waveform = GaussianSquareDrag(duration=100, sigma=1.0, amp=1.1, beta=0.1, width=-1) + self.assertLess(waveform.width, 0) def test_gaussian_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per Gaussian instance.""" with self.assertRaises(PulseError): Gaussian(duration=100, sigma=1.0, amp=1.6, angle=np.pi / 2.5) @@ -691,17 +730,8 @@ def test_gaussian_limit_amplitude_per_instance(self): ) self.assertGreater(np.abs(waveform.amp), 1.0) - def test_gaussian_square_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - GaussianSquare(duration=100, sigma=1.0, amp=1.5, width=10, angle=np.pi / 5) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = GaussianSquare(duration=100, sigma=1.0, amp=1.5, width=10, angle=np.pi / 5) - self.assertGreater(np.abs(waveform.amp), 1.0) - def test_gaussian_square_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per GaussianSquare instance.""" with self.assertRaises(PulseError): GaussianSquare(duration=100, sigma=1.0, amp=1.5, width=10, angle=np.pi / 3) @@ -710,17 +740,8 @@ def test_gaussian_square_limit_amplitude_per_instance(self): ) self.assertGreater(np.abs(waveform.amp), 1.0) - def test_gaussian_square_drag_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - GaussianSquareDrag(duration=100, sigma=1.0, amp=1.1, beta=0.1, width=10) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = GaussianSquareDrag(duration=100, sigma=1.0, amp=1.1, beta=0.1, width=10) - self.assertGreater(np.abs(waveform.amp), 1.0) - def test_gaussian_square_drag_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per GaussianSquareDrag instance.""" with self.assertRaises(PulseError): GaussianSquareDrag(duration=100, sigma=1.0, amp=1.1, beta=0.1, width=10) @@ -729,17 +750,8 @@ def test_gaussian_square_drag_limit_amplitude_per_instance(self): ) self.assertGreater(np.abs(waveform.amp), 1.0) - def test_gaussian_square_echo_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - gaussian_square_echo(duration=1000, sigma=4.0, amp=1.01, width=100) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = gaussian_square_echo(duration=100, sigma=1.0, amp=1.1, width=10) - self.assertGreater(np.abs(waveform.amp), 1.0) - def test_gaussian_square_echo_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per GaussianSquareEcho instance.""" with self.assertRaises(PulseError): gaussian_square_echo(duration=1000, sigma=4.0, amp=1.01, width=100) @@ -748,17 +760,8 @@ def test_gaussian_square_echo_limit_amplitude_per_instance(self): ) self.assertGreater(np.abs(waveform.amp), 1.0) - def test_drag_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - Drag(duration=100, sigma=1.0, beta=1.0, amp=1.8, angle=np.pi * 0.3) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = Drag(duration=100, sigma=1.0, beta=1.0, amp=1.8, angle=np.pi * 0.3) - self.assertGreater(np.abs(waveform.amp), 1.0) - def test_drag_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per DRAG instance.""" with self.assertRaises(PulseError): Drag(duration=100, sigma=1.0, beta=1.0, amp=1.8, angle=np.pi * 0.3) @@ -767,136 +770,64 @@ def test_drag_limit_amplitude_per_instance(self): ) self.assertGreater(np.abs(waveform.amp), 1.0) - def test_constant_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - Constant(duration=100, amp=1.3, angle=0.1) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = Constant(duration=100, amp=1.3, angle=0.1) - self.assertGreater(np.abs(waveform.amp), 1.0) - def test_constant_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per Constant instance.""" with self.assertRaises(PulseError): Constant(duration=100, amp=1.6, angle=0.5) waveform = Constant(duration=100, amp=1.6, angle=0.5, limit_amplitude=False) self.assertGreater(np.abs(waveform.amp), 1.0) - def test_sin_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - Sin(duration=100, amp=1.1, phase=0) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = Sin(duration=100, amp=1.1, phase=0) - self.assertGreater(np.abs(waveform.amp), 1.0) - def test_sin_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per Sin instance.""" with self.assertRaises(PulseError): Sin(duration=100, amp=1.1, phase=0) waveform = Sin(duration=100, amp=1.1, phase=0, limit_amplitude=False) self.assertGreater(np.abs(waveform.amp), 1.0) - def test_sawtooth_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - Sawtooth(duration=100, amp=1.1, phase=0) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = Sawtooth(duration=100, amp=1.1, phase=0) - self.assertGreater(np.abs(waveform.amp), 1.0) - def test_sawtooth_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per Sawtooth instance.""" with self.assertRaises(PulseError): Sawtooth(duration=100, amp=1.1, phase=0) waveform = Sawtooth(duration=100, amp=1.1, phase=0, limit_amplitude=False) self.assertGreater(np.abs(waveform.amp), 1.0) - def test_triangle_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - Triangle(duration=100, amp=1.1, phase=0) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = Triangle(duration=100, amp=1.1, phase=0) - self.assertGreater(np.abs(waveform.amp), 1.0) - def test_triangle_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per Triangle instance.""" with self.assertRaises(PulseError): Triangle(duration=100, amp=1.1, phase=0) waveform = Triangle(duration=100, amp=1.1, phase=0, limit_amplitude=False) self.assertGreater(np.abs(waveform.amp), 1.0) - def test_square_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - Square(duration=100, amp=1.1, phase=0) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = Square(duration=100, amp=1.1, phase=0) - self.assertGreater(np.abs(waveform.amp), 1.0) - def test_square_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per Square instance.""" with self.assertRaises(PulseError): Square(duration=100, amp=1.1, phase=0) waveform = Square(duration=100, amp=1.1, phase=0, limit_amplitude=False) self.assertGreater(np.abs(waveform.amp), 1.0) - def test_gaussian_deriv_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - GaussianDeriv(duration=100, amp=5, sigma=1) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = GaussianDeriv(duration=100, amp=5, sigma=1) - self.assertGreater(np.abs(waveform.amp / waveform.sigma), np.exp(0.5)) - def test_gaussian_deriv_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per GaussianDeriv instance.""" with self.assertRaises(PulseError): GaussianDeriv(duration=100, amp=5, sigma=1) waveform = GaussianDeriv(duration=100, amp=5, sigma=1, limit_amplitude=False) self.assertGreater(np.abs(waveform.amp / waveform.sigma), np.exp(0.5)) - def test_sech_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - Sech(duration=100, amp=5, sigma=1) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = Sech(duration=100, amp=5, sigma=1) - self.assertGreater(np.abs(waveform.amp), 1.0) - def test_sech_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per Sech instance.""" with self.assertRaises(PulseError): Sech(duration=100, amp=5, sigma=1) waveform = Sech(duration=100, amp=5, sigma=1, limit_amplitude=False) self.assertGreater(np.abs(waveform.amp), 1.0) - def test_sech_deriv_limit_amplitude(self): - """Test that the check for amplitude less than or equal to 1 can be disabled.""" - with self.assertRaises(PulseError): - SechDeriv(duration=100, amp=5, sigma=1) - - with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = SechDeriv(duration=100, amp=5, sigma=1) - self.assertGreater(np.abs(waveform.amp) / waveform.sigma, 2.0) - def test_sech_deriv_limit_amplitude_per_instance(self): - """Test that the check for amplitude per instance.""" + """Test limit amplitude option per SechDeriv instance.""" with self.assertRaises(PulseError): SechDeriv(duration=100, amp=5, sigma=1) diff --git a/test/python/pulse/test_pulse_targets.py b/test/python/pulse/test_pulse_targets.py new file mode 100644 index 000000000000..d3a8c92c3216 --- /dev/null +++ b/test/python/pulse/test_pulse_targets.py @@ -0,0 +1,89 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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. + +"""Test pulse logical elements and frames""" + +from qiskit.pulse import ( + PulseError, + Qubit, + Coupler, + Port, +) +from qiskit.test import QiskitTestCase + + +class TestLogicalElements(QiskitTestCase): + """Test logical elements.""" + + def test_qubit_initialization(self): + """Test that Qubit type logical elements are created and validated correctly""" + qubit = Qubit(0) + self.assertEqual(qubit.index, (0,)) + self.assertEqual(qubit.qubit_index, 0) + self.assertEqual(str(qubit), "Qubit(0)") + + with self.assertRaises(PulseError): + Qubit(0.5) + with self.assertRaises(PulseError): + Qubit(-0.5) + with self.assertRaises(PulseError): + Qubit(-1) + + def test_coupler_initialization(self): + """Test that Coupler type logical elements are created and validated correctly""" + coupler = Coupler(0, 3) + self.assertEqual(coupler.index, (0, 3)) + self.assertEqual(str(coupler), "Coupler(0, 3)") + + coupler = Coupler(0, 3, 2) + self.assertEqual(coupler.index, (0, 3, 2)) + + with self.assertRaises(PulseError): + Coupler(-1, 0) + with self.assertRaises(PulseError): + Coupler(2, -0.5) + with self.assertRaises(PulseError): + Coupler(3, -1) + with self.assertRaises(PulseError): + Coupler(0, 0, 1) + with self.assertRaises(PulseError): + Coupler(0) + + def test_logical_elements_comparison(self): + """Test the comparison of various logical elements""" + self.assertEqual(Qubit(0), Qubit(0)) + self.assertNotEqual(Qubit(0), Qubit(1)) + + self.assertEqual(Coupler(0, 1), Coupler(0, 1)) + self.assertNotEqual(Coupler(0, 1), Coupler(0, 2)) + + +class TestPorts(QiskitTestCase): + """Test ports.""" + + def test_ports_initialization(self): + """Test that Ports are created correctly""" + port = Port("d0") + self.assertEqual(port.name, "d0") + + def test_ports_comparison(self): + """Test that Ports are compared correctly""" + port1 = Port("d0") + port2 = Port("d0") + port3 = Port("d1") + self.assertEqual(port1, port2) + self.assertNotEqual(port1, port3) + + def test_ports_representation(self): + """Test Ports repr""" + port1 = Port("d0") + self.assertEqual(str(port1), "Port(d0)") diff --git a/test/python/pulse/test_schedule.py b/test/python/pulse/test_schedule.py index 654b4ffe39f9..006925c586ba 100644 --- a/test/python/pulse/test_schedule.py +++ b/test/python/pulse/test_schedule.py @@ -117,8 +117,9 @@ def test_fail_to_insert_instruction_into_occupied_timing(self): def test_can_create_valid_schedule(self): """Test valid schedule creation without error.""" - gp0 = library.gaussian(duration=20, amp=0.7, sigma=3) - gp1 = library.gaussian(duration=20, amp=0.7, sigma=3) + with self.assertWarns(DeprecationWarning): + gp0 = library.gaussian(duration=20, amp=0.7, sigma=3) + gp1 = library.gaussian(duration=20, amp=0.7, sigma=3) sched = Schedule() sched = sched.append(Play(gp0, self.config.drive(0))) @@ -147,8 +148,9 @@ def test_can_create_valid_schedule(self): def test_can_create_valid_schedule_with_syntax_sugar(self): """Test that in place operations on schedule are still immutable and return equivalent schedules.""" - gp0 = library.gaussian(duration=20, amp=0.7, sigma=3) - gp1 = library.gaussian(duration=20, amp=0.5, sigma=3) + with self.assertWarns(DeprecationWarning): + gp0 = library.gaussian(duration=20, amp=0.7, sigma=3) + gp1 = library.gaussian(duration=20, amp=0.5, sigma=3) sched = Schedule() sched += Play(gp0, self.config.drive(0)) @@ -162,8 +164,9 @@ def test_can_create_valid_schedule_with_syntax_sugar(self): def test_immutability(self): """Test that operations are immutable.""" - gp0 = library.gaussian(duration=100, amp=0.7, sigma=3) - gp1 = library.gaussian(duration=20, amp=0.5, sigma=3) + with self.assertWarns(DeprecationWarning): + gp0 = library.gaussian(duration=100, amp=0.7, sigma=3) + gp1 = library.gaussian(duration=20, amp=0.5, sigma=3) sched = Play(gp1, self.config.drive(0)) << 100 # if schedule was mutable the next two sequences would overlap and an error @@ -173,8 +176,9 @@ def test_immutability(self): def test_inplace(self): """Test that in place operations on schedule are still immutable.""" - gp0 = library.gaussian(duration=100, amp=0.7, sigma=3) - gp1 = library.gaussian(duration=20, amp=0.5, sigma=3) + with self.assertWarns(DeprecationWarning): + gp0 = library.gaussian(duration=100, amp=0.7, sigma=3) + gp1 = library.gaussian(duration=20, amp=0.5, sigma=3) sched = Schedule() sched = sched + Play(gp1, self.config.drive(0)) @@ -300,7 +304,8 @@ def test_auto_naming(self, is_main_process_mock): def test_name_inherited(self): """Test that schedule keeps name if an instruction is added.""" - gp0 = library.gaussian(duration=100, amp=0.7, sigma=3, name="pulse_name") + with self.assertWarns(DeprecationWarning): + gp0 = library.gaussian(duration=100, amp=0.7, sigma=3, name="pulse_name") snapshot = Snapshot("snapshot_label", "state") sched1 = Schedule(name="test_name") diff --git a/test/python/qpy/test_circuit_load_from_qpy.py b/test/python/qpy/test_circuit_load_from_qpy.py index 3684e8aacfe6..ba6aeb81f290 100644 --- a/test/python/qpy/test_circuit_load_from_qpy.py +++ b/test/python/qpy/test_circuit_load_from_qpy.py @@ -13,12 +13,15 @@ """Test cases for the schedule block qpy loading and saving.""" import io +import struct from ddt import ddt, data from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit from qiskit.providers.fake_provider import FakeHanoi, FakeSherbrooke -from qiskit.qpy import dump, load +from qiskit.exceptions import QiskitError +from qiskit.qpy import dump, load, formats +from qiskit.qpy.common import QPY_VERSION from qiskit.test import QiskitTestCase from qiskit.transpiler import PassManager, TranspileLayout from qiskit.transpiler import passes @@ -71,6 +74,20 @@ def test_rzx_calibration_echo(self, angle): self.assert_roundtrip_equal(rzx_qc) +class TestVersions(QpyCircuitTestCase): + """Test version handling in qpy.""" + + def test_invalid_qpy_version(self): + """Test a descriptive exception is raised if QPY version is too new.""" + with io.BytesIO() as buf: + buf.write( + struct.pack(formats.FILE_HEADER_PACK, b"QISKIT", QPY_VERSION + 4, 42, 42, 1, 2) + ) + buf.seek(0) + with self.assertRaisesRegex(QiskitError, str(QPY_VERSION + 4)): + load(buf) + + @ddt class TestLayout(QpyCircuitTestCase): """Test circuit serialization for layout preservation.""" diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index 76cd9463f00e..615f09e6ad21 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -1044,7 +1044,8 @@ def test_visualize_does_not_throw_error(self): # An error may be thrown if visualization code calls op.condition instead # of getattr(op, "condition", None) clifford = random_clifford(3, seed=0) - print(clifford) + _ = str(clifford) + _ = repr(clifford) @combine(num_qubits=[1, 2, 3, 4]) def test_from_matrix_round_trip(self, num_qubits): diff --git a/test/python/transpiler/aqc/test_aqc.py b/test/python/transpiler/aqc/test_aqc.py index 45b1a5ea51ee..c54de8c21dc7 100644 --- a/test/python/transpiler/aqc/test_aqc.py +++ b/test/python/transpiler/aqc/test_aqc.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2022. +# (C) Copyright IBM 2022, 2023. # # 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 @@ -12,10 +12,15 @@ """ Tests AQC framework using hardcoded and randomly generated circuits. """ +from functools import partial + import unittest from test.python.transpiler.aqc.sample_data import ORIGINAL_CIRCUIT, INITIAL_THETAS + +from ddt import ddt, data import numpy as np -from qiskit.algorithms.optimizers import L_BFGS_B +from scipy.optimize import minimize + from qiskit.quantum_info import Operator from qiskit.test import QiskitTestCase from qiskit.transpiler.synthesis.aqc.aqc import AQC @@ -25,10 +30,12 @@ from qiskit.transpiler.synthesis.aqc.fast_gradient.fast_gradient import FastCNOTUnitObjective +@ddt class TestAqc(QiskitTestCase): """Main tests of approximate quantum compiler.""" - def test_aqc(self): + @data(True, False) + def test_aqc(self, uses_default): """Tests AQC on a hardcoded circuit/matrix.""" seed = 12345 @@ -38,9 +45,11 @@ def test_aqc(self): num_qubits=num_qubits, network_layout="spin", connectivity_type="full", depth=0 ) - optimizer = L_BFGS_B(maxiter=200) - - aqc = AQC(optimizer=optimizer, seed=seed) + if uses_default: + aqc = AQC(seed=seed) + else: + optimizer = partial(minimize, args=(), method="L-BFGS-B", options={"maxiter": 200}) + aqc = AQC(optimizer=optimizer, seed=seed) target_matrix = ORIGINAL_CIRCUIT approximate_circuit = CNOTUnitCircuit(num_qubits, cnots) @@ -55,7 +64,7 @@ def test_aqc(self): approx_matrix = Operator(approximate_circuit).data error = 0.5 * (np.linalg.norm(approx_matrix - ORIGINAL_CIRCUIT, "fro") ** 2) - self.assertTrue(error < 1e-3) + self.assertLess(error, 1e-3) def test_aqc_fastgrad(self): """ @@ -70,7 +79,7 @@ def test_aqc_fastgrad(self): num_qubits=num_qubits, network_layout="spin", connectivity_type="full", depth=0 ) - optimizer = L_BFGS_B(maxiter=200) + optimizer = partial(minimize, args=(), method="L-BFGS-B", options={"maxiter": 200}) aqc = AQC(optimizer=optimizer, seed=seed) # Make multi-control CNOT gate matrix. @@ -103,7 +112,7 @@ def test_aqc_determinant_minus_one(self): num_qubits=num_qubits, network_layout="spin", connectivity_type="full", depth=0 ) - optimizer = L_BFGS_B(maxiter=200) + optimizer = partial(minimize, args=(), method="L-BFGS-B", options={"maxiter": 200}) aqc = AQC(optimizer=optimizer, seed=seed) target_matrix = np.eye(2**num_qubits, dtype=int) diff --git a/test/python/transpiler/aqc/test_aqc_plugin.py b/test/python/transpiler/aqc/test_aqc_plugin.py index 0ad2742a1195..b5f3bf1858f4 100644 --- a/test/python/transpiler/aqc/test_aqc_plugin.py +++ b/test/python/transpiler/aqc/test_aqc_plugin.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2021, 2023. # # 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 @@ -12,11 +12,12 @@ """ Tests AQC plugin. """ +from functools import partial import numpy as np +from scipy.optimize import minimize from qiskit import QuantumCircuit -from qiskit.algorithms.optimizers import SLSQP from qiskit.converters import dag_to_circuit, circuit_to_dag from qiskit.quantum_info import Operator from qiskit.test import QiskitTestCase @@ -68,12 +69,13 @@ def test_plugin_setup(self): def test_plugin_configuration(self): """Tests plugin with a custom configuration.""" + optimizer = partial(minimize, args=(), method="SLSQP") config = { "network_layout": "sequ", "connectivity_type": "full", "depth": 0, "seed": 12345, - "optimizer": SLSQP(), + "optimizer": optimizer, } transpiler_pass = UnitarySynthesis( basis_gates=["rx", "ry", "rz", "cx"], method="aqc", plugin_config=config diff --git a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py index feb50bab8002..efd3b86ee393 100644 --- a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py +++ b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py @@ -14,8 +14,316 @@ from qiskit import QuantumCircuit, pulse from qiskit.test import QiskitTestCase +from qiskit.transpiler import InstructionDurations from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes import ValidatePulseGates +from qiskit.transpiler.passes import ( + AlignMeasures, + ValidatePulseGates, + ALAPSchedule, + TimeUnitConversion, +) + + +class TestAlignMeasures(QiskitTestCase): + """A test for measurement alignment pass.""" + + def setUp(self): + super().setUp() + instruction_durations = InstructionDurations() + instruction_durations.update( + [ + ("rz", (0,), 0), + ("rz", (1,), 0), + ("x", (0,), 160), + ("x", (1,), 160), + ("sx", (0,), 160), + ("sx", (1,), 160), + ("cx", (0, 1), 800), + ("cx", (1, 0), 800), + ("measure", None, 1600), + ] + ) + self.time_conversion_pass = TimeUnitConversion(inst_durations=instruction_durations) + # reproduce old behavior of 0.20.0 before #7655 + # currently default write latency is 0 + self.scheduling_pass = ALAPSchedule( + durations=instruction_durations, + clbit_write_latency=1600, + conditional_latency=0, + ) + self.align_measure_pass = AlignMeasures(alignment=16) + + def test_t1_experiment_type(self): + """Test T1 experiment type circuit. + + (input) + + ┌───┐┌────────────────┐┌─┐ + q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├ + └───┘└────────────────┘└╥┘ + c: 1/════════════════════════╩═ + 0 + + (aligned) + + ┌───┐┌────────────────┐┌─┐ + q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├ + └───┘└────────────────┘└╥┘ + c: 1/════════════════════════╩═ + 0 + + This type of experiment slightly changes delay duration of interest. + However the quantization error should be less than alignment * dt. + """ + circuit = QuantumCircuit(1, 1) + circuit.x(0) + circuit.delay(100, 0, unit="dt") + circuit.measure(0, 0) + + timed_circuit = self.time_conversion_pass(circuit) + scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) + aligned_circuit = self.align_measure_pass( + scheduled_circuit, property_set={"time_unit": "dt"} + ) + + ref_circuit = QuantumCircuit(1, 1) + ref_circuit.x(0) + ref_circuit.delay(112, 0, unit="dt") + ref_circuit.measure(0, 0) + + self.assertEqual(aligned_circuit, ref_circuit) + + def test_hanh_echo_experiment_type(self): + """Test Hahn echo experiment type circuit. + + (input) + + ┌────┐┌────────────────┐┌───┐┌────────────────┐┌────┐┌─┐ + q_0: ┤ √X ├┤ Delay(100[dt]) ├┤ X ├┤ Delay(100[dt]) ├┤ √X ├┤M├ + └────┘└────────────────┘└───┘└────────────────┘└────┘└╥┘ + c: 1/══════════════════════════════════════════════════════╩═ + 0 + + (output) + + ┌────┐┌────────────────┐┌───┐┌────────────────┐┌────┐┌──────────────┐┌─┐ + q_0: ┤ √X ├┤ Delay(100[dt]) ├┤ X ├┤ Delay(100[dt]) ├┤ √X ├┤ Delay(8[dt]) ├┤M├ + └────┘└────────────────┘└───┘└────────────────┘└────┘└──────────────┘└╥┘ + c: 1/══════════════════════════════════════════════════════════════════════╩═ + 0 + + This type of experiment doesn't change duration of interest (two in the middle). + However induces slight delay less than alignment * dt before measurement. + This might induce extra amplitude damping error. + """ + circuit = QuantumCircuit(1, 1) + circuit.sx(0) + circuit.delay(100, 0, unit="dt") + circuit.x(0) + circuit.delay(100, 0, unit="dt") + circuit.sx(0) + circuit.measure(0, 0) + + timed_circuit = self.time_conversion_pass(circuit) + scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) + aligned_circuit = self.align_measure_pass( + scheduled_circuit, property_set={"time_unit": "dt"} + ) + + ref_circuit = QuantumCircuit(1, 1) + ref_circuit.sx(0) + ref_circuit.delay(100, 0, unit="dt") + ref_circuit.x(0) + ref_circuit.delay(100, 0, unit="dt") + ref_circuit.sx(0) + ref_circuit.delay(8, 0, unit="dt") + ref_circuit.measure(0, 0) + + self.assertEqual(aligned_circuit, ref_circuit) + + def test_mid_circuit_measure(self): + """Test circuit with mid circuit measurement. + + (input) + + ┌───┐┌────────────────┐┌─┐┌───────────────┐┌───┐┌────────────────┐┌─┐ + q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├┤ Delay(10[dt]) ├┤ X ├┤ Delay(120[dt]) ├┤M├ + └───┘└────────────────┘└╥┘└───────────────┘└───┘└────────────────┘└╥┘ + c: 2/════════════════════════╩══════════════════════════════════════════╩═ + 0 1 + + (output) + + ┌───┐┌────────────────┐┌─┐┌───────────────┐┌───┐┌────────────────┐┌─┐ + q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├┤ Delay(10[dt]) ├┤ X ├┤ Delay(134[dt]) ├┤M├ + └───┘└────────────────┘└╥┘└───────────────┘└───┘└────────────────┘└╥┘ + c: 2/════════════════════════╩══════════════════════════════════════════╩═ + 0 1 + + Extra delay is always added to the existing delay right before the measurement. + Delay after measurement is unchanged. + """ + circuit = QuantumCircuit(1, 2) + circuit.x(0) + circuit.delay(100, 0, unit="dt") + circuit.measure(0, 0) + circuit.delay(10, 0, unit="dt") + circuit.x(0) + circuit.delay(120, 0, unit="dt") + circuit.measure(0, 1) + + timed_circuit = self.time_conversion_pass(circuit) + scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) + aligned_circuit = self.align_measure_pass( + scheduled_circuit, property_set={"time_unit": "dt"} + ) + + ref_circuit = QuantumCircuit(1, 2) + ref_circuit.x(0) + ref_circuit.delay(112, 0, unit="dt") + ref_circuit.measure(0, 0) + ref_circuit.delay(10, 0, unit="dt") + ref_circuit.x(0) + ref_circuit.delay(134, 0, unit="dt") + ref_circuit.measure(0, 1) + + self.assertEqual(aligned_circuit, ref_circuit) + + def test_mid_circuit_multiq_gates(self): + """Test circuit with mid circuit measurement and multi qubit gates. + + (input) + + ┌───┐┌────────────────┐┌─┐ ┌─┐ + q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├──■───────■──┤M├ + └───┘└────────────────┘└╥┘┌─┴─┐┌─┐┌─┴─┐└╥┘ + q_1: ────────────────────────╫─┤ X ├┤M├┤ X ├─╫─ + ║ └───┘└╥┘└───┘ ║ + c: 2/════════════════════════╩═══════╩═══════╩═ + 0 1 0 + + (output) + + ┌───┐ ┌────────────────┐┌─┐ ┌─────────────────┐ ┌─┐» + q_0: ───────┤ X ├───────┤ Delay(112[dt]) ├┤M├──■──┤ Delay(1600[dt]) ├──■──┤M├» + ┌──────┴───┴──────┐└────────────────┘└╥┘┌─┴─┐└───────┬─┬───────┘┌─┴─┐└╥┘» + q_1: ┤ Delay(1872[dt]) ├───────────────────╫─┤ X ├────────┤M├────────┤ X ├─╫─» + └─────────────────┘ ║ └───┘ └╥┘ └───┘ ║ » + c: 2/══════════════════════════════════════╩═══════════════╩═══════════════╩═» + 0 1 0 » + « + «q_0: ─────────────────── + « ┌─────────────────┐ + «q_1: ┤ Delay(1600[dt]) ├ + « └─────────────────┘ + «c: 2/═══════════════════ + « + + Delay for the other channel paired by multi-qubit instruction is also scheduled. + Delay (1872dt) = X (160dt) + Delay (100dt + extra 12dt) + Measure (1600dt). + """ + circuit = QuantumCircuit(2, 2) + circuit.x(0) + circuit.delay(100, 0, unit="dt") + circuit.measure(0, 0) + circuit.cx(0, 1) + circuit.measure(1, 1) + circuit.cx(0, 1) + circuit.measure(0, 0) + + timed_circuit = self.time_conversion_pass(circuit) + scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) + aligned_circuit = self.align_measure_pass( + scheduled_circuit, property_set={"time_unit": "dt"} + ) + + ref_circuit = QuantumCircuit(2, 2) + ref_circuit.x(0) + ref_circuit.delay(112, 0, unit="dt") + ref_circuit.measure(0, 0) + ref_circuit.delay(160 + 112 + 1600, 1, unit="dt") + ref_circuit.cx(0, 1) + ref_circuit.delay(1600, 0, unit="dt") + ref_circuit.measure(1, 1) + ref_circuit.cx(0, 1) + ref_circuit.delay(1600, 1, unit="dt") + ref_circuit.measure(0, 0) + + self.assertEqual(aligned_circuit, ref_circuit) + + def test_alignment_is_not_processed(self): + """Test avoid pass processing if delay is aligned.""" + circuit = QuantumCircuit(2, 2) + circuit.x(0) + circuit.delay(160, 0, unit="dt") + circuit.measure(0, 0) + circuit.cx(0, 1) + circuit.measure(1, 1) + circuit.cx(0, 1) + circuit.measure(0, 0) + + # pre scheduling is not necessary because alignment is skipped + # this is to minimize breaking changes to existing code. + transpiled = self.align_measure_pass(circuit, property_set={"time_unit": "dt"}) + + self.assertEqual(transpiled, circuit) + + def test_circuit_using_clbit(self): + """Test a circuit with instructions using a common clbit. + + (input) + ┌───┐┌────────────────┐┌─┐ + q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├────────────── + └───┘└────────────────┘└╥┘ ┌───┐ + q_1: ────────────────────────╫────┤ X ├────── + ║ └─╥─┘ ┌─┐ + q_2: ────────────────────────╫──────╫─────┤M├ + ║ ┌────╨────┐└╥┘ + c: 1/════════════════════════╩═╡ c_0 = T ╞═╩═ + 0 └─────────┘ 0 + + (aligned) + ┌───┐ ┌────────────────┐┌─┐┌────────────────┐ + q_0: ───────┤ X ├───────┤ Delay(112[dt]) ├┤M├┤ Delay(160[dt]) ├─── + ┌──────┴───┴──────┐└────────────────┘└╥┘└─────┬───┬──────┘ + q_1: ┤ Delay(1872[dt]) ├───────────────────╫───────┤ X ├────────── + └┬────────────────┤ ║ └─╥─┘ ┌─┐ + q_2: ─┤ Delay(432[dt]) ├───────────────────╫─────────╫─────────┤M├ + └────────────────┘ ║ ┌────╨────┐ └╥┘ + c: 1/══════════════════════════════════════╩════╡ c_0 = T ╞═════╩═ + 0 └─────────┘ 0 + + Looking at the q_0, the total schedule length T becomes + 160 (x) + 112 (aligned delay) + 1600 (measure) + 160 (delay) = 2032. + The last delay comes from ALAP scheduling called before the AlignMeasure pass, + which aligns stop times as late as possible, so the start time of x(1).c_if(0) + and the stop time of measure(0, 0) become T - 160. + """ + circuit = QuantumCircuit(3, 1) + circuit.x(0) + circuit.delay(100, 0, unit="dt") + circuit.measure(0, 0) + circuit.x(1).c_if(0, 1) + circuit.measure(2, 0) + + timed_circuit = self.time_conversion_pass(circuit) + scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) + aligned_circuit = self.align_measure_pass( + scheduled_circuit, property_set={"time_unit": "dt"} + ) + self.assertEqual(aligned_circuit.duration, 2032) + + ref_circuit = QuantumCircuit(3, 1) + ref_circuit.x(0) + ref_circuit.delay(112, 0, unit="dt") + ref_circuit.delay(1872, 1, unit="dt") # 2032 - 160 + ref_circuit.delay(432, 2, unit="dt") # 2032 - 1600 + ref_circuit.measure(0, 0) + ref_circuit.x(1).c_if(0, 1) + ref_circuit.delay(160, 0, unit="dt") + ref_circuit.measure(2, 0) + + self.assertEqual(aligned_circuit, ref_circuit) class TestPulseGateValidation(QiskitTestCase): diff --git a/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py b/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py new file mode 100644 index 000000000000..2f375c46f67b --- /dev/null +++ b/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py @@ -0,0 +1,811 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""Test the legacy Scheduling passes""" + +import unittest + +from ddt import ddt, data, unpack + +from qiskit import QuantumCircuit +from qiskit.circuit import Delay, Parameter +from qiskit.circuit.library.standard_gates import XGate, YGate, CXGate +from qiskit.test import QiskitTestCase +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.instruction_durations import InstructionDurations +from qiskit.transpiler.passes import ASAPSchedule, ALAPSchedule, DynamicalDecoupling +from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.target import Target, InstructionProperties + + +@ddt +class TestSchedulingPass(QiskitTestCase): + """Tests the Scheduling passes""" + + def test_alap_agree_with_reverse_asap_reverse(self): + """Test if ALAP schedule agrees with doubly-reversed ASAP schedule.""" + qc = QuantumCircuit(2) + qc.h(0) + qc.delay(500, 1) + qc.cx(0, 1) + qc.measure_all() + + durations = InstructionDurations( + [("h", 0, 200), ("cx", [0, 1], 700), ("measure", None, 1000)] + ) + + pm = PassManager(ALAPSchedule(durations)) + alap_qc = pm.run(qc) + + pm = PassManager(ASAPSchedule(durations)) + new_qc = pm.run(qc.reverse_ops()) + new_qc = new_qc.reverse_ops() + new_qc.name = new_qc.name + + self.assertEqual(alap_qc, new_qc) + + @data(ALAPSchedule, ASAPSchedule) + def test_classically_controlled_gate_after_measure(self, schedule_pass): + """Test if ALAP/ASAP schedules circuits with c_if after measure with a common clbit. + See: https://github.com/Qiskit/qiskit-terra/issues/7654 + + (input) + ┌─┐ + q_0: ┤M├─────────── + └╥┘ ┌───┐ + q_1: ─╫────┤ X ├─── + ║ └─╥─┘ + ║ ┌────╨────┐ + c: 1/═╩═╡ c_0 = T ╞ + 0 └─────────┘ + + (scheduled) + ┌─┐┌────────────────┐ + q_0: ───────────────────┤M├┤ Delay(200[dt]) ├ + ┌─────────────────┐└╥┘└─────┬───┬──────┘ + q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├─────── + └─────────────────┘ ║ └─╥─┘ + ║ ┌────╨────┐ + c: 1/════════════════════╩════╡ c_0=0x1 ╞════ + 0 └─────────┘ + """ + qc = QuantumCircuit(2, 1) + qc.measure(0, 0) + qc.x(1).c_if(0, True) + + durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) + pm = PassManager(schedule_pass(durations)) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.measure(0, 0) + expected.delay(1000, 1) # x.c_if starts after measure + expected.x(1).c_if(0, True) + expected.delay(200, 0) + + self.assertEqual(expected, scheduled) + + @data(ALAPSchedule, ASAPSchedule) + def test_measure_after_measure(self, schedule_pass): + """Test if ALAP/ASAP schedules circuits with measure after measure with a common clbit. + See: https://github.com/Qiskit/qiskit-terra/issues/7654 + + (input) + ┌───┐┌─┐ + q_0: ┤ X ├┤M├─── + └───┘└╥┘┌─┐ + q_1: ──────╫─┤M├ + ║ └╥┘ + c: 1/══════╩══╩═ + 0 0 + + (scheduled) + ┌───┐ ┌─┐┌─────────────────┐ + q_0: ───────┤ X ├───────┤M├┤ Delay(1000[dt]) ├ + ┌──────┴───┴──────┐└╥┘└───────┬─┬───────┘ + q_1: ┤ Delay(1200[dt]) ├─╫─────────┤M├──────── + └─────────────────┘ ║ └╥┘ + c: 1/════════════════════╩══════════╩═════════ + 0 0 + """ + qc = QuantumCircuit(2, 1) + qc.x(0) + qc.measure(0, 0) + qc.measure(1, 0) + + durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) + pm = PassManager(schedule_pass(durations)) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.x(0) + expected.measure(0, 0) + expected.delay(1200, 1) + expected.measure(1, 0) + expected.delay(1000, 0) + + self.assertEqual(expected, scheduled) + + @data(ALAPSchedule, ASAPSchedule) + def test_c_if_on_different_qubits(self, schedule_pass): + """Test if ALAP/ASAP schedules circuits with `c_if`s on different qubits. + + (input) + ┌─┐ + q_0: ┤M├────────────────────── + └╥┘ ┌───┐ + q_1: ─╫────┤ X ├────────────── + ║ └─╥─┘ ┌───┐ + q_2: ─╫──────╫────────┤ X ├─── + ║ ║ └─╥─┘ + ║ ┌────╨────┐┌────╨────┐ + c: 1/═╩═╡ c_0 = T ╞╡ c_0 = T ╞ + 0 └─────────┘└─────────┘ + + (scheduled) + + ┌─┐┌────────────────┐ + q_0: ───────────────────┤M├┤ Delay(200[dt]) ├─────────── + ┌─────────────────┐└╥┘└─────┬───┬──────┘ + q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├────────────────── + ├─────────────────┤ ║ └─╥─┘ ┌───┐ + q_2: ┤ Delay(1000[dt]) ├─╫─────────╫────────────┤ X ├─── + └─────────────────┘ ║ ║ └─╥─┘ + ║ ┌────╨────┐ ┌────╨────┐ + c: 1/════════════════════╩════╡ c_0=0x1 ╞════╡ c_0=0x1 ╞ + 0 └─────────┘ └─────────┘ + """ + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.x(1).c_if(0, True) + qc.x(2).c_if(0, True) + + durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) + pm = PassManager(schedule_pass(durations)) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.measure(0, 0) + expected.delay(1000, 1) + expected.delay(1000, 2) + expected.x(1).c_if(0, True) + expected.x(2).c_if(0, True) + expected.delay(200, 0) + + self.assertEqual(expected, scheduled) + + @data(ALAPSchedule, ASAPSchedule) + def test_shorter_measure_after_measure(self, schedule_pass): + """Test if ALAP/ASAP schedules circuits with shorter measure after measure with a common clbit. + + (input) + ┌─┐ + q_0: ┤M├─── + └╥┘┌─┐ + q_1: ─╫─┤M├ + ║ └╥┘ + c: 1/═╩══╩═ + 0 0 + + (scheduled) + ┌─┐┌────────────────┐ + q_0: ───────────────────┤M├┤ Delay(700[dt]) ├ + ┌─────────────────┐└╥┘└──────┬─┬───────┘ + q_1: ┤ Delay(1000[dt]) ├─╫────────┤M├──────── + └─────────────────┘ ║ └╥┘ + c: 1/════════════════════╩═════════╩═════════ + 0 0 + """ + qc = QuantumCircuit(2, 1) + qc.measure(0, 0) + qc.measure(1, 0) + + durations = InstructionDurations([("measure", [0], 1000), ("measure", [1], 700)]) + pm = PassManager(schedule_pass(durations)) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.measure(0, 0) + expected.delay(1000, 1) + expected.measure(1, 0) + expected.delay(700, 0) + + self.assertEqual(expected, scheduled) + + @data(ALAPSchedule, ASAPSchedule) + def test_measure_after_c_if(self, schedule_pass): + """Test if ALAP/ASAP schedules circuits with c_if after measure with a common clbit. + + (input) + ┌─┐ + q_0: ┤M├────────────── + └╥┘ ┌───┐ + q_1: ─╫────┤ X ├────── + ║ └─╥─┘ ┌─┐ + q_2: ─╫──────╫─────┤M├ + ║ ┌────╨────┐└╥┘ + c: 1/═╩═╡ c_0 = T ╞═╩═ + 0 └─────────┘ 0 + + (scheduled) + ┌─┐┌─────────────────┐ + q_0: ───────────────────┤M├┤ Delay(1000[dt]) ├────────────────── + ┌─────────────────┐└╥┘└──────┬───┬──────┘┌────────────────┐ + q_1: ┤ Delay(1000[dt]) ├─╫────────┤ X ├───────┤ Delay(800[dt]) ├ + ├─────────────────┤ ║ └─╥─┘ └──────┬─┬───────┘ + q_2: ┤ Delay(1000[dt]) ├─╫──────────╫────────────────┤M├──────── + └─────────────────┘ ║ ┌────╨────┐ └╥┘ + c: 1/════════════════════╩═════╡ c_0=0x1 ╞════════════╩═════════ + 0 └─────────┘ 0 + """ + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.x(1).c_if(0, 1) + qc.measure(2, 0) + + durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) + pm = PassManager(schedule_pass(durations)) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.delay(1000, 1) + expected.delay(1000, 2) + expected.measure(0, 0) + expected.x(1).c_if(0, 1) + expected.measure(2, 0) + expected.delay(1000, 0) + expected.delay(800, 1) + + self.assertEqual(expected, scheduled) + + def test_parallel_gate_different_length(self): + """Test circuit having two parallel instruction with different length. + + (input) + ┌───┐┌─┐ + q_0: ┤ X ├┤M├─── + ├───┤└╥┘┌─┐ + q_1: ┤ X ├─╫─┤M├ + └───┘ ║ └╥┘ + c: 2/══════╩══╩═ + 0 1 + + (expected, ALAP) + ┌────────────────┐┌───┐┌─┐ + q_0: ┤ Delay(200[dt]) ├┤ X ├┤M├ + └─────┬───┬──────┘└┬─┬┘└╥┘ + q_1: ──────┤ X ├────────┤M├──╫─ + └───┘ └╥┘ ║ + c: 2/════════════════════╩═══╩═ + 1 0 + + (expected, ASAP) + ┌───┐┌─┐┌────────────────┐ + q_0: ┤ X ├┤M├┤ Delay(200[dt]) ├ + ├───┤└╥┘└──────┬─┬───────┘ + q_1: ┤ X ├─╫────────┤M├──────── + └───┘ ║ └╥┘ + c: 2/══════╩═════════╩═════════ + 0 1 + + """ + qc = QuantumCircuit(2, 2) + qc.x(0) + qc.x(1) + qc.measure(0, 0) + qc.measure(1, 1) + + durations = InstructionDurations( + [("x", [0], 200), ("x", [1], 400), ("measure", None, 1000)] + ) + pm = PassManager(ALAPSchedule(durations)) + qc_alap = pm.run(qc) + + alap_expected = QuantumCircuit(2, 2) + alap_expected.delay(200, 0) + alap_expected.x(0) + alap_expected.x(1) + alap_expected.measure(0, 0) + alap_expected.measure(1, 1) + + self.assertEqual(qc_alap, alap_expected) + + pm = PassManager(ASAPSchedule(durations)) + qc_asap = pm.run(qc) + + asap_expected = QuantumCircuit(2, 2) + asap_expected.x(0) + asap_expected.x(1) + asap_expected.measure(0, 0) # immediately start after X gate + asap_expected.measure(1, 1) + asap_expected.delay(200, 0) + + self.assertEqual(qc_asap, asap_expected) + + def test_parallel_gate_different_length_with_barrier(self): + """Test circuit having two parallel instruction with different length with barrier. + + (input) + ┌───┐┌─┐ + q_0: ┤ X ├┤M├─── + ├───┤└╥┘┌─┐ + q_1: ┤ X ├─╫─┤M├ + └───┘ ║ └╥┘ + c: 2/══════╩══╩═ + 0 1 + + (expected, ALAP) + ┌────────────────┐┌───┐ ░ ┌─┐ + q_0: ┤ Delay(200[dt]) ├┤ X ├─░─┤M├─── + └─────┬───┬──────┘└───┘ ░ └╥┘┌─┐ + q_1: ──────┤ X ├─────────────░──╫─┤M├ + └───┘ ░ ║ └╥┘ + c: 2/═══════════════════════════╩══╩═ + 0 1 + + (expected, ASAP) + ┌───┐┌────────────────┐ ░ ┌─┐ + q_0: ┤ X ├┤ Delay(200[dt]) ├─░─┤M├─── + ├───┤└────────────────┘ ░ └╥┘┌─┐ + q_1: ┤ X ├───────────────────░──╫─┤M├ + └───┘ ░ ║ └╥┘ + c: 2/═══════════════════════════╩══╩═ + 0 1 + """ + qc = QuantumCircuit(2, 2) + qc.x(0) + qc.x(1) + qc.barrier() + qc.measure(0, 0) + qc.measure(1, 1) + + durations = InstructionDurations( + [("x", [0], 200), ("x", [1], 400), ("measure", None, 1000)] + ) + pm = PassManager(ALAPSchedule(durations)) + qc_alap = pm.run(qc) + + alap_expected = QuantumCircuit(2, 2) + alap_expected.delay(200, 0) + alap_expected.x(0) + alap_expected.x(1) + alap_expected.barrier() + alap_expected.measure(0, 0) + alap_expected.measure(1, 1) + + self.assertEqual(qc_alap, alap_expected) + + pm = PassManager(ASAPSchedule(durations)) + qc_asap = pm.run(qc) + + asap_expected = QuantumCircuit(2, 2) + asap_expected.x(0) + asap_expected.delay(200, 0) + asap_expected.x(1) + asap_expected.barrier() + asap_expected.measure(0, 0) + asap_expected.measure(1, 1) + + self.assertEqual(qc_asap, asap_expected) + + def test_measure_after_c_if_on_edge_locking(self): + """Test if ALAP/ASAP schedules circuits with c_if after measure with a common clbit. + + The scheduler is configured to reproduce behavior of the 0.20.0, + in which clbit lock is applied to the end-edge of measure instruction. + See https://github.com/Qiskit/qiskit-terra/pull/7655 + + (input) + ┌─┐ + q_0: ┤M├────────────── + └╥┘ ┌───┐ + q_1: ─╫────┤ X ├────── + ║ └─╥─┘ ┌─┐ + q_2: ─╫──────╫─────┤M├ + ║ ┌────╨────┐└╥┘ + c: 1/═╩═╡ c_0 = T ╞═╩═ + 0 └─────────┘ 0 + + (ASAP scheduled) + ┌─┐┌────────────────┐ + q_0: ───────────────────┤M├┤ Delay(200[dt]) ├───────────────────── + ┌─────────────────┐└╥┘└─────┬───┬──────┘ + q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├──────────────────────────── + └─────────────────┘ ║ └─╥─┘ ┌─┐┌────────────────┐ + q_2: ────────────────────╫─────────╫─────────┤M├┤ Delay(200[dt]) ├ + ║ ┌────╨────┐ └╥┘└────────────────┘ + c: 1/════════════════════╩════╡ c_0=0x1 ╞═════╩═══════════════════ + 0 └─────────┘ 0 + + (ALAP scheduled) + ┌─┐┌────────────────┐ + q_0: ───────────────────┤M├┤ Delay(200[dt]) ├─── + ┌─────────────────┐└╥┘└─────┬───┬──────┘ + q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├────────── + └┬────────────────┤ ║ └─╥─┘ ┌─┐ + q_2: ─┤ Delay(200[dt]) ├─╫─────────╫─────────┤M├ + └────────────────┘ ║ ┌────╨────┐ └╥┘ + c: 1/════════════════════╩════╡ c_0=0x1 ╞═════╩═ + 0 └─────────┘ 0 + + """ + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.x(1).c_if(0, 1) + qc.measure(2, 0) + + durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) + + # lock at the end edge + actual_asap = PassManager(ASAPSchedule(durations, clbit_write_latency=1000)).run(qc) + actual_alap = PassManager(ALAPSchedule(durations, clbit_write_latency=1000)).run(qc) + + # start times of 2nd measure depends on ASAP/ALAP + expected_asap = QuantumCircuit(3, 1) + expected_asap.measure(0, 0) + expected_asap.delay(1000, 1) + expected_asap.x(1).c_if(0, 1) + expected_asap.measure(2, 0) + expected_asap.delay(200, 0) + expected_asap.delay(200, 2) + self.assertEqual(expected_asap, actual_asap) + + expected_alap = QuantumCircuit(3, 1) + expected_alap.measure(0, 0) + expected_alap.delay(1000, 1) + expected_alap.x(1).c_if(0, 1) + expected_alap.delay(200, 2) + expected_alap.measure(2, 0) + expected_alap.delay(200, 0) + self.assertEqual(expected_alap, actual_alap) + + @data([100, 200], [500, 0], [1000, 200]) + @unpack + def test_active_reset_circuit(self, write_lat, cond_lat): + """Test practical example of reset circuit. + + Because of the stimulus pulse overlap with the previous XGate on the q register, + measure instruction is always triggered after XGate regardless of write latency. + Thus only conditional latency matters in the scheduling. + + (input) + ┌─┐ ┌───┐ ┌─┐ ┌───┐ ┌─┐ ┌───┐ + q: ┤M├───┤ X ├───┤M├───┤ X ├───┤M├───┤ X ├─── + └╥┘ └─╥─┘ └╥┘ └─╥─┘ └╥┘ └─╥─┘ + ║ ┌────╨────┐ ║ ┌────╨────┐ ║ ┌────╨────┐ + c: 1/═╩═╡ c_0=0x1 ╞═╩═╡ c_0=0x1 ╞═╩═╡ c_0=0x1 ╞ + 0 └─────────┘ 0 └─────────┘ 0 └─────────┘ + + """ + qc = QuantumCircuit(1, 1) + qc.measure(0, 0) + qc.x(0).c_if(0, 1) + qc.measure(0, 0) + qc.x(0).c_if(0, 1) + qc.measure(0, 0) + qc.x(0).c_if(0, 1) + + durations = InstructionDurations([("x", None, 100), ("measure", None, 1000)]) + actual_asap = PassManager( + ASAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) + ).run(qc) + actual_alap = PassManager( + ALAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) + ).run(qc) + + expected = QuantumCircuit(1, 1) + expected.measure(0, 0) + if cond_lat > 0: + expected.delay(cond_lat, 0) + expected.x(0).c_if(0, 1) + expected.measure(0, 0) + if cond_lat > 0: + expected.delay(cond_lat, 0) + expected.x(0).c_if(0, 1) + expected.measure(0, 0) + if cond_lat > 0: + expected.delay(cond_lat, 0) + expected.x(0).c_if(0, 1) + + self.assertEqual(expected, actual_asap) + self.assertEqual(expected, actual_alap) + + def test_random_complicated_circuit(self): + """Test scheduling complicated circuit with control flow. + + (input) + ┌────────────────┐ ┌───┐ ░ ┌───┐ » + q_0: ┤ Delay(100[dt]) ├───┤ X ├────░──────────────────┤ X ├───» + └────────────────┘ └─╥─┘ ░ ┌───┐ └─╥─┘ » + q_1: ───────────────────────╫──────░───────┤ X ├────────╫─────» + ║ ░ ┌─┐ └─╥─┘ ║ » + q_2: ───────────────────────╫──────░─┤M├─────╫──────────╫─────» + ┌────╨────┐ ░ └╥┘┌────╨────┐┌────╨────┐» + c: 1/══════════════════╡ c_0=0x1 ╞════╩═╡ c_0=0x0 ╞╡ c_0=0x0 ╞» + └─────────┘ 0 └─────────┘└─────────┘» + « ┌────────────────┐┌───┐ + «q_0: ┤ Delay(300[dt]) ├┤ X ├─────■───── + « └────────────────┘└───┘ ┌─┴─┐ + «q_1: ────────■─────────────────┤ X ├─── + « ┌─┴─┐ ┌─┐ └─╥─┘ + «q_2: ──────┤ X ├────────┤M├──────╫───── + « └───┘ └╥┘ ┌────╨────┐ + «c: 1/════════════════════╩══╡ c_0=0x0 ╞ + « 0 └─────────┘ + + (ASAP scheduled) duration = 2800 dt + ┌────────────────┐┌────────────────┐ ┌───┐ ░ ┌─────────────────┐» + q_0: ┤ Delay(100[dt]) ├┤ Delay(100[dt]) ├───┤ X ├────░─┤ Delay(1400[dt]) ├» + ├────────────────┤└────────────────┘ └─╥─┘ ░ ├─────────────────┤» + q_1: ┤ Delay(300[dt]) ├───────────────────────╫──────░─┤ Delay(1200[dt]) ├» + ├────────────────┤ ║ ░ └───────┬─┬───────┘» + q_2: ┤ Delay(300[dt]) ├───────────────────────╫──────░─────────┤M├────────» + └────────────────┘ ┌────╨────┐ ░ └╥┘ » + c: 1/════════════════════════════════════╡ c_0=0x1 ╞════════════╩═════════» + └─────────┘ 0 » + « ┌───┐ ┌────────────────┐» + «q_0: ────────────────────────────────┤ X ├───┤ Delay(300[dt]) ├» + « ┌───┐ └─╥─┘ └────────────────┘» + «q_1: ───┤ X ├──────────────────────────╫─────────────■─────────» + « └─╥─┘ ┌────────────────┐ ║ ┌─┴─┐ » + «q_2: ─────╫─────┤ Delay(300[dt]) ├─────╫───────────┤ X ├───────» + « ┌────╨────┐└────────────────┘┌────╨────┐ └───┘ » + «c: 1/╡ c_0=0x0 ╞══════════════════╡ c_0=0x0 ╞══════════════════» + « └─────────┘ └─────────┘ » + « ┌───┐ ┌────────────────┐ + «q_0: ──────┤ X ├────────────■─────┤ Delay(700[dt]) ├ + « ┌─────┴───┴──────┐ ┌─┴─┐ ├────────────────┤ + «q_1: ┤ Delay(400[dt]) ├───┤ X ├───┤ Delay(700[dt]) ├ + « ├────────────────┤ └─╥─┘ └──────┬─┬───────┘ + «q_2: ┤ Delay(300[dt]) ├─────╫────────────┤M├──────── + « └────────────────┘┌────╨────┐ └╥┘ + «c: 1/══════════════════╡ c_0=0x0 ╞════════╩═════════ + « └─────────┘ 0 + + (ALAP scheduled) duration = 3100 + ┌────────────────┐┌────────────────┐ ┌───┐ ░ ┌─────────────────┐» + q_0: ┤ Delay(100[dt]) ├┤ Delay(100[dt]) ├───┤ X ├────░─┤ Delay(1400[dt]) ├» + ├────────────────┤└────────────────┘ └─╥─┘ ░ ├─────────────────┤» + q_1: ┤ Delay(300[dt]) ├───────────────────────╫──────░─┤ Delay(1200[dt]) ├» + ├────────────────┤ ║ ░ └───────┬─┬───────┘» + q_2: ┤ Delay(300[dt]) ├───────────────────────╫──────░─────────┤M├────────» + └────────────────┘ ┌────╨────┐ ░ └╥┘ » + c: 1/════════════════════════════════════╡ c_0=0x1 ╞════════════╩═════════» + └─────────┘ 0 » + « ┌───┐ ┌────────────────┐» + «q_0: ────────────────────────────────┤ X ├───┤ Delay(300[dt]) ├» + « ┌───┐ ┌────────────────┐ └─╥─┘ └────────────────┘» + «q_1: ───┤ X ├───┤ Delay(300[dt]) ├─────╫─────────────■─────────» + « └─╥─┘ ├────────────────┤ ║ ┌─┴─┐ » + «q_2: ─────╫─────┤ Delay(600[dt]) ├─────╫───────────┤ X ├───────» + « ┌────╨────┐└────────────────┘┌────╨────┐ └───┘ » + «c: 1/╡ c_0=0x0 ╞══════════════════╡ c_0=0x0 ╞══════════════════» + « └─────────┘ └─────────┘ » + « ┌───┐ ┌────────────────┐ + «q_0: ──────┤ X ├────────────■─────┤ Delay(700[dt]) ├ + « ┌─────┴───┴──────┐ ┌─┴─┐ ├────────────────┤ + «q_1: ┤ Delay(100[dt]) ├───┤ X ├───┤ Delay(700[dt]) ├ + « └──────┬─┬───────┘ └─╥─┘ └────────────────┘ + «q_2: ───────┤M├─────────────╫─────────────────────── + « └╥┘ ┌────╨────┐ + «c: 1/════════╩═════════╡ c_0=0x0 ╞══════════════════ + « 0 └─────────┘ + + """ + qc = QuantumCircuit(3, 1) + qc.delay(100, 0) + qc.x(0).c_if(0, 1) + qc.barrier() + qc.measure(2, 0) + qc.x(1).c_if(0, 0) + qc.x(0).c_if(0, 0) + qc.delay(300, 0) + qc.cx(1, 2) + qc.x(0) + qc.cx(0, 1).c_if(0, 0) + qc.measure(2, 0) + + durations = InstructionDurations( + [("x", None, 100), ("measure", None, 1000), ("cx", None, 200)] + ) + + actual_asap = PassManager( + ASAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) + ).run(qc) + actual_alap = PassManager( + ALAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) + ).run(qc) + + expected_asap = QuantumCircuit(3, 1) + expected_asap.delay(100, 0) + expected_asap.delay(100, 0) # due to conditional latency of 200dt + expected_asap.delay(300, 1) + expected_asap.delay(300, 2) + expected_asap.x(0).c_if(0, 1) + expected_asap.barrier() + expected_asap.delay(1400, 0) + expected_asap.delay(1200, 1) + expected_asap.measure(2, 0) + expected_asap.x(1).c_if(0, 0) + expected_asap.x(0).c_if(0, 0) + expected_asap.delay(300, 0) + expected_asap.x(0) + expected_asap.delay(300, 2) + expected_asap.cx(1, 2) + expected_asap.delay(400, 1) + expected_asap.cx(0, 1).c_if(0, 0) + expected_asap.delay(700, 0) # creg is released at t0 of cx(0,1).c_if(0,0) + expected_asap.delay( + 700, 1 + ) # no creg write until 100dt. thus measure can move left by 300dt. + expected_asap.delay(300, 2) + expected_asap.measure(2, 0) + self.assertEqual(expected_asap, actual_asap) + self.assertEqual(actual_asap.duration, 3100) + + expected_alap = QuantumCircuit(3, 1) + expected_alap.delay(100, 0) + expected_alap.delay(100, 0) # due to conditional latency of 200dt + expected_alap.delay(300, 1) + expected_alap.delay(300, 2) + expected_alap.x(0).c_if(0, 1) + expected_alap.barrier() + expected_alap.delay(1400, 0) + expected_alap.delay(1200, 1) + expected_alap.measure(2, 0) + expected_alap.x(1).c_if(0, 0) + expected_alap.x(0).c_if(0, 0) + expected_alap.delay(300, 0) + expected_alap.x(0) + expected_alap.delay(300, 1) + expected_alap.delay(600, 2) + expected_alap.cx(1, 2) + expected_alap.delay(100, 1) + expected_alap.cx(0, 1).c_if(0, 0) + expected_alap.measure(2, 0) + expected_alap.delay(700, 0) + expected_alap.delay(700, 1) + self.assertEqual(expected_alap, actual_alap) + self.assertEqual(actual_alap.duration, 3100) + + def test_dag_introduces_extra_dependency_between_conditionals(self): + """Test dependency between conditional operations in the scheduling. + + In the below example circuit, the conditional x on q1 could start at time 0, + however it must be scheduled after the conditional x on q0 in ASAP scheduling. + That is because circuit model used in the transpiler passes (DAGCircuit) + interprets instructions acting on common clbits must be run in the order + given by the original circuit (QuantumCircuit). + + (input) + ┌────────────────┐ ┌───┐ + q_0: ┤ Delay(100[dt]) ├───┤ X ├─── + └─────┬───┬──────┘ └─╥─┘ + q_1: ──────┤ X ├────────────╫───── + └─╥─┘ ║ + ┌────╨────┐ ┌────╨────┐ + c: 1/═══╡ c_0=0x1 ╞════╡ c_0=0x1 ╞ + └─────────┘ └─────────┘ + + (ASAP scheduled) + ┌────────────────┐ ┌───┐ + q_0: ┤ Delay(100[dt]) ├───┤ X ├────────────── + ├────────────────┤ └─╥─┘ ┌───┐ + q_1: ┤ Delay(100[dt]) ├─────╫────────┤ X ├─── + └────────────────┘ ║ └─╥─┘ + ┌────╨────┐┌────╨────┐ + c: 1/══════════════════╡ c_0=0x1 ╞╡ c_0=0x1 ╞ + └─────────┘└─────────┘ + """ + qc = QuantumCircuit(2, 1) + qc.delay(100, 0) + qc.x(0).c_if(0, True) + qc.x(1).c_if(0, True) + + durations = InstructionDurations([("x", None, 160)]) + pm = PassManager(ASAPSchedule(durations)) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.delay(100, 0) + expected.delay(100, 1) # due to extra dependency on clbits + expected.x(0).c_if(0, True) + expected.x(1).c_if(0, True) + + self.assertEqual(expected, scheduled) + + @data(ALAPSchedule, ASAPSchedule) + def test_respect_target_instruction_constraints(self, schedule_pass): + """Test if ALAP/ASAP does not pad delays for qubits that do not support delay instructions. + See: https://github.com/Qiskit/qiskit-terra/issues/9993 + """ + target = Target(dt=1) + target.add_instruction(XGate(), {(1,): InstructionProperties(duration=200)}) + # delays are not supported + + qc = QuantumCircuit(2) + qc.x(1) + + pm = PassManager(schedule_pass(target=target)) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2) + expected.x(1) + # no delay on qubit 0 + + self.assertEqual(expected, scheduled) + + def test_dd_respect_target_instruction_constraints(self): + """Test if DD pass does not pad delays for qubits that do not support delay instructions + and does not insert DD gates for qubits that do not support necessary gates. + See: https://github.com/Qiskit/qiskit-terra/issues/9993 + """ + qc = QuantumCircuit(3) + qc.cx(0, 1) + qc.cx(1, 2) + + target = Target(dt=1) + # Y is partially supported (not supported on qubit 2) + target.add_instruction( + XGate(), {(q,): InstructionProperties(duration=100) for q in range(2)} + ) + target.add_instruction( + CXGate(), + { + (0, 1): InstructionProperties(duration=1000), + (1, 2): InstructionProperties(duration=1000), + }, + ) + # delays are not supported + + # No DD instructions nor delays are padded due to no delay support in the target + pm_xx = PassManager( + [ + ALAPSchedule(target=target), + DynamicalDecoupling(durations=None, dd_sequence=[XGate(), XGate()], target=target), + ] + ) + scheduled = pm_xx.run(qc) + self.assertEqual(qc, scheduled) + + # Fails since Y is not supported in the target + with self.assertRaises(TranspilerError): + PassManager( + [ + ALAPSchedule(target=target), + DynamicalDecoupling( + durations=None, + dd_sequence=[XGate(), YGate(), XGate(), YGate()], + target=target, + ), + ] + ) + + # Add delay support to the target + target.add_instruction(Delay(Parameter("t")), {(q,): None for q in range(3)}) + # No error but no DD on qubit 2 (just delay is padded) since X is not supported on it + scheduled = pm_xx.run(qc) + + expected = QuantumCircuit(3) + expected.delay(1000, [2]) + expected.cx(0, 1) + expected.cx(1, 2) + expected.delay(200, [0]) + expected.x([0]) + expected.delay(400, [0]) + expected.x([0]) + expected.delay(200, [0]) + self.assertEqual(expected, scheduled) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/transpiler/test_dynamical_decoupling.py b/test/python/transpiler/test_dynamical_decoupling.py index b74091dd76f8..e53fb9b0226e 100644 --- a/test/python/transpiler/test_dynamical_decoupling.py +++ b/test/python/transpiler/test_dynamical_decoupling.py @@ -1031,6 +1031,25 @@ def test_respect_target_instruction_constraints(self): expected.delay(200, [0]) self.assertEqual(expected, scheduled) + def test_paramaterized_global_phase(self): + """Test paramaterized global phase in DD circuit. + See:https://github.com/Qiskit/qiskit-terra/issues/10569 + """ + dd_sequence = [XGate(), YGate()] * 2 + qc = QuantumCircuit(1, 1) + qc.h(0) + qc.delay(1700, 0) + qc.y(0) + qc.global_phase = Parameter("a") + pm = PassManager( + [ + ALAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling(self.durations, dd_sequence), + ] + ) + + self.assertEqual(qc.global_phase + np.pi, pm.run(qc).global_phase) + if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_inverse_cancellation.py b/test/python/transpiler/test_inverse_cancellation.py index 686b01dd5d7c..c39b531c1b0f 100644 --- a/test/python/transpiler/test_inverse_cancellation.py +++ b/test/python/transpiler/test_inverse_cancellation.py @@ -22,7 +22,17 @@ from qiskit.transpiler.passes import InverseCancellation from qiskit.transpiler import PassManager from qiskit.test import QiskitTestCase -from qiskit.circuit.library import RXGate, HGate, CXGate, PhaseGate, XGate, TGate, TdgGate +from qiskit.circuit.library import ( + RXGate, + HGate, + CXGate, + PhaseGate, + XGate, + TGate, + TdgGate, + CZGate, + RZGate, +) class TestInverseCancellation(QiskitTestCase): @@ -271,6 +281,111 @@ def test_cx_do_not_wrongly_cancel(self): self.assertIn("cx", gates_after) self.assertEqual(gates_after["cx"], 2) + def test_no_gates_to_cancel(self): + """Test when there are no gates to cancel.""" + qc = QuantumCircuit(2) + qc.cx(0, 1) + qc.cx(1, 0) + inverse_pass = InverseCancellation([HGate()]) + new_circ = inverse_pass(qc) + self.assertEqual(qc, new_circ) + + def test_some_cancel_rules_to_cancel(self): + """Test when there are some gates to cancel.""" + qc = QuantumCircuit(2) + qc.cx(0, 1) + qc.cx(1, 0) + qc.h(0) + qc.h(0) + inverse_pass = InverseCancellation([HGate(), CXGate(), CZGate()]) + new_circ = inverse_pass(qc) + self.assertNotIn("h", new_circ.count_ops()) + + def test_no_inverse_pairs(self): + """Test when there are no inverse pairs to cancel.""" + qc = QuantumCircuit(1) + qc.s(0) + qc.sdg(0) + inverse_pass = InverseCancellation([(TGate(), TdgGate())]) + new_circ = inverse_pass(qc) + self.assertEqual(qc, new_circ) + + def test_some_inverse_pairs(self): + """Test when there are some but not all inverse pairs to cancel.""" + qc = QuantumCircuit(1) + qc.s(0) + qc.sdg(0) + qc.t(0) + qc.tdg(0) + inverse_pass = InverseCancellation([(TGate(), TdgGate())]) + new_circ = inverse_pass(qc) + self.assertNotIn("t", new_circ.count_ops()) + self.assertNotIn("tdg", new_circ.count_ops()) + + def test_some_inverse_and_cancelled(self): + """Test when there are some but not all pairs to cancel.""" + qc = QuantumCircuit(2) + qc.s(0) + qc.sdg(0) + qc.t(0) + qc.tdg(0) + qc.cx(0, 1) + qc.cx(1, 0) + qc.h(0) + qc.h(0) + inverse_pass = InverseCancellation([HGate(), CXGate(), CZGate(), (TGate(), TdgGate())]) + new_circ = inverse_pass(qc) + self.assertNotIn("h", new_circ.count_ops()) + self.assertNotIn("t", new_circ.count_ops()) + self.assertNotIn("tdg", new_circ.count_ops()) + + def test_half_of_an_inverse_pair(self): + """Test that half of an inverse pair doesn't do anything.""" + qc = QuantumCircuit(1) + qc.t(0) + qc.t(0) + qc.t(0) + inverse_pass = InverseCancellation([(TGate(), TdgGate())]) + new_circ = inverse_pass(qc) + self.assertEqual(new_circ, qc) + + def test_parameterized_self_inverse(self): + """Test that a parameterized self inverse gate cancels correctly.""" + qc = QuantumCircuit(1) + qc.rz(0, 0) + qc.rz(0, 0) + inverse_pass = InverseCancellation([RZGate(0)]) + new_circ = inverse_pass(qc) + self.assertEqual(new_circ, QuantumCircuit(1)) + + def test_parameterized_self_inverse_not_equal_parameter(self): + """Test that a parameterized self inverse gate doesn't cancel incorrectly.""" + qc = QuantumCircuit(1) + qc.rz(0, 0) + qc.rz(3.14159, 0) + qc.rz(0, 0) + inverse_pass = InverseCancellation([RZGate(0)]) + new_circ = inverse_pass(qc) + self.assertEqual(new_circ, qc) + + def test_controlled_gate_open_control_does_not_cancel(self): + """Test that a controlled gate with an open control doesn't cancel.""" + qc = QuantumCircuit(2) + qc.cx(0, 1) + qc.cx(0, 1, ctrl_state=0) + inverse_pass = InverseCancellation([CXGate()]) + new_circ = inverse_pass(qc) + self.assertEqual(new_circ, qc) + + def test_backwards_pair(self): + """Test a backwards inverse pair works.""" + qc = QuantumCircuit(1) + qc.tdg(0) + qc.t(0) + inverse_pass = InverseCancellation([(TGate(), TdgGate())]) + new_circ = inverse_pass(qc) + self.assertEqual(new_circ, QuantumCircuit(1)) + if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index c1df58eb03cf..d37d5c2cbaad 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1272,7 +1272,7 @@ def test_size_optimization(self, level): @ddt -class TestGeenratePresetPassManagers(QiskitTestCase): +class TestGeneratePresetPassManagers(QiskitTestCase): """Test generate_preset_pass_manager function.""" @data(0, 1, 2, 3) @@ -1445,6 +1445,37 @@ def get_translation_stage_plugin(self): ] self.assertIn("RemoveResetInZeroState", post_translation_pass_list) + def test_generate_preset_pass_manager_with_list_coupling_map(self): + """Test that generate_preset_pass_manager can handle list-based coupling_map.""" + + # Define the coupling map as a list + coupling_map_list = [[0, 1]] + coupling_map_object = CouplingMap(coupling_map_list) + + # Circuit that doesn't fit in the coupling map + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.cx(1, 0) + qc.measure_all() + + pm_list = generate_preset_pass_manager( + optimization_level=0, coupling_map=coupling_map_list, seed_transpiler=42 + ) + pm_object = generate_preset_pass_manager( + optimization_level=0, coupling_map=coupling_map_object, seed_transpiler=42 + ) + + transpiled_circuit_list = pm_list.run(qc) + transpiled_circuit_object = pm_object.run(qc) + + # Check if both are instances of PassManager + self.assertIsInstance(pm_list, PassManager) + self.assertIsInstance(pm_object, PassManager) + + # Ensure the DAGs from both methods are identical + self.assertEqual(transpiled_circuit_list, transpiled_circuit_object) + @ddt class TestIntegrationControlFlow(QiskitTestCase): diff --git a/test/python/transpiler/test_vf2_post_layout.py b/test/python/transpiler/test_vf2_post_layout.py index d06e46a229bd..da0dbb274c2b 100644 --- a/test/python/transpiler/test_vf2_post_layout.py +++ b/test/python/transpiler/test_vf2_post_layout.py @@ -108,7 +108,7 @@ def test_empty_circuit(self): vf2_pass.run(circuit_to_dag(qc)) self.assertEqual( vf2_pass.property_set["VF2PostLayout_stop_reason"], - VF2PostLayoutStopReason.NO_SOLUTION_FOUND, + VF2PostLayoutStopReason.NO_BETTER_SOLUTION_FOUND, ) def test_empty_circuit_v2(self): @@ -119,7 +119,7 @@ def test_empty_circuit_v2(self): vf2_pass.run(circuit_to_dag(qc)) self.assertEqual( vf2_pass.property_set["VF2PostLayout_stop_reason"], - VF2PostLayoutStopReason.NO_SOLUTION_FOUND, + VF2PostLayoutStopReason.NO_BETTER_SOLUTION_FOUND, ) def test_skip_3q_circuit(self): @@ -389,6 +389,46 @@ def test_target_some_error(self): # No layout selected because nothing will beat initial layout self.assertNotIn("post_layout", vf2_pass.property_set) + def test_trivial_layout_is_best(self): + """Test that vf2postlayout reports no better solution if the trivial layout is the best layout""" + n_qubits = 4 + trivial_target = Target() + trivial_target.add_instruction( + CXGate(), {(i, i + 1): InstructionProperties(error=0.001) for i in range(n_qubits - 1)} + ) + + circuit = QuantumCircuit(n_qubits) + circuit.cx(0, 1) + circuit.cx(1, 2) + + vf2_pass = VF2PostLayout(target=trivial_target, seed=self.seed, strict_direction=False) + dag = circuit_to_dag(circuit) + vf2_pass.run(dag) + self.assertEqual( + vf2_pass.property_set["VF2PostLayout_stop_reason"], + VF2PostLayoutStopReason.NO_BETTER_SOLUTION_FOUND, + ) + + def test_last_qubits_best(self): + """Test that vf2postlayout determines the best layout when the last qubits have least error""" + n_qubits = 4 + target_last_qubits_best = Target() + target_last_qubits_best.add_instruction( + CXGate(), + {(i, i + 1): InstructionProperties(error=10**-i) for i in range(n_qubits - 1)}, + ) + + circuit = QuantumCircuit(n_qubits) + circuit.cx(0, 1) + circuit.cx(1, 2) + + vf2_pass = VF2PostLayout( + target=target_last_qubits_best, seed=self.seed, strict_direction=False + ) + dag = circuit_to_dag(circuit) + vf2_pass.run(dag) + self.assertLayout(dag, target_last_qubits_best.build_coupling_map(), vf2_pass.property_set) + class TestVF2PostLayoutScoring(QiskitTestCase): """Test scoring heuristic function for VF2PostLayout.""" @@ -480,7 +520,7 @@ def test_empty_circuit(self): vf2_pass.run(circuit_to_dag(qc)) self.assertEqual( vf2_pass.property_set["VF2PostLayout_stop_reason"], - VF2PostLayoutStopReason.NO_SOLUTION_FOUND, + VF2PostLayoutStopReason.NO_BETTER_SOLUTION_FOUND, ) def test_empty_circuit_v2(self): @@ -491,7 +531,7 @@ def test_empty_circuit_v2(self): vf2_pass.run(circuit_to_dag(qc)) self.assertEqual( vf2_pass.property_set["VF2PostLayout_stop_reason"], - VF2PostLayoutStopReason.NO_SOLUTION_FOUND, + VF2PostLayoutStopReason.NO_BETTER_SOLUTION_FOUND, ) def test_skip_3q_circuit(self): diff --git a/test/python/utils/test_deprecation.py b/test/python/utils/test_deprecation.py index ccfd226b56ca..56024d2e2e3a 100644 --- a/test/python/utils/test_deprecation.py +++ b/test/python/utils/test_deprecation.py @@ -72,7 +72,7 @@ def test_deprecate_func_docstring(self) -> None: f"""\ .. deprecated:: 9.99 - The function ``{__name__}._deprecated_func()`` is deprecated as of qiskit-terra \ + The function ``{__name__}._deprecated_func()`` is deprecated as of qiskit \ 9.99. It will be removed in 2 releases. Instead, use new_func(). """ ), @@ -83,7 +83,7 @@ def test_deprecate_func_docstring(self) -> None: f"""\ .. deprecated:: 9.99_pending - The class ``{__name__}._Foo`` is pending deprecation as of qiskit-terra 9.99. It \ + The class ``{__name__}._Foo`` is pending deprecation as of qiskit 9.99. It \ will be marked deprecated in a future release, and then removed no earlier than 3 months after \ the release date. """ @@ -96,7 +96,7 @@ def test_deprecate_func_docstring(self) -> None: Method. .. deprecated:: 9.99 - The method ``{__name__}._Foo.my_method()`` is deprecated as of qiskit-terra \ + The method ``{__name__}._Foo.my_method()`` is deprecated as of qiskit \ 9.99. It will be removed no earlier than 3 months after the release date. Stop using this! """ ), @@ -108,7 +108,7 @@ def test_deprecate_func_docstring(self) -> None: Property. .. deprecated:: 9.99 - The property ``{__name__}._Foo.my_property`` is deprecated as of qiskit-terra \ + The property ``{__name__}._Foo.my_property`` is deprecated as of qiskit \ 9.99. It will be removed no earlier than 3 months after the release date. """ ), @@ -143,22 +143,22 @@ def my_func() -> None: .. deprecated:: 9.99 ``{__name__}.{my_func.__qualname__}()``'s argument ``arg4`` is deprecated as of \ -qiskit-terra 9.99. It will be removed no earlier than 3 months after the release date. Instead, \ +qiskit 9.99. It will be removed no earlier than 3 months after the release date. Instead, \ use foo. .. deprecated:: 9.99 - Using the argument arg3 is deprecated as of qiskit-terra 9.99. It will be \ + Using the argument arg3 is deprecated as of qiskit 9.99. It will be \ removed no earlier than 3 months after the release date. Instead, use the argument ``new_arg3``, \ which behaves identically. .. deprecated:: 9.99_pending ``{__name__}.{my_func.__qualname__}()``'s argument ``arg2`` is pending \ -deprecation as of qiskit-terra 9.99. It will be marked deprecated in a future release, and then \ +deprecation as of qiskit 9.99. It will be marked deprecated in a future release, and then \ removed no earlier than 3 months after the release date. .. deprecated:: 9.99 ``{__name__}.{my_func.__qualname__}()``'s argument ``arg1`` is deprecated as of \ -qiskit-terra 9.99. It will be removed in 2 releases. +qiskit 9.99. It will be removed in 2 releases. """ ), ) diff --git a/test/python/visualization/pulse_v2/test_generators.py b/test/python/visualization/pulse_v2/test_generators.py index 9c9c5110f8bc..35f1ad16c6dc 100644 --- a/test/python/visualization/pulse_v2/test_generators.py +++ b/test/python/visualization/pulse_v2/test_generators.py @@ -83,7 +83,7 @@ def test_consecutive_index_tiny_diff(self): def test_parse_waveform(self): """Test helper function that parse waveform with Waveform instance.""" - test_pulse = pulse.library.gaussian(10, 0.1, 3) + test_pulse = pulse.library.Gaussian(10, 0.1, 3).get_waveform() inst = pulse.Play(test_pulse, pulse.DriveChannel(0)) inst_data = create_instruction(inst, 0, 0, 10, 0.1) diff --git a/test/python/visualization/references/dag_dep.png b/test/python/visualization/references/dag_dep.png new file mode 100644 index 000000000000..8faed6008357 Binary files /dev/null and b/test/python/visualization/references/dag_dep.png differ diff --git a/test/python/visualization/references/dag_no_reg.png b/test/python/visualization/references/dag_no_reg.png index 48f7324b5230..1b1860929506 100644 Binary files a/test/python/visualization/references/dag_no_reg.png and b/test/python/visualization/references/dag_no_reg.png differ diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py index 1a38a689fd85..2db1cd26949e 100644 --- a/test/python/visualization/test_circuit_text_drawer.py +++ b/test/python/visualization/test_circuit_text_drawer.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""`_text_circuit_drawer` draws a circuit in ascii art""" +"""circuit_drawer with output="text" draws a circuit in ascii art""" import pathlib import os @@ -27,9 +27,9 @@ from qiskit.quantum_info.random import random_unitary from qiskit.test import QiskitTestCase from qiskit.transpiler.layout import Layout, TranspileLayout +from qiskit.visualization.circuit.circuit_visualization import _text_circuit_drawer from qiskit.visualization import circuit_drawer from qiskit.visualization.circuit import text as elements -from qiskit.visualization.circuit.circuit_visualization import _text_circuit_drawer from qiskit.providers.fake_provider import FakeBelemV2 from qiskit.circuit.classical import expr from qiskit.circuit.library import ( @@ -112,7 +112,7 @@ def test_text_empty(self): """The empty circuit.""" expected = "" circuit = QuantumCircuit() - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_pager(self): """The pager breaks the circuit when the drawing does not fit in the console.""" @@ -153,7 +153,9 @@ def test_text_pager(self): circuit.measure(qr[0], cr[0]) circuit.cx(qr[1], qr[0]) circuit.cx(qr[0], qr[1]) - self.assertEqual(str(_text_circuit_drawer(circuit, fold=20)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, fold=20)), expected + ) def test_text_no_pager(self): """The pager can be disable.""" @@ -161,7 +163,9 @@ def test_text_no_pager(self): circuit = QuantumCircuit(qr) for _ in range(100): circuit.h(qr[0]) - amount_of_lines = str(_text_circuit_drawer(circuit, fold=-1)).count("\n") + amount_of_lines = str( + circuit_drawer(circuit, output="text", initial_state=True, fold=-1) + ).count("\n") self.assertEqual(amount_of_lines, 2) @@ -188,7 +192,10 @@ def test_text_measure_cregbundle(self): cr = ClassicalRegister(3, "c") circuit = QuantumCircuit(qr, cr) circuit.measure(qr, cr) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_measure_cregbundle_2(self): """The measure operator, using 2 classical registers with cregbundle=True.""" @@ -212,7 +219,10 @@ def test_text_measure_cregbundle_2(self): circuit = QuantumCircuit(qr, cr_a, cr_b) circuit.measure(qr[0], cr_a[0]) circuit.measure(qr[1], cr_b[0]) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_measure_1(self): """The measure operator, using 3-bit-length registers.""" @@ -238,7 +248,10 @@ def test_text_measure_1(self): cr = ClassicalRegister(3, "c") circuit = QuantumCircuit(qr, cr) circuit.measure(qr, cr) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_measure_1_reverse_bits(self): """The measure operator, using 3-bit-length registers, with reverse_bits""" @@ -260,7 +273,10 @@ def test_text_measure_1_reverse_bits(self): cr = ClassicalRegister(3, "c") circuit = QuantumCircuit(qr, cr) circuit.measure(qr, cr) - self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, reverse_bits=True)), + expected, + ) def test_text_measure_2(self): """The measure operator, using some registers.""" @@ -288,7 +304,7 @@ def test_text_measure_2(self): cr2 = ClassicalRegister(2, "c2") circuit = QuantumCircuit(qr1, qr2, cr1, cr2) circuit.measure(qr2, cr2) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_measure_2_reverse_bits(self): """The measure operator, using some registers, with reverse_bits""" @@ -316,7 +332,10 @@ def test_text_measure_2_reverse_bits(self): cr2 = ClassicalRegister(2, "c2") circuit = QuantumCircuit(qr1, qr2, cr1, cr2) circuit.measure(qr2, cr2) - self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, reverse_bits=True)), + expected, + ) def test_wire_order(self): """Test the wire_order option""" @@ -356,8 +375,12 @@ def test_wire_order(self): circuit.x(3).c_if(cr, 10) self.assertEqual( str( - _text_circuit_drawer( - circuit, cregbundle=False, wire_order=[2, 1, 3, 0, 6, 8, 9, 5, 4, 7] + circuit_drawer( + circuit, + output="text", + initial_state=True, + cregbundle=False, + wire_order=[2, 1, 3, 0, 6, 8, 9, 5, 4, 7], ) ), expected, @@ -383,7 +406,7 @@ def test_text_swap(self): qr2 = QuantumRegister(2, "q2") circuit = QuantumCircuit(qr1, qr2) circuit.swap(qr1, qr2) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_swap_reverse_bits(self): """Swap drawing with reverse_bits.""" @@ -405,7 +428,10 @@ def test_text_swap_reverse_bits(self): qr2 = QuantumRegister(2, "q2") circuit = QuantumCircuit(qr1, qr2) circuit.swap(qr1, qr2) - self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, reverse_bits=True)), + expected, + ) def test_text_reverse_bits_read_from_config(self): """Swap drawing with reverse_bits set in the configuration file.""" @@ -473,7 +499,7 @@ def test_text_cswap(self): circuit.cswap(qr[0], qr[1], qr[2]) circuit.cswap(qr[1], qr[0], qr[2]) circuit.cswap(qr[2], qr[1], qr[0]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_cswap_reverse_bits(self): """CSwap drawing with reverse_bits.""" @@ -494,7 +520,10 @@ def test_text_cswap_reverse_bits(self): circuit.cswap(qr[0], qr[1], qr[2]) circuit.cswap(qr[1], qr[0], qr[2]) circuit.cswap(qr[2], qr[1], qr[0]) - self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, reverse_bits=True)), + expected, + ) def test_text_cu3(self): """cu3 drawing.""" @@ -514,7 +543,7 @@ def test_text_cu3(self): circuit = QuantumCircuit(qr) circuit.append(CU3Gate(pi / 2, pi / 2, pi / 2), [qr[0], qr[1]]) circuit.append(CU3Gate(pi / 2, pi / 2, pi / 2), [qr[2], qr[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_cu3_reverse_bits(self): """cu3 drawing with reverse_bits""" @@ -534,7 +563,10 @@ def test_text_cu3_reverse_bits(self): circuit = QuantumCircuit(qr) circuit.append(CU3Gate(pi / 2, pi / 2, pi / 2), [qr[0], qr[1]]) circuit.append(CU3Gate(pi / 2, pi / 2, pi / 2), [qr[2], qr[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, reverse_bits=True)), + expected, + ) def test_text_crz(self): """crz drawing.""" @@ -553,7 +585,7 @@ def test_text_crz(self): circuit = QuantumCircuit(qr) circuit.crz(pi / 2, qr[0], qr[1]) circuit.crz(pi / 2, qr[2], qr[0]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_cry(self): """cry drawing.""" @@ -572,7 +604,7 @@ def test_text_cry(self): circuit = QuantumCircuit(qr) circuit.cry(pi / 2, qr[0], qr[1]) circuit.cry(pi / 2, qr[2], qr[0]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_crx(self): """crx drawing.""" @@ -591,7 +623,7 @@ def test_text_crx(self): circuit = QuantumCircuit(qr) circuit.crx(pi / 2, qr[0], qr[1]) circuit.crx(pi / 2, qr[2], qr[0]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_cx(self): """cx drawing.""" @@ -610,7 +642,7 @@ def test_text_cx(self): circuit = QuantumCircuit(qr) circuit.cx(qr[0], qr[1]) circuit.cx(qr[2], qr[0]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_cy(self): """cy drawing.""" @@ -629,7 +661,7 @@ def test_text_cy(self): circuit = QuantumCircuit(qr) circuit.cy(qr[0], qr[1]) circuit.cy(qr[2], qr[0]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_cz(self): """cz drawing.""" @@ -648,7 +680,7 @@ def test_text_cz(self): circuit = QuantumCircuit(qr) circuit.cz(qr[0], qr[1]) circuit.cz(qr[2], qr[0]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_ch(self): """ch drawing.""" @@ -667,7 +699,7 @@ def test_text_ch(self): circuit = QuantumCircuit(qr) circuit.ch(qr[0], qr[1]) circuit.ch(qr[2], qr[0]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_rzz(self): """rzz drawing. See #1957""" @@ -686,7 +718,7 @@ def test_text_rzz(self): circuit = QuantumCircuit(qr) circuit.rzz(0, qr[0], qr[1]) circuit.rzz(pi / 2, qr[2], qr[1]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_cu1(self): """cu1 drawing.""" @@ -705,7 +737,7 @@ def test_text_cu1(self): circuit = QuantumCircuit(qr) circuit.append(CU1Gate(pi / 2), [qr[0], qr[1]]) circuit.append(CU1Gate(pi / 2), [qr[2], qr[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_cp(self): """cp drawing.""" @@ -724,7 +756,7 @@ def test_text_cp(self): circuit = QuantumCircuit(qr) circuit.append(CPhaseGate(pi / 2), [qr[0], qr[1]]) circuit.append(CPhaseGate(pi / 2), [qr[2], qr[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_cu1_condition(self): """Test cu1 with condition""" @@ -745,7 +777,7 @@ def test_text_cu1_condition(self): cr = ClassicalRegister(3, "c") circuit = QuantumCircuit(qr, cr) circuit.append(CU1Gate(pi / 2), [qr[0], qr[1]]).c_if(cr[1], 1) - self.assertEqual(str(_text_circuit_drawer(circuit, initial_state=False)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=False)), expected) def test_text_rzz_condition(self): """Test rzz with condition""" @@ -766,7 +798,7 @@ def test_text_rzz_condition(self): cr = ClassicalRegister(3, "c") circuit = QuantumCircuit(qr, cr) circuit.append(RZZGate(pi / 2), [qr[0], qr[1]]).c_if(cr[1], 1) - self.assertEqual(str(_text_circuit_drawer(circuit, initial_state=False)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=False)), expected) def test_text_cp_condition(self): """Test cp with condition""" @@ -787,7 +819,7 @@ def test_text_cp_condition(self): cr = ClassicalRegister(3, "c") circuit = QuantumCircuit(qr, cr) circuit.append(CPhaseGate(pi / 2), [qr[0], qr[1]]).c_if(cr[1], 1) - self.assertEqual(str(_text_circuit_drawer(circuit, initial_state=False)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=False)), expected) def test_text_cu1_reverse_bits(self): """cu1 drawing with reverse_bits""" @@ -806,7 +838,10 @@ def test_text_cu1_reverse_bits(self): circuit = QuantumCircuit(qr) circuit.append(CU1Gate(pi / 2), [qr[0], qr[1]]) circuit.append(CU1Gate(pi / 2), [qr[2], qr[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, reverse_bits=True)), + expected, + ) def test_text_ccx(self): """cx drawing.""" @@ -826,7 +861,7 @@ def test_text_ccx(self): circuit.ccx(qr[0], qr[1], qr[2]) circuit.ccx(qr[2], qr[0], qr[1]) circuit.ccx(qr[2], qr[1], qr[0]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_reset(self): """Reset drawing.""" @@ -849,7 +884,7 @@ def test_text_reset(self): circuit = QuantumCircuit(qr1, qr2) circuit.reset(qr1) circuit.reset(qr2[1]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_single_gate(self): """Single Qbit gate drawing.""" @@ -872,7 +907,7 @@ def test_text_single_gate(self): circuit = QuantumCircuit(qr1, qr2) circuit.h(qr1) circuit.h(qr2[1]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_id(self): """Id drawing.""" @@ -895,7 +930,7 @@ def test_text_id(self): circuit = QuantumCircuit(qr1, qr2) circuit.id(qr1) circuit.id(qr2[1]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_barrier(self): """Barrier drawing.""" @@ -918,7 +953,7 @@ def test_text_barrier(self): circuit = QuantumCircuit(qr1, qr2) circuit.barrier(qr1) circuit.barrier(qr2[1]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_no_barriers(self): """Drawing without plotbarriers.""" @@ -943,7 +978,10 @@ def test_text_no_barriers(self): circuit.barrier(qr1) circuit.barrier(qr2[1]) circuit.h(qr2) - self.assertEqual(str(_text_circuit_drawer(circuit, plot_barriers=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, plot_barriers=False)), + expected, + ) def test_text_measure_html(self): """The measure operator. HTML representation.""" @@ -965,7 +1003,9 @@ def test_text_measure_html(self): cr = ClassicalRegister(1, "c") circuit = QuantumCircuit(qr, cr) circuit.measure(qr, cr) - self.assertEqual(_text_circuit_drawer(circuit)._repr_html_(), expected) + self.assertEqual( + circuit_drawer(circuit, output="text", initial_state=True)._repr_html_(), expected + ) def test_text_repr(self): """The measure operator. repr.""" @@ -982,7 +1022,9 @@ def test_text_repr(self): cr = ClassicalRegister(1, "c") circuit = QuantumCircuit(qr, cr) circuit.measure(qr, cr) - self.assertEqual(_text_circuit_drawer(circuit).__repr__(), expected) + self.assertEqual( + circuit_drawer(circuit, output="text", initial_state=True).__repr__(), expected + ) def test_text_justify_left(self): """Drawing with left justify""" @@ -1004,7 +1046,10 @@ def test_text_justify_left(self): circuit.x(qr1[0]) circuit.h(qr1[1]) circuit.measure(qr1[1], cr1[1]) - self.assertEqual(str(_text_circuit_drawer(circuit, justify="left")), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, justify="left")), + expected, + ) def test_text_justify_right(self): """Drawing with right justify""" @@ -1026,7 +1071,10 @@ def test_text_justify_right(self): circuit.x(qr1[0]) circuit.h(qr1[1]) circuit.measure(qr1[1], cr1[1]) - self.assertEqual(str(_text_circuit_drawer(circuit, justify="right")), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, justify="right")), + expected, + ) def test_text_justify_none(self): """Drawing with none justify""" @@ -1048,7 +1096,10 @@ def test_text_justify_none(self): circuit.x(qr1[0]) circuit.h(qr1[1]) circuit.measure(qr1[1], cr1[1]) - self.assertEqual(str(_text_circuit_drawer(circuit, justify="none")), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, justify="none")), + expected, + ) def test_text_justify_left_barrier(self): """Left justify respects barriers""" @@ -1067,7 +1118,10 @@ def test_text_justify_left_barrier(self): circuit.h(qr1[0]) circuit.barrier(qr1) circuit.h(qr1[1]) - self.assertEqual(str(_text_circuit_drawer(circuit, justify="left")), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, justify="left")), + expected, + ) def test_text_justify_right_barrier(self): """Right justify respects barriers""" @@ -1086,7 +1140,10 @@ def test_text_justify_right_barrier(self): circuit.h(qr1[0]) circuit.barrier(qr1) circuit.h(qr1[1]) - self.assertEqual(str(_text_circuit_drawer(circuit, justify="right")), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, justify="right")), + expected, + ) def test_text_barrier_label(self): """Show barrier label""" @@ -1108,7 +1165,7 @@ def test_text_barrier_label(self): circuit.y(0) circuit.x(1) circuit.barrier(label="End Y/X") - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_overlap_cx(self): """Overlapping CX gates are drawn not overlapping""" @@ -1130,7 +1187,10 @@ def test_text_overlap_cx(self): circuit = QuantumCircuit(qr1) circuit.cx(qr1[0], qr1[3]) circuit.cx(qr1[1], qr1[2]) - self.assertEqual(str(_text_circuit_drawer(circuit, justify="left")), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, justify="left")), + expected, + ) def test_text_overlap_measure(self): """Measure is drawn not overlapping""" @@ -1151,7 +1211,10 @@ def test_text_overlap_measure(self): circuit = QuantumCircuit(qr1, cr1) circuit.measure(qr1[0], cr1[0]) circuit.x(qr1[1]) - self.assertEqual(str(_text_circuit_drawer(circuit, justify="left")), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, justify="left")), + expected, + ) def test_text_overlap_swap(self): """Swap is drawn in 2 separate columns""" @@ -1173,7 +1236,10 @@ def test_text_overlap_swap(self): qr2 = QuantumRegister(2, "q2") circuit = QuantumCircuit(qr1, qr2) circuit.swap(qr1, qr2) - self.assertEqual(str(_text_circuit_drawer(circuit, justify="left")), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, justify="left")), + expected, + ) def test_text_justify_right_measure_resize(self): """Measure gate can resize if necessary""" @@ -1194,7 +1260,10 @@ def test_text_justify_right_measure_resize(self): circuit = QuantumCircuit(qr1, cr1) circuit.x(qr1[0]) circuit.measure(qr1[1], cr1[1]) - self.assertEqual(str(_text_circuit_drawer(circuit, justify="right")), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, justify="right")), + expected, + ) def test_text_box_length(self): """The length of boxes is independent of other boxes in the layer @@ -1216,7 +1285,7 @@ def test_text_box_length(self): circuit.h(qr[0]) circuit.h(qr[0]) circuit.rz(0.0000001, qr[2]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_spacing_2378(self): """Small gates in the same layer as long gates. @@ -1236,7 +1305,7 @@ def test_text_spacing_2378(self): circuit = QuantumCircuit(qr) circuit.swap(qr[0], qr[1]) circuit.rz(11111, qr[2]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) @unittest.skipUnless(HAS_TWEEDLEDUM, "Tweedledum is required for these tests.") def test_text_synth_no_registerless(self): @@ -1261,7 +1330,7 @@ def grover_oracle(a: Int1, b: Int1, c: Int1) -> Int1: return a and b and not c circuit = grover_oracle.synth(registerless=False) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) class TestTextDrawerLabels(QiskitTestCase): @@ -1277,7 +1346,7 @@ def test_label(self): circuit = QuantumCircuit(1) circuit.append(HGate(label="an H gate"), [0]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_gate_with_label(self): """Test a controlled gate-with-a-label.""" @@ -1293,7 +1362,7 @@ def test_controlled_gate_with_label(self): circuit = QuantumCircuit(2) circuit.append(HGate(label="an H gate").control(1), [0, 1]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_label_on_controlled_gate(self): """Test a controlled gate with a label (as a as a whole).""" @@ -1310,7 +1379,7 @@ def test_label_on_controlled_gate(self): circuit = QuantumCircuit(2) circuit.append(HGate().control(1, label="a controlled H gate"), [0, 1]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_rzz_on_wide_layer(self): """Test a labeled gate (RZZ) in a wide layer. @@ -1330,7 +1399,7 @@ def test_rzz_on_wide_layer(self): circuit.rzz(pi / 2, 0, 1) circuit.x(2, label="This is a really long long long box") - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_cu1_on_wide_layer(self): """Test a labeled gate (CU1) in a wide layer. @@ -1350,7 +1419,7 @@ def test_cu1_on_wide_layer(self): circuit.append(CU1Gate(pi / 2), [0, 1]) circuit.x(2, label="This is a really long long long box") - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) class TestTextDrawerMultiQGates(QiskitTestCase): @@ -1374,7 +1443,10 @@ def test_2Qgate(self): my_gate2 = Gate(name="twoQ", num_qubits=2, params=[], label="twoQ") circuit.append(my_gate2, [qr[0], qr[1]]) - self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, reverse_bits=True)), + expected, + ) def test_2Qgate_cross_wires(self): """2Q no params, with cross wires""" @@ -1394,7 +1466,10 @@ def test_2Qgate_cross_wires(self): my_gate2 = Gate(name="twoQ", num_qubits=2, params=[], label="twoQ") circuit.append(my_gate2, [qr[1], qr[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, reverse_bits=True)), + expected, + ) def test_3Qgate_cross_wires(self): """3Q no params, with cross wires""" @@ -1416,7 +1491,10 @@ def test_3Qgate_cross_wires(self): my_gate3 = Gate(name="threeQ", num_qubits=3, params=[], label="threeQ") circuit.append(my_gate3, [qr[1], qr[2], qr[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, reverse_bits=True)), + expected, + ) def test_2Qgate_nottogether(self): """2Q that are not together""" @@ -1437,7 +1515,10 @@ def test_2Qgate_nottogether(self): my_gate2 = Gate(name="twoQ", num_qubits=2, params=[], label="twoQ") circuit.append(my_gate2, [qr[0], qr[2]]) - self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, reverse_bits=True)), + expected, + ) def test_2Qgate_nottogether_across_4(self): """2Q that are 2 bits apart""" @@ -1461,7 +1542,10 @@ def test_2Qgate_nottogether_across_4(self): my_gate2 = Gate(name="twoQ", num_qubits=2, params=[], label="twoQ") circuit.append(my_gate2, [qr[0], qr[3]]) - self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, reverse_bits=True)), + expected, + ) def test_unitary_nottogether_across_4(self): """unitary that are 2 bits apart""" @@ -1484,7 +1568,7 @@ def test_unitary_nottogether_across_4(self): qc.append(random_unitary(4, seed=42), [qr[0], qr[3]]) - self.assertEqual(str(_text_circuit_drawer(qc)), expected) + self.assertEqual(str(circuit_drawer(qc, initial_state=True, output="text")), expected) def test_kraus(self): """Test Kraus. @@ -1499,7 +1583,7 @@ def test_kraus(self): qc = QuantumCircuit(qr) qc.append(error, [qr[0]]) - self.assertEqual(str(_text_circuit_drawer(qc)), expected) + self.assertEqual(str(circuit_drawer(qc, output="text", initial_state=True)), expected) def test_multiplexer(self): """Test Multiplexer. @@ -1520,7 +1604,7 @@ def test_multiplexer(self): qc = QuantumCircuit(qr) qc.append(cx_multiplexer, [qr[0], qr[1]]) - self.assertEqual(str(_text_circuit_drawer(qc)), expected) + self.assertEqual(str(circuit_drawer(qc, output="text", initial_state=True)), expected) def test_label_over_name_2286(self): """If there is a label, it should be used instead of the name @@ -1540,7 +1624,7 @@ def test_label_over_name_2286(self): circ.append(XGate(label="alt-X"), [qr[0]]) circ.append(UnitaryGate(numpy.eye(4), label="iswap"), [qr[0], qr[1]]) - self.assertEqual(str(_text_circuit_drawer(circ)), expected) + self.assertEqual(str(circuit_drawer(circ, output="text", initial_state=True)), expected) def test_label_turns_to_box_2286(self): """If there is a label, non-boxes turn into boxes @@ -1560,7 +1644,7 @@ def test_label_turns_to_box_2286(self): circ.append(CZGate(), [qr[0], qr[1]]) circ.append(CZGate(label="cz label"), [qr[0], qr[1]]) - self.assertEqual(str(_text_circuit_drawer(circ)), expected) + self.assertEqual(str(circuit_drawer(circ, output="text", initial_state=True)), expected) def test_control_gate_with_base_label_4361(self): """Control gate has a label and a base gate with a label @@ -1582,7 +1666,7 @@ def test_control_gate_with_base_label_4361(self): circ.append(controlh, [0, 1]) circ.append(controlh, [1, 0]) - self.assertEqual(str(_text_circuit_drawer(circ)), expected) + self.assertEqual(str(circuit_drawer(circ, output="text", initial_state=True)), expected) def test_control_gate_label_with_cond_1_low(self): """Control gate has a label and a conditional (compression=low) @@ -1608,7 +1692,12 @@ def test_control_gate_label_with_cond_1_low(self): controlh = hgate.control(label="my ch").c_if(cr, 1) circ.append(controlh, [0, 1]) - self.assertEqual(str(_text_circuit_drawer(circ, vertical_compression="low")), expected) + self.assertEqual( + str( + circuit_drawer(circ, output="text", initial_state=True, vertical_compression="low") + ), + expected, + ) def test_control_gate_label_with_cond_1_low_cregbundle(self): """Control gate has a label and a conditional (compression=low) with cregbundle @@ -1635,7 +1724,16 @@ def test_control_gate_label_with_cond_1_low_cregbundle(self): circ.append(controlh, [0, 1]) self.assertEqual( - str(_text_circuit_drawer(circ, vertical_compression="low", cregbundle=True)), expected + str( + circuit_drawer( + circ, + output="text", + initial_state=True, + vertical_compression="low", + cregbundle=True, + ) + ), + expected, ) def test_control_gate_label_with_cond_1_med(self): @@ -1661,7 +1759,15 @@ def test_control_gate_label_with_cond_1_med(self): circ.append(controlh, [0, 1]) self.assertEqual( - str(_text_circuit_drawer(circ, cregbundle=False, vertical_compression="medium")), + str( + circuit_drawer( + circ, + output="text", + initial_state=True, + cregbundle=False, + vertical_compression="medium", + ) + ), expected, ) @@ -1689,7 +1795,15 @@ def test_control_gate_label_with_cond_1_med_cregbundle(self): circ.append(controlh, [0, 1]) self.assertEqual( - str(_text_circuit_drawer(circ, vertical_compression="medium", cregbundle=True)), + str( + circuit_drawer( + circ, + output="text", + initial_state=True, + cregbundle=True, + vertical_compression="medium", + ) + ), expected, ) @@ -1716,7 +1830,16 @@ def test_control_gate_label_with_cond_1_high(self): circ.append(controlh, [0, 1]) self.assertEqual( - str(_text_circuit_drawer(circ, cregbundle=False, vertical_compression="high")), expected + str( + circuit_drawer( + circ, + output="text", + initial_state=True, + cregbundle=False, + vertical_compression="high", + ) + ), + expected, ) def test_control_gate_label_with_cond_1_high_cregbundle(self): @@ -1742,7 +1865,16 @@ def test_control_gate_label_with_cond_1_high_cregbundle(self): circ.append(controlh, [0, 1]) self.assertEqual( - str(_text_circuit_drawer(circ, vertical_compression="high", cregbundle=True)), expected + str( + circuit_drawer( + circ, + output="text", + initial_state=True, + cregbundle=True, + vertical_compression="high", + ) + ), + expected, ) def test_control_gate_label_with_cond_2_med_space(self): @@ -1768,7 +1900,14 @@ def test_control_gate_label_with_cond_2_med_space(self): controlh = hgate.control(label="my ch").c_if(cr, 1) circ.append(controlh, [1, 0]) - self.assertEqual(str(_text_circuit_drawer(circ, vertical_compression="medium")), expected) + self.assertEqual( + str( + circuit_drawer( + circ, output="text", initial_state=True, vertical_compression="medium" + ) + ), + expected, + ) def test_control_gate_label_with_cond_2_med(self): """Control gate has a label and a conditional (on label, compression=med) @@ -1794,7 +1933,15 @@ def test_control_gate_label_with_cond_2_med(self): circ.append(controlh, [1, 0]) self.assertEqual( - str(_text_circuit_drawer(circ, cregbundle=False, vertical_compression="medium")), + str( + circuit_drawer( + circ, + output="text", + initial_state=True, + cregbundle=False, + vertical_compression="medium", + ) + ), expected, ) @@ -1822,7 +1969,15 @@ def test_control_gate_label_with_cond_2_med_cregbundle(self): circ.append(controlh, [1, 0]) self.assertEqual( - str(_text_circuit_drawer(circ, vertical_compression="medium", cregbundle=True)), + str( + circuit_drawer( + circ, + output="text", + initial_state=True, + cregbundle=True, + vertical_compression="medium", + ) + ), expected, ) @@ -1851,7 +2006,16 @@ def test_control_gate_label_with_cond_2_low(self): circ.append(controlh, [1, 0]) self.assertEqual( - str(_text_circuit_drawer(circ, cregbundle=False, vertical_compression="low")), expected + str( + circuit_drawer( + circ, + output="text", + initial_state=True, + cregbundle=False, + vertical_compression="low", + ) + ), + expected, ) def test_control_gate_label_with_cond_2_low_cregbundle(self): @@ -1879,7 +2043,16 @@ def test_control_gate_label_with_cond_2_low_cregbundle(self): circ.append(controlh, [1, 0]) self.assertEqual( - str(_text_circuit_drawer(circ, vertical_compression="low", cregbundle=True)), expected + str( + circuit_drawer( + circ, + output="text", + initial_state=True, + cregbundle=True, + vertical_compression="low", + ) + ), + expected, ) @@ -1899,7 +2072,7 @@ def test_text_no_parameters(self): qr = QuantumRegister(1, "q") circuit = QuantumCircuit(qr) circuit.x(0) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_parameters_mix(self): """cu3 drawing with parameters""" @@ -1917,7 +2090,7 @@ def test_text_parameters_mix(self): circuit = QuantumCircuit(qr) circuit.cu(pi / 2, Parameter("theta"), pi, 0, qr[0], qr[1]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_bound_parameters(self): """Bound parameters @@ -1937,7 +2110,7 @@ def test_text_bound_parameters(self): circuit.append(my_u2, [qr[0]]) circuit = circuit.assign_parameters({phi: 3.141592653589793, lam: 3.141592653589793}) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_pi_param_expr(self): """Text pi in circuit with parameter expression.""" @@ -1976,7 +2149,7 @@ def test_text_ndarray_parameters(self): qr = QuantumRegister(1, "q") circuit = QuantumCircuit(qr) circuit.unitary(numpy.array([[0, 1], [1, 0]]), 0) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_qc_parameters(self): """Test that if params are type QuantumCircuit, params are not displayed.""" @@ -1997,7 +2170,7 @@ def test_text_qc_parameters(self): qr = QuantumRegister(2, "q") circuit = QuantumCircuit(qr) circuit.append(inst, [0, 1]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) class TestTextDrawerVerticalCompressionLow(QiskitTestCase): @@ -2030,7 +2203,15 @@ def test_text_conditional_1(self): circuit = QuantumCircuit.from_qasm_str(qasm_string) self.assertEqual( - str(_text_circuit_drawer(circuit, cregbundle=False, vertical_compression="low")), + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + cregbundle=False, + vertical_compression="low", + ) + ), expected, ) @@ -2061,7 +2242,15 @@ def test_text_conditional_1_bundle(self): circuit = QuantumCircuit.from_qasm_str(qasm_string) self.assertEqual( - str(_text_circuit_drawer(circuit, vertical_compression="low", cregbundle=True)), + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + vertical_compression="low", + cregbundle=True, + ) + ), expected, ) @@ -2104,8 +2293,13 @@ def test_text_conditional_reverse_bits_true(self): self.assertEqual( str( - _text_circuit_drawer( - circuit, vertical_compression="low", cregbundle=False, reverse_bits=True + circuit_drawer( + circuit, + output="text", + initial_state=True, + cregbundle=False, + reverse_bits=True, + vertical_compression="low", ) ), expected, @@ -2150,8 +2344,13 @@ def test_text_conditional_reverse_bits_false(self): self.assertEqual( str( - _text_circuit_drawer( - circuit, vertical_compression="low", cregbundle=False, reverse_bits=False + circuit_drawer( + circuit, + output="text", + initial_state=True, + vertical_compression="low", + cregbundle=False, + reverse_bits=False, ) ), expected, @@ -2180,7 +2379,15 @@ def test_text_justify_right(self): circuit.h(qr1[1]) circuit.measure(qr1[1], cr1[1]) self.assertEqual( - str(_text_circuit_drawer(circuit, justify="right", vertical_compression="low")), + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + justify="right", + vertical_compression="low", + ) + ), expected, ) @@ -2212,7 +2419,15 @@ def test_text_conditional_1(self): ) circuit = QuantumCircuit.from_qasm_str(qasm_string) self.assertEqual( - str(_text_circuit_drawer(circuit, cregbundle=False, vertical_compression="medium")), + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + cregbundle=False, + vertical_compression="medium", + ) + ), expected, ) @@ -2242,7 +2457,15 @@ def test_text_conditional_1_bundle(self): circuit = QuantumCircuit.from_qasm_str(qasm_string) self.assertEqual( - str(_text_circuit_drawer(circuit, vertical_compression="medium", cregbundle=True)), + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + vertical_compression="medium", + cregbundle=True, + ) + ), expected, ) @@ -2275,7 +2498,15 @@ def test_text_measure_with_spaces(self): ) circuit = QuantumCircuit.from_qasm_str(qasm_string) self.assertEqual( - str(_text_circuit_drawer(circuit, cregbundle=False, vertical_compression="medium")), + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + cregbundle=False, + vertical_compression="medium", + ) + ), expected, ) @@ -2305,7 +2536,15 @@ def test_text_measure_with_spaces_bundle(self): ) circuit = QuantumCircuit.from_qasm_str(qasm_string) self.assertEqual( - str(_text_circuit_drawer(circuit, vertical_compression="medium", cregbundle=True)), + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + vertical_compression="medium", + cregbundle=True, + ) + ), expected, ) @@ -2332,7 +2571,15 @@ def test_text_barrier_med_compress_1(self): ) self.assertEqual( - str(_text_circuit_drawer(circuit, vertical_compression="medium", cregbundle=False)), + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + vertical_compression="medium", + cregbundle=False, + ) + ), expected, ) @@ -2360,7 +2607,15 @@ def test_text_barrier_med_compress_2(self): ) self.assertEqual( - str(_text_circuit_drawer(circuit, vertical_compression="medium", cregbundle=False)), + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + vertical_compression="medium", + cregbundle=False, + ) + ), expected, ) @@ -2391,8 +2646,10 @@ def test_text_barrier_med_compress_3(self): self.assertEqual( str( - _text_circuit_drawer( + circuit_drawer( circuit, + output="text", + initial_state=True, vertical_compression="medium", wire_order=[0, 1, 3, 4, 2], cregbundle=False, @@ -2429,7 +2686,18 @@ def test_text_conditional_1_cregbundle(self): ) circuit = QuantumCircuit.from_qasm_str(qasm_string) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + cregbundle=True, + vertical_compression="high", + ) + ), + expected, + ) def test_text_conditional_1(self): """Conditional drawing with 1-bit-length regs.""" @@ -2455,7 +2723,10 @@ def test_text_conditional_1(self): ) circuit = QuantumCircuit.from_qasm_str(qasm_string) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_2_cregbundle(self): """Conditional drawing with 2-bit-length regs with cregbundle""" @@ -2480,7 +2751,18 @@ def test_text_conditional_2_cregbundle(self): ] ) circuit = QuantumCircuit.from_qasm_str(qasm_string) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + cregbundle=True, + vertical_compression="high", + ) + ), + expected, + ) def test_text_conditional_2(self): """Conditional drawing with 2-bit-length regs.""" @@ -2509,7 +2791,10 @@ def test_text_conditional_2(self): ] ) circuit = QuantumCircuit.from_qasm_str(qasm_string) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_3_cregbundle(self): """Conditional drawing with 3-bit-length regs with cregbundle.""" @@ -2534,7 +2819,18 @@ def test_text_conditional_3_cregbundle(self): ] ) circuit = QuantumCircuit.from_qasm_str(qasm_string) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + cregbundle=True, + vertical_compression="high", + ) + ), + expected, + ) def test_text_conditional_3(self): """Conditional drawing with 3-bit-length regs.""" @@ -2567,7 +2863,10 @@ def test_text_conditional_3(self): ] ) circuit = QuantumCircuit.from_qasm_str(qasm_string) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_4(self): """Conditional drawing with 4-bit-length regs.""" @@ -2592,7 +2891,14 @@ def test_text_conditional_4(self): ] ) circuit = QuantumCircuit.from_qasm_str(qasm_string) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual( + str( + circuit_drawer( + circuit, output="text", initial_state=True, vertical_compression="high" + ) + ), + expected, + ) def test_text_conditional_5(self): """Conditional drawing with 5-bit-length regs.""" @@ -2633,7 +2939,10 @@ def test_text_conditional_5(self): ] ) circuit = QuantumCircuit.from_qasm_str(qasm_string) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_cz_no_space_cregbundle(self): """Conditional CZ without space""" @@ -2654,7 +2963,10 @@ def test_text_conditional_cz_no_space_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_conditional_cz_no_space(self): """Conditional CZ without space""" @@ -2675,7 +2987,10 @@ def test_text_conditional_cz_no_space(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_cz_cregbundle(self): """Conditional CZ with a wire in the middle""" @@ -2698,7 +3013,10 @@ def test_text_conditional_cz_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_conditional_cz(self): """Conditional CZ with a wire in the middle""" @@ -2721,7 +3039,10 @@ def test_text_conditional_cz(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_cx_ct_cregbundle(self): """Conditional CX (control-target) with a wire in the middle""" @@ -2744,7 +3065,10 @@ def test_text_conditional_cx_ct_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_conditional_cx_ct(self): """Conditional CX (control-target) with a wire in the middle""" @@ -2767,7 +3091,10 @@ def test_text_conditional_cx_ct(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_cx_tc_cregbundle(self): """Conditional CX (target-control) with a wire in the middle with cregbundle.""" @@ -2790,7 +3117,10 @@ def test_text_conditional_cx_tc_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_conditional_cx_tc(self): """Conditional CX (target-control) with a wire in the middle""" @@ -2813,7 +3143,10 @@ def test_text_conditional_cx_tc(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_cu3_ct_cregbundle(self): """Conditional Cu3 (control-target) with a wire in the middle with cregbundle""" @@ -2836,7 +3169,10 @@ def test_text_conditional_cu3_ct_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_conditional_cu3_ct(self): """Conditional Cu3 (control-target) with a wire in the middle""" @@ -2859,7 +3195,10 @@ def test_text_conditional_cu3_ct(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_cu3_tc_cregbundle(self): """Conditional Cu3 (target-control) with a wire in the middle with cregbundle""" @@ -2882,7 +3221,10 @@ def test_text_conditional_cu3_tc_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_conditional_cu3_tc(self): """Conditional Cu3 (target-control) with a wire in the middle""" @@ -2905,7 +3247,10 @@ def test_text_conditional_cu3_tc(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_ccx_cregbundle(self): """Conditional CCX with a wire in the middle with cregbundle""" @@ -2930,7 +3275,10 @@ def test_text_conditional_ccx_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_conditional_ccx(self): """Conditional CCX with a wire in the middle""" @@ -2955,7 +3303,10 @@ def test_text_conditional_ccx(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_ccx_no_space_cregbundle(self): """Conditional CCX without space with cregbundle""" @@ -2978,7 +3329,18 @@ def test_text_conditional_ccx_no_space_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + cregbundle=True, + vertical_compression="high", + ) + ), + expected, + ) def test_text_conditional_ccx_no_space(self): """Conditional CCX without space""" @@ -3001,7 +3363,10 @@ def test_text_conditional_ccx_no_space(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_h_cregbundle(self): """Conditional H with a wire in the middle with cregbundle""" @@ -3022,7 +3387,10 @@ def test_text_conditional_h_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_conditional_h(self): """Conditional H with a wire in the middle""" @@ -3043,7 +3411,10 @@ def test_text_conditional_h(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_swap_cregbundle(self): """Conditional SWAP with cregbundle""" @@ -3066,7 +3437,10 @@ def test_text_conditional_swap_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_conditional_swap(self): """Conditional SWAP""" @@ -3089,7 +3463,10 @@ def test_text_conditional_swap(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_conditional_cswap_cregbundle(self): """Conditional CSwap with cregbundle""" @@ -3114,7 +3491,10 @@ def test_text_conditional_cswap_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_conditional_cswap(self): """Conditional CSwap""" @@ -3139,7 +3519,10 @@ def test_text_conditional_cswap(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_conditional_reset_cregbundle(self): """Reset drawing with cregbundle.""" @@ -3161,7 +3544,10 @@ def test_conditional_reset_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_conditional_reset(self): """Reset drawing.""" @@ -3183,7 +3569,10 @@ def test_conditional_reset(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_conditional_multiplexer_cregbundle(self): """Test Multiplexer with cregbundle.""" @@ -3207,7 +3596,9 @@ def test_conditional_multiplexer_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(qc, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(qc, output="text", initial_state=True, cregbundle=True)), expected + ) def test_conditional_multiplexer(self): """Test Multiplexer.""" @@ -3231,7 +3622,9 @@ def test_conditional_multiplexer(self): ] ) - self.assertEqual(str(_text_circuit_drawer(qc, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(qc, output="text", initial_state=True, cregbundle=False)), expected + ) def test_text_conditional_measure_cregbundle(self): """Conditional with measure on same clbit with cregbundle""" @@ -3254,7 +3647,18 @@ def test_text_conditional_measure_cregbundle(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + cregbundle=True, + vertical_compression="high", + ) + ), + expected, + ) def test_text_conditional_measure(self): """Conditional with measure on same clbit""" @@ -3278,7 +3682,10 @@ def test_text_conditional_measure(self): " 0x1 ", ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_bit_conditional(self): """Test bit conditions on gates""" @@ -3303,7 +3710,10 @@ def test_text_bit_conditional(self): ] ) - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=False)), + expected, + ) def test_text_bit_conditional_cregbundle(self): """Test bit conditions on gates when cregbundle=True""" @@ -3328,7 +3738,15 @@ def test_text_bit_conditional_cregbundle(self): ) self.assertEqual( - str(_text_circuit_drawer(circuit, cregbundle=True, vertical_compression="medium")), + str( + circuit_drawer( + circuit, + output="text", + initial_state=True, + cregbundle=True, + vertical_compression="medium", + ) + ), expected, ) @@ -3362,7 +3780,8 @@ def test_text_condition_measure_bits_true(self): ] ) self.assertEqual( - str(_text_circuit_drawer(circuit, cregbundle=True, initial_state=False)), expected + str(circuit_drawer(circuit, output="text", cregbundle=True, initial_state=False)), + expected, ) def test_text_condition_measure_bits_false(self): @@ -3401,7 +3820,8 @@ def test_text_condition_measure_bits_false(self): ] ) self.assertEqual( - str(_text_circuit_drawer(circuit, cregbundle=False, initial_state=False)), expected + str(circuit_drawer(circuit, output="text", cregbundle=False, initial_state=False)), + expected, ) def test_text_conditional_reverse_bits_1(self): @@ -3428,7 +3848,12 @@ def test_text_conditional_reverse_bits_1(self): ) self.assertEqual( - str(_text_circuit_drawer(circuit, cregbundle=False, reverse_bits=True)), expected + str( + circuit_drawer( + circuit, output="text", initial_state=True, cregbundle=False, reverse_bits=True + ) + ), + expected, ) def test_text_conditional_reverse_bits_2(self): @@ -3460,7 +3885,12 @@ def test_text_conditional_reverse_bits_2(self): ) self.assertEqual( - str(_text_circuit_drawer(circuit, cregbundle=False, reverse_bits=True)), expected + str( + circuit_drawer( + circuit, output="text", initial_state=True, cregbundle=False, reverse_bits=True + ) + ), + expected, ) def test_text_condition_bits_reverse(self): @@ -3493,8 +3923,8 @@ def test_text_condition_bits_reverse(self): ) self.assertEqual( str( - _text_circuit_drawer( - circuit, cregbundle=True, initial_state=False, reverse_bits=True + circuit_drawer( + circuit, output="text", cregbundle=True, initial_state=False, reverse_bits=True ) ), expected, @@ -3514,7 +3944,10 @@ def test_text_h(self): qr1 = QuantumRegister(3, "q1") circuit = QuantumCircuit(qr1) circuit.h(qr1[1]) - self.assertEqual(str(_text_circuit_drawer(circuit, idle_wires=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, idle_wires=False)), + expected, + ) def test_text_measure(self): """Remove QuWires and ClWires.""" @@ -3535,13 +3968,19 @@ def test_text_measure(self): cr2 = ClassicalRegister(2, "c2") circuit = QuantumCircuit(qr1, qr2, cr1, cr2) circuit.measure(qr2, cr2) - self.assertEqual(str(_text_circuit_drawer(circuit, idle_wires=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, idle_wires=False)), + expected, + ) def test_text_empty_circuit(self): """Remove everything in an empty circuit.""" expected = "" circuit = QuantumCircuit() - self.assertEqual(str(_text_circuit_drawer(circuit, idle_wires=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, idle_wires=False)), + expected, + ) def test_text_barrier(self): """idle_wires should ignore barrier @@ -3555,7 +3994,10 @@ def test_text_barrier(self): circuit = QuantumCircuit(qr) circuit.h(qr[1]) circuit.barrier(qr[1], qr[2]) - self.assertEqual(str(_text_circuit_drawer(circuit, idle_wires=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, idle_wires=False)), + expected, + ) def test_text_barrier_delay(self): """idle_wires should ignore delay""" @@ -3569,7 +4011,10 @@ def test_text_barrier_delay(self): circuit.h(qr[1]) circuit.barrier() circuit.delay(100, qr[2]) - self.assertEqual(str(_text_circuit_drawer(circuit, idle_wires=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, idle_wires=False)), + expected, + ) def test_does_not_mutate_circuit(self): """Using 'idle_wires=False' should not mutate the circuit. Regression test of gh-8739.""" @@ -3594,7 +4039,7 @@ def test_text_pifrac(self): qr = QuantumRegister(1, "q") circuit = QuantumCircuit(qr) circuit.u(pi, -5 * pi / 8, 0, qr[0]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_complex(self): """Complex numbers show up in the text @@ -3671,7 +4116,7 @@ def test_text_all_1q_1c(self): circuit = QuantumCircuit(qr1, cr1) circuit.append(inst, qr1[:], cr1[:]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_all_2q_2c(self): """Test q0-q1-c0-c1 in q0-q1-c0-c1""" @@ -3695,7 +4140,7 @@ def test_text_all_2q_2c(self): circuit = QuantumCircuit(qr2, cr2) circuit.append(inst, qr2[:], cr2[:]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_all_2q_2c_cregbundle(self): """Test q0-q1-c0-c1 in q0-q1-c0-c1. Ignore cregbundle=True""" @@ -3719,7 +4164,10 @@ def test_text_all_2q_2c_cregbundle(self): circuit = QuantumCircuit(qr2, cr2) circuit.append(inst, qr2[:], cr2[:]) with self.assertWarns(RuntimeWarning): - self.assertEqual(str(_text_circuit_drawer(circuit, cregbundle=True)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, cregbundle=True)), + expected, + ) def test_text_4q_2c(self): """Test q1-q2-q3-q4-c1-c2 in q0-q1-q2-q3-q4-q5-c0-c1-c2-c3-c4-c5""" @@ -3761,7 +4209,7 @@ def test_text_4q_2c(self): circuit = QuantumCircuit(qr6, cr6) circuit.append(inst, qr6[1:5], cr6[1:3]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_2q_1c(self): """Test q0-c0 in q0-q1-c0 @@ -3784,7 +4232,7 @@ def test_text_2q_1c(self): inst = QuantumCircuit(1, 1, name="Name").to_instruction() circuit.append(inst, [qr[0]], [cr[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_3q_3c_qlabels_inverted(self): """Test q3-q0-q1-c0-c1-c_10 in q0-q1-q2-q3-c0-c1-c2-c_10-c_11 @@ -3820,7 +4268,7 @@ def test_text_3q_3c_qlabels_inverted(self): inst = QuantumCircuit(3, 3, name="Name").to_instruction() circuit.append(inst, [qr[3], qr[0], qr[1]], [cr[0], cr[1], cr1[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_3q_3c_clabels_inverted(self): """Test q0-q1-q3-c_11-c0-c_10 in q0-q1-q2-q3-c0-c1-c2-c_10-c_11 @@ -3856,7 +4304,7 @@ def test_text_3q_3c_clabels_inverted(self): inst = QuantumCircuit(3, 3, name="Name").to_instruction() circuit.append(inst, [qr[0], qr[1], qr[3]], [cr1[1], cr[0], cr1[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_3q_3c_qclabels_inverted(self): """Test q3-q1-q2-c_11-c0-c_10 in q0-q1-q2-q3-c0-c1-c2-c_10-c_11 @@ -3892,7 +4340,7 @@ def test_text_3q_3c_qclabels_inverted(self): inst = QuantumCircuit(3, 3, name="Name").to_instruction() circuit.append(inst, [qr[3], qr[1], qr[2]], [cr1[1], cr[0], cr1[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) class TestTextDrawerAppendedLargeInstructions(QiskitTestCase): @@ -3934,7 +4382,7 @@ def test_text_11q(self): inst = QuantumCircuit(11, name="Name").to_instruction() circuit.append(inst, qr) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_11q_1c(self): """Test q0-...-q10-c0 in q0-...-q10-c0""" @@ -3974,7 +4422,7 @@ def test_text_11q_1c(self): inst = QuantumCircuit(11, 1, name="Name").to_instruction() circuit.append(inst, qr, cr) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) class TestTextControlledGate(QiskitTestCase): @@ -3996,7 +4444,7 @@ def test_cch_bot(self): qr = QuantumRegister(3, "q") circuit = QuantumCircuit(qr) circuit.append(HGate().control(2), [qr[0], qr[1], qr[2]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_cch_mid(self): """Controlled CH (middle)""" @@ -4014,7 +4462,7 @@ def test_cch_mid(self): qr = QuantumRegister(3, "q") circuit = QuantumCircuit(qr) circuit.append(HGate().control(2), [qr[0], qr[2], qr[1]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_cch_top(self): """Controlled CH""" @@ -4032,7 +4480,7 @@ def test_cch_top(self): qr = QuantumRegister(3, "q") circuit = QuantumCircuit(qr) circuit.append(HGate().control(2), [qr[2], qr[1], qr[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_c3h(self): """Controlled Controlled CH""" @@ -4052,7 +4500,7 @@ def test_c3h(self): qr = QuantumRegister(4, "q") circuit = QuantumCircuit(qr) circuit.append(HGate().control(3), [qr[0], qr[1], qr[2], qr[3]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_c3h_middle(self): """Controlled Controlled CH (middle)""" @@ -4072,7 +4520,7 @@ def test_c3h_middle(self): qr = QuantumRegister(4, "q") circuit = QuantumCircuit(qr) circuit.append(HGate().control(3), [qr[0], qr[3], qr[2], qr[1]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_c3u2(self): """Controlled Controlled U2""" @@ -4092,7 +4540,7 @@ def test_c3u2(self): qr = QuantumRegister(4, "q") circuit = QuantumCircuit(qr) circuit.append(U2Gate(pi, -5 * pi / 8).control(3), [qr[0], qr[3], qr[2], qr[1]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_composite_gate_edge(self): """Controlled composite gates (edge) @@ -4119,7 +4567,7 @@ def test_controlled_composite_gate_edge(self): circuit = QuantumCircuit(4) circuit.append(cghz, [1, 0, 2, 3]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_composite_gate_top(self): """Controlled composite gates (top)""" @@ -4145,7 +4593,7 @@ def test_controlled_composite_gate_top(self): circuit = QuantumCircuit(4) circuit.append(cghz, [0, 1, 3, 2]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_composite_gate_bot(self): """Controlled composite gates (bottom)""" @@ -4171,7 +4619,7 @@ def test_controlled_composite_gate_bot(self): circuit = QuantumCircuit(4) circuit.append(cghz, [3, 1, 0, 2]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_composite_gate_top_bot(self): """Controlled composite gates (top and bottom)""" @@ -4199,7 +4647,7 @@ def test_controlled_composite_gate_top_bot(self): circuit = QuantumCircuit(5) circuit.append(ccghz, [4, 0, 1, 2, 3]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_composite_gate_all(self): """Controlled composite gates (top, bot, and edge)""" @@ -4229,7 +4677,7 @@ def test_controlled_composite_gate_all(self): circuit = QuantumCircuit(6) circuit.append(ccghz, [0, 2, 5, 1, 3, 4]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_composite_gate_even_label(self): """Controlled composite gates (top and bottom) with a even label length""" @@ -4257,7 +4705,7 @@ def test_controlled_composite_gate_even_label(self): circuit = QuantumCircuit(5) circuit.append(ccghz, [4, 0, 1, 2, 3]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) class TestTextOpenControlledGate(QiskitTestCase): @@ -4277,7 +4725,7 @@ def test_ch_bot(self): qr = QuantumRegister(2, "q") circuit = QuantumCircuit(qr) circuit.append(HGate().control(1, ctrl_state=0), [qr[0], qr[1]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_cz_bot(self): """Open controlled Z (bottom)""" @@ -4291,7 +4739,7 @@ def test_cz_bot(self): qr = QuantumRegister(2, "q") circuit = QuantumCircuit(qr) circuit.append(ZGate().control(1, ctrl_state=0), [qr[0], qr[1]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_ccz_bot(self): """Closed-Open controlled Z (bottom)""" @@ -4309,7 +4757,7 @@ def test_ccz_bot(self): qr = QuantumRegister(3, "q") circuit = QuantumCircuit(qr) circuit.append(ZGate().control(2, ctrl_state="01"), [qr[0], qr[1], qr[2]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_cccz_conditional(self): """Closed-Open controlled Z (with conditional)""" @@ -4334,7 +4782,7 @@ def test_cccz_conditional(self): circuit.append( ZGate().control(3, ctrl_state="101").c_if(cr, 1), [qr[0], qr[1], qr[2], qr[3]] ) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_cch_bot(self): """Controlled CH (bottom)""" @@ -4352,7 +4800,7 @@ def test_cch_bot(self): qr = QuantumRegister(3, "q") circuit = QuantumCircuit(qr) circuit.append(HGate().control(2, ctrl_state="10"), [qr[0], qr[1], qr[2]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_cch_mid(self): """Controlled CH (middle)""" @@ -4370,7 +4818,7 @@ def test_cch_mid(self): qr = QuantumRegister(3, "q") circuit = QuantumCircuit(qr) circuit.append(HGate().control(2, ctrl_state="10"), [qr[0], qr[2], qr[1]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_cch_top(self): """Controlled CH""" @@ -4388,7 +4836,7 @@ def test_cch_top(self): qr = QuantumRegister(3, "q") circuit = QuantumCircuit(qr) circuit.append(HGate().control(2, ctrl_state="10"), [qr[1], qr[2], qr[0]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_c3h(self): """Controlled Controlled CH""" @@ -4408,7 +4856,7 @@ def test_c3h(self): qr = QuantumRegister(4, "q") circuit = QuantumCircuit(qr) circuit.append(HGate().control(3, ctrl_state="100"), [qr[0], qr[1], qr[2], qr[3]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_c3h_middle(self): """Controlled Controlled CH (middle)""" @@ -4428,7 +4876,7 @@ def test_c3h_middle(self): qr = QuantumRegister(4, "q") circuit = QuantumCircuit(qr) circuit.append(HGate().control(3, ctrl_state="010"), [qr[0], qr[3], qr[2], qr[1]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_c3u2(self): """Controlled Controlled U2""" @@ -4450,7 +4898,7 @@ def test_c3u2(self): circuit.append( U2Gate(pi, -5 * pi / 8).control(3, ctrl_state="100"), [qr[0], qr[3], qr[2], qr[1]] ) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_composite_gate_edge(self): """Controlled composite gates (edge) @@ -4477,7 +4925,7 @@ def test_controlled_composite_gate_edge(self): circuit = QuantumCircuit(4) circuit.append(cghz, [1, 0, 2, 3]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_composite_gate_top(self): """Controlled composite gates (top)""" @@ -4503,7 +4951,7 @@ def test_controlled_composite_gate_top(self): circuit = QuantumCircuit(4) circuit.append(cghz, [0, 1, 3, 2]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_composite_gate_bot(self): """Controlled composite gates (bottom)""" @@ -4529,7 +4977,7 @@ def test_controlled_composite_gate_bot(self): circuit = QuantumCircuit(4) circuit.append(cghz, [3, 1, 0, 2]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_composite_gate_top_bot(self): """Controlled composite gates (top and bottom)""" @@ -4557,7 +5005,7 @@ def test_controlled_composite_gate_top_bot(self): circuit = QuantumCircuit(5) circuit.append(ccghz, [4, 0, 1, 2, 3]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_controlled_composite_gate_all(self): """Controlled composite gates (top, bot, and edge)""" @@ -4587,7 +5035,7 @@ def test_controlled_composite_gate_all(self): circuit = QuantumCircuit(6) circuit.append(ccghz, [0, 2, 5, 1, 3, 4]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_open_controlled_x(self): """Controlled X gates. @@ -4620,7 +5068,7 @@ def test_open_controlled_x(self): control3 = XGate().control(4, ctrl_state="0101") circuit.append(control3, [0, 1, 4, 2, 3]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_open_controlled_y(self): """Controlled Y gates. @@ -4653,7 +5101,7 @@ def test_open_controlled_y(self): control3 = YGate().control(4, ctrl_state="0101") circuit.append(control3, [0, 1, 4, 2, 3]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_open_controlled_z(self): """Controlled Z gates.""" @@ -4685,7 +5133,7 @@ def test_open_controlled_z(self): control3 = ZGate().control(4, ctrl_state="0101") circuit.append(control3, [0, 1, 4, 2, 3]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_open_controlled_u1(self): """Controlled U1 gates.""" @@ -4717,7 +5165,7 @@ def test_open_controlled_u1(self): control3 = U1Gate(0.5).control(4, ctrl_state="0101") circuit.append(control3, [0, 1, 4, 2, 3]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_open_controlled_swap(self): """Controlled SWAP gates.""" @@ -4747,7 +5195,7 @@ def test_open_controlled_swap(self): control3 = SwapGate().control(3, ctrl_state="010") circuit.append(control3, [0, 1, 2, 3, 4]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_open_controlled_rzz(self): """Controlled RZZ gates.""" @@ -4777,7 +5225,7 @@ def test_open_controlled_rzz(self): control3 = RZZGate(1).control(3, ctrl_state="010") circuit.append(control3, [0, 1, 2, 3, 4]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_open_out_of_order(self): """Out of order CXs @@ -4801,7 +5249,7 @@ def test_open_out_of_order(self): circuit = QuantumCircuit(qr) circuit.append(XGate().control(3, ctrl_state="101"), [qr[0], qr[3], qr[1], qr[2]]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) class TestTextWithLayout(QiskitTestCase): @@ -4823,7 +5271,7 @@ def test_with_no_layout(self): qr = QuantumRegister(3, "q") circuit = QuantumCircuit(qr) circuit.h(qr[1]) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_mixed_layout(self): """With a mixed layout.""" @@ -4849,7 +5297,9 @@ def test_mixed_layout(self): pass_.property_set["layout"] = Layout({qr[0]: 0, ancilla[1]: 1, ancilla[0]: 2, qr[1]: 3}) circuit_with_layout = pass_(circuit) - self.assertEqual(str(_text_circuit_drawer(circuit_with_layout)), expected) + self.assertEqual( + str(circuit_drawer(circuit_with_layout, output="text", initial_state=True)), expected + ) def test_partial_layout(self): """With a partial layout. @@ -4878,7 +5328,7 @@ def test_partial_layout(self): ) circuit._layout.initial_layout.add_register(qr) - self.assertEqual(str(_text_circuit_drawer(circuit)), expected) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_with_classical_regs(self): """Involving classical registers""" @@ -4909,7 +5359,9 @@ def test_with_classical_regs(self): pass_.property_set["layout"] = Layout({qr1[0]: 0, qr1[1]: 1, qr2[0]: 2, qr2[1]: 3}) circuit_with_layout = pass_(circuit) - self.assertEqual(str(_text_circuit_drawer(circuit_with_layout)), expected) + self.assertEqual( + str(circuit_drawer(circuit_with_layout, output="text", initial_state=True)), expected + ) def test_with_layout_but_disable(self): """With parameter without_layout=False""" @@ -4936,7 +5388,10 @@ def test_with_layout_but_disable(self): circuit._layout = Layout({qr1[0]: 0, qr1[1]: 1, qr2[0]: 2, qr2[1]: 3}) circuit.measure(pqr[2], cr[0]) circuit.measure(pqr[3], cr[1]) - self.assertEqual(str(_text_circuit_drawer(circuit, with_layout=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=True, with_layout=False)), + expected, + ) def test_after_transpile(self): """After transpile, the drawing should include the layout""" @@ -5079,7 +5534,9 @@ def test_initial_value_false(self): ] ) - self.assertEqual(str(_text_circuit_drawer(self.circuit, initial_state=False)), expected) + self.assertEqual( + str(circuit_drawer(self.circuit, output="text", initial_state=False)), expected + ) class TestTextHamiltonianGate(QiskitTestCase): @@ -5255,7 +5712,12 @@ def test_text_drawer_utf8(self): filename = "current_textplot_utf8.txt" qc = self.sample_circuit() output = _text_circuit_drawer( - qc, filename=filename, fold=-1, initial_state=True, cregbundle=False, encoding="utf8" + qc, + filename=filename, + fold=-1, + initial_state=True, + cregbundle=False, + encoding="utf8", ) try: encode(str(output), encoding="utf8") @@ -5269,13 +5731,18 @@ def test_text_drawer_cp437(self): filename = "current_textplot_cp437.txt" qc = self.sample_circuit() output = _text_circuit_drawer( - qc, filename=filename, fold=-1, initial_state=True, cregbundle=False, encoding="cp437" + qc, + filename=filename, + fold=-1, + initial_state=True, + cregbundle=False, + encoding="cp437", ) try: encode(str(output), encoding="cp437") except UnicodeEncodeError: self.fail("_text_circuit_drawer() should be cp437.") - self.assertFilesAreEqual(filename, self.text_reference_cp437, "cp437") + self.assertFilesAreEqual("current_textplot_cp437.txt", self.text_reference_cp437, "cp437") os.remove(filename) @@ -5310,7 +5777,8 @@ def test_if_op_bundle_false(self): circuit.h(0) circuit.cx(0, 1) self.assertEqual( - str(_text_circuit_drawer(circuit, initial_state=False, cregbundle=False)), expected + str(circuit_drawer(circuit, output="text", initial_state=False, cregbundle=False)), + expected, ) def test_if_op_bundle_true(self): @@ -5338,7 +5806,10 @@ def test_if_op_bundle_true(self): with circuit.if_test((cr[1], 1)): circuit.h(0) circuit.cx(0, 1) - self.assertEqual(str(_text_circuit_drawer(circuit, initial_state=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=False)), + expected, + ) def test_if_else_with_body_specified(self): """Test an IfElseOp where the body is directly specified.""" @@ -5382,7 +5853,8 @@ def test_if_else_with_body_specified(self): circuit.if_else((cr[1], 1), circuit2, None, [0, 1, 2], [0, 1, 2]) circuit.x(0, label="X1i") self.assertEqual( - str(_text_circuit_drawer(circuit, initial_state=False, cregbundle=False)), expected + str(circuit_drawer(circuit, output="text", initial_state=False, cregbundle=False)), + expected, ) def test_if_op_nested_wire_order(self): @@ -5464,8 +5936,9 @@ def test_if_op_nested_wire_order(self): circuit.x(0) self.assertEqual( str( - _text_circuit_drawer( + circuit_drawer( circuit, + output="text", fold=77, initial_state=False, wire_order=[2, 0, 3, 1, 4, 5, 6], @@ -5509,7 +5982,8 @@ def test_while_loop(self): with circuit.if_test((cr[2], 1)): circuit.x(0) self.assertEqual( - str(_text_circuit_drawer(circuit, initial_state=False, cregbundle=False)), expected + str(circuit_drawer(circuit, output="text", initial_state=False, cregbundle=False)), + expected, ) def test_for_loop(self): @@ -5548,7 +6022,11 @@ def test_for_loop(self): with circuit.if_test((cr[2], 1)): circuit.z(0) self.assertEqual( - str(_text_circuit_drawer(circuit, fold=-1, initial_state=False, cregbundle=False)), + str( + circuit_drawer( + circuit, output="text", fold=-1, initial_state=False, cregbundle=False + ) + ), expected, ) @@ -5604,7 +6082,11 @@ def test_switch_case(self): circuit.cx(0, 1) circuit.h(0) self.assertEqual( - str(_text_circuit_drawer(circuit, fold=78, initial_state=False, cregbundle=False)), + str( + circuit_drawer( + circuit, output="text", fold=78, initial_state=False, cregbundle=False + ) + ), expected, ) @@ -5644,7 +6126,11 @@ def test_inner_wire_map_control_op(self): circuit = transpile(qc, backend, optimization_level=2, seed_transpiler=671_42) self.assertEqual( - str(_text_circuit_drawer(circuit, fold=78, initial_state=False, cregbundle=False)), + str( + circuit_drawer( + circuit, output="text", fold=78, initial_state=False, cregbundle=False + ) + ), expected, ) @@ -5683,7 +6169,8 @@ def test_if_else_op_from_circuit_with_conditions(self): circuit.if_else((cr[1], 1), qc2, None, [0, 1, 2], [0, 1, 2]) self.assertEqual( - str(_text_circuit_drawer(circuit, initial_state=False, cregbundle=False)), expected + str(circuit_drawer(circuit, output="text", initial_state=False, cregbundle=False)), + expected, ) def test_if_with_expr(self): @@ -5718,7 +6205,10 @@ def test_if_with_expr(self): with circuit.if_test(expr.equal(expr.bit_and(cr1, expr.bit_and(cr2, cr3)), 3)): circuit.z(0) - self.assertEqual(str(_text_circuit_drawer(circuit, initial_state=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", initial_state=False)), + expected, + ) def test_if_with_expr_nested(self): """Test an IfElseOp with an expression for nested""" @@ -5755,7 +6245,8 @@ def test_if_with_expr_nested(self): circuit.z(1) self.assertEqual( - str(_text_circuit_drawer(circuit, initial_state=False, fold=120)), expected + str(circuit_drawer(circuit, output="text", initial_state=False, fold=120)), + expected, ) def test_switch_with_expression(self): @@ -5808,7 +6299,10 @@ def test_switch_with_expression(self): with case(case.DEFAULT): circuit.cx(0, 1) - self.assertEqual(str(_text_circuit_drawer(circuit, fold=80, initial_state=False)), expected) + self.assertEqual( + str(circuit_drawer(circuit, output="text", fold=80, initial_state=False)), + expected, + ) if __name__ == "__main__": diff --git a/test/python/visualization/test_dag_drawer.py b/test/python/visualization/test_dag_drawer.py index bc5c588ff570..4b920390e880 100644 --- a/test/python/visualization/test_dag_drawer.py +++ b/test/python/visualization/test_dag_drawer.py @@ -16,11 +16,11 @@ import tempfile import unittest -from qiskit.circuit import QuantumRegister, QuantumCircuit, Qubit, Clbit +from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit, Qubit, Clbit from qiskit.visualization import dag_drawer from qiskit.exceptions import InvalidFileError from qiskit.visualization import VisualizationError -from qiskit.converters import circuit_to_dag +from qiskit.converters import circuit_to_dag, circuit_to_dagdependency from qiskit.utils import optionals as _optionals from .visualization import path_to_diagram_reference, QiskitVisualizationTestCase @@ -79,7 +79,34 @@ def test_dag_drawer_no_register(self): dag_drawer(dag, filename=tmp_path) image_ref = path_to_diagram_reference("dag_no_reg.png") image = Image.open(tmp_path) - self.assertImagesAreEqual(image, image_ref, 0.2) + self.assertImagesAreEqual(image, image_ref, 0.1) + + @unittest.skipUnless(_optionals.HAS_GRAPHVIZ, "Graphviz not installed") + @unittest.skipUnless(_optionals.HAS_PIL, "PIL not installed") + def test_dag_drawer_with_dag_dep(self): + """Test dag dependency visualization.""" + from PIL import Image # pylint: disable=import-error + + bits = [Qubit(), Clbit()] + qr = QuantumRegister(4, "qr") + cr = ClassicalRegister(4, "cr") + qc = QuantumCircuit(qr, bits, cr) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + qc.cx(0, 3) + qc.x(3).c_if(cr[1], 1) + qc.h(3) + qc.x(4) + qc.barrier(0, 1) + qc.measure(0, 0) + dag = circuit_to_dagdependency(qc) + with tempfile.TemporaryDirectory() as tmpdirname: + tmp_path = os.path.join(tmpdirname, "dag_d.png") + dag_drawer(dag, filename=tmp_path) + image_ref = path_to_diagram_reference("dag_dep.png") + image = Image.open(tmp_path) + self.assertImagesAreEqual(image, image_ref, 0.1) if __name__ == "__main__": diff --git a/test/qpy_compat/compare_versions.py b/test/qpy_compat/compare_versions.py new file mode 100755 index 000000000000..5deec1b9ca5b --- /dev/null +++ b/test/qpy_compat/compare_versions.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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. + +"""Compare Qiskit versions to determine if we should run qpy compat tests.""" + +import argparse +import sys + +from qiskit.qpy.interface import VERSION_PATTERN_REGEX + + +def main(): + """Main function.""" + parser = argparse.ArgumentParser(prog="compare_version", description="Compare version strings") + parser.add_argument( + "source_version", help="Source version of Qiskit that is generating the payload" + ) + parser.add_argument("test_version", help="Version under test that will load the QPY payload") + args = parser.parse_args() + source_match = VERSION_PATTERN_REGEX.search(args.source_version) + target_match = VERSION_PATTERN_REGEX.search(args.test_version) + source_version = tuple(int(x) for x in source_match.group("release").split(".")) + target_version = tuple(int(x) for x in target_match.group("release").split(".")) + if source_version > target_version: + sys.exit(1) + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/test/qpy_compat/process_version.sh b/test/qpy_compat/process_version.sh index 59c5610eb95c..9112be67adb1 100755 --- a/test/qpy_compat/process_version.sh +++ b/test/qpy_compat/process_version.sh @@ -14,9 +14,25 @@ set -e set -x +# version is the source version, this is the release with which to generate +# qpy files with to load with the version under test version=$1 parts=( ${version//./ } ) -if [[ ${parts[1]} -lt 18 ]] ; then +# qiskit_version is the version under test, We're testing that we can correctly +# read the qpy files generated with source version with this version. +qiskit_version=`./qiskit_venv/bin/python -c "import qiskit;print(qiskit.__version__)"` +qiskit_parts=( ${qiskit_version//./ } ) + + +# If source version is less than 0.18 QPY didn't exist yet so exit fast +if [[ ${parts[0]} -eq 0 && ${parts[1]} -lt 18 ]] ; then + exit 0 +fi + +# If the source version is newer than the version under test exit fast because +# there is no QPY compatibility for loading a qpy file generated from a newer +# release with an older release of Qiskit. +if ! ./qiskit_venv/bin/python ./compare_versions.py "$version" "$qiskit_version" ; then exit 0 fi diff --git a/test/visual/mpl/circuit/references/qreg_names_after_layout.png b/test/visual/mpl/circuit/references/qreg_names_after_layout.png new file mode 100644 index 000000000000..7b372af05276 Binary files /dev/null and b/test/visual/mpl/circuit/references/qreg_names_after_layout.png differ diff --git a/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py b/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py index 88910f2db80e..e2f74f03c52a 100644 --- a/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py +++ b/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (C) Copyright IBM 2020, 2023. # # 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 @@ -24,7 +24,7 @@ from qiskit.test import QiskitTestCase from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile from qiskit.providers.fake_provider import FakeTenerife, FakeBelemV2 -from qiskit.visualization.circuit.circuit_visualization import _matplotlib_circuit_drawer +from qiskit.visualization.circuit.circuit_visualization import circuit_drawer from qiskit.circuit.library import ( XGate, MCXGate, @@ -40,7 +40,7 @@ Isometry, ) from qiskit.circuit.library import MCXVChain -from qiskit.circuit import Parameter, Qubit, Clbit, SwitchCaseOp, IfElseOp +from qiskit.circuit import Parameter, Qubit, Clbit, IfElseOp, SwitchCaseOp from qiskit.circuit.library import IQP from qiskit.circuit.classical import expr from qiskit.quantum_info.random import random_unitary @@ -64,7 +64,7 @@ class TestCircuitMatplotlibDrawer(QiskitTestCase): def setUp(self): super().setUp() self.circuit_drawer = VisualTestUtilities.save_data_wrap( - _matplotlib_circuit_drawer, str(self), RESULT_DIR + circuit_drawer, str(self), RESULT_DIR ) if not os.path.exists(FAILURE_DIFF_DIR): @@ -90,7 +90,7 @@ def test_empty_circuit(self): circuit = QuantumCircuit() fname = "empty_circut.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -119,7 +119,7 @@ def test_calibrations(self): circuit.add_calibration("h", [0], h_q0) fname = "calibrations.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -156,7 +156,7 @@ def test_calibrations_with_control_gates(self): circuit.add_calibration("ch", [0, 1], ch_q01) fname = "calibrations_with_control_gates.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -193,7 +193,7 @@ def test_calibrations_with_swap_and_reset(self): circuit.add_calibration("reset", [0], reset_q0) fname = "calibrations_with_swap_and_reset.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -229,7 +229,7 @@ def test_calibrations_with_rzz_and_rxx(self): circuit.add_calibration("rxx", [0, 1], rxx_q01) fname = "calibrations_with_rzz_and_rxx.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -246,7 +246,7 @@ def test_no_ops(self): circuit = QuantumCircuit(2, 3) fname = "no_op_circut.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -274,7 +274,7 @@ def test_long_name(self): circuit.h(qr) fname = "long_name.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -294,7 +294,7 @@ def test_multi_underscore_reg_names(self): circuit = QuantumCircuit(q_reg1, q_reg3, c_reg1, c_reg3) fname = "multi_underscore_true.png" - self.circuit_drawer(circuit, cregbundle=True, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=True, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -304,7 +304,7 @@ def test_multi_underscore_reg_names(self): FAILURE_PREFIX, ) fname2 = "multi_underscore_false.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname2) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname2) ratio2 = VisualTestUtilities._save_diff( self._image_path(fname2), @@ -329,7 +329,7 @@ def test_conditional(self): circuit.h(qr[0]).c_if(cr, 2) fname = "reg_conditional.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -353,7 +353,7 @@ def test_bit_conditional_with_cregbundle(self): circuit.x(qr[1]).c_if(cr[1], 0) fname = "bit_conditional_bundle.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -377,7 +377,7 @@ def test_bit_conditional_no_cregbundle(self): circuit.x(qr[1]).c_if(cr[1], 0) fname = "bit_conditional_no_bundle.png" - self.circuit_drawer(circuit, filename=fname, cregbundle=False) + self.circuit_drawer(circuit, output="mpl", filename=fname, cregbundle=False) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -402,7 +402,7 @@ def test_plot_partial_barrier(self): circuit.h(q[0]) fname = "plot_partial_barrier.png" - self.circuit_drawer(circuit, filename=fname, plot_barriers=True) + self.circuit_drawer(circuit, output="mpl", filename=fname, plot_barriers=True) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -437,7 +437,7 @@ def test_plot_barriers(self): # check the barriers plot properly when plot_barriers= True fname = "plot_barriers_true.png" - self.circuit_drawer(circuit, filename=fname, plot_barriers=True) + self.circuit_drawer(circuit, output="mpl", filename=fname, plot_barriers=True) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -447,7 +447,7 @@ def test_plot_barriers(self): FAILURE_PREFIX, ) fname2 = "plot_barriers_false.png" - self.circuit_drawer(circuit, filename=fname2, plot_barriers=False) + self.circuit_drawer(circuit, output="mpl", filename=fname2, plot_barriers=False) ratio2 = VisualTestUtilities._save_diff( self._image_path(fname2), @@ -470,7 +470,7 @@ def test_no_barriers_false(self): circuit.h(q1[1]) fname = "no_barriers.png" - self.circuit_drawer(circuit, filename=fname, plot_barriers=False) + self.circuit_drawer(circuit, output="mpl", filename=fname, plot_barriers=False) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -491,7 +491,7 @@ def test_fold_minus1(self): circuit.x(0) fname = "fold_minus1.png" - self.circuit_drawer(circuit, fold=-1, filename=fname) + self.circuit_drawer(circuit, output="mpl", fold=-1, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -512,7 +512,7 @@ def test_fold_4(self): circuit.x(0) fname = "fold_4.png" - self.circuit_drawer(circuit, fold=4, filename=fname) + self.circuit_drawer(circuit, output="mpl", fold=4, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -549,7 +549,7 @@ def test_big_gates(self): circuit.append(Isometry(np.eye(4, 4), 0, 0), list(range(3, 5))) fname = "big_gates.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -572,7 +572,7 @@ def test_cnot(self): circuit.append(MCXVChain(3, dirty_ancillas=True), [qr[0], qr[1], qr[2], qr[3], qr[5]]) fname = "cnot.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -594,7 +594,7 @@ def test_cz(self): circuit.append(ZGate().control(1, ctrl_state="0", label="CZ Gate"), [2, 3]) fname = "cz.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -624,7 +624,7 @@ def test_pauli_clifford(self): circuit.dcx(3, 4) fname = "pauli_clifford.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -645,7 +645,9 @@ def test_creg_initial(self): circuit.x(1) fname = "creg_initial_true.png" - self.circuit_drawer(circuit, filename=fname, cregbundle=True, initial_state=True) + self.circuit_drawer( + circuit, output="mpl", filename=fname, cregbundle=True, initial_state=True + ) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -655,7 +657,9 @@ def test_creg_initial(self): FAILURE_PREFIX, ) fname2 = "creg_initial_false.png" - self.circuit_drawer(circuit, filename=fname2, cregbundle=False, initial_state=False) + self.circuit_drawer( + circuit, output="mpl", filename=fname2, cregbundle=False, initial_state=False + ) ratio2 = VisualTestUtilities._save_diff( self._image_path(fname2), @@ -682,7 +686,7 @@ def test_r_gates(self): circuit.rzz(pi / 2, 2, 3) fname = "r_gates.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -706,7 +710,7 @@ def test_ctrl_labels(self): ) fname = "ctrl_labels.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -725,7 +729,7 @@ def test_cswap_rzz(self): circuit.append(RZZGate(3 * pi / 4).control(3, ctrl_state="010"), [2, 1, 4, 3, 0]) fname = "cswap_rzz.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -749,7 +753,7 @@ def test_ghz_to_gate(self): circuit.append(ccghz, [4, 0, 1, 3, 2]) fname = "ghz_to_gate.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -767,7 +771,7 @@ def test_scale(self): circuit.unitary(random_unitary(2**5), circuit.qubits) fname = "scale_default.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -777,7 +781,7 @@ def test_scale(self): FAILURE_PREFIX, ) fname2 = "scale_half.png" - self.circuit_drawer(circuit, filename=fname2, scale=0.5) + self.circuit_drawer(circuit, output="mpl", filename=fname2, scale=0.5) ratio2 = VisualTestUtilities._save_diff( self._image_path(fname2), @@ -788,7 +792,7 @@ def test_scale(self): ) fname3 = "scale_double.png" - self.circuit_drawer(circuit, filename=fname3, scale=2) + self.circuit_drawer(circuit, output="mpl", filename=fname3, scale=2) ratio3 = VisualTestUtilities._save_diff( self._image_path(fname3), @@ -809,7 +813,7 @@ def test_pi_param_expr(self): circuit.rx((pi - x) * (pi - y), 0) fname = "pi_in_param_expr.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -835,7 +839,7 @@ def test_partial_layout(self): ) fname = "partial_layout.png" - self.circuit_drawer(transpiled, filename=fname) + self.circuit_drawer(transpiled, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -854,7 +858,7 @@ def test_init_reset(self): circuit.initialize([0, 1, 0, 0], [0, 1]) fname = "init_reset.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -871,7 +875,7 @@ def test_with_global_phase(self): circuit.h(range(3)) fname = "global_phase.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -924,7 +928,7 @@ def test_alternative_colors(self): else: ref_fname = fname - self.circuit_drawer(circuit, style={"name": style}, filename=fname) + self.circuit_drawer(circuit, output="mpl", style={"name": style}, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -946,7 +950,7 @@ def test_reverse_bits(self): circuit.ccx(2, 1, 0) fname = "reverse_bits.png" - self.circuit_drawer(circuit, reverse_bits=True, filename=fname) + self.circuit_drawer(circuit, output="mpl", reverse_bits=True, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -969,7 +973,7 @@ def test_bw(self): circuit.measure_all() fname = "bw.png" - self.circuit_drawer(circuit, style={"name": "bw"}, filename=fname) + self.circuit_drawer(circuit, output="mpl", style={"name": "bw"}, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1012,6 +1016,7 @@ def test_user_style(self): fname = "user_style.png" self.circuit_drawer( circuit, + output="mpl", style={ "name": "user_style", "displaytext": {"H2": "H_2"}, @@ -1039,7 +1044,7 @@ def test_subfont_change(self): style = {"name": "iqx", "subfontsize": 11} fname = "subfont.png" - self.circuit_drawer(circuit, style=style, filename=fname) + self.circuit_drawer(circuit, output="mpl", style=style, filename=fname) self.assertEqual(style, {"name": "iqx", "subfontsize": 11}) # check does not change style ratio = VisualTestUtilities._save_diff( @@ -1061,7 +1066,7 @@ def test_meas_condition(self): circuit.h(qr[1]).c_if(cr, 1) fname = "meas_condition.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1087,7 +1092,9 @@ def test_reverse_bits_condition(self): circuit.x(2).c_if(cr, 2) fname = "reverse_bits_cond_true.png" - self.circuit_drawer(circuit, cregbundle=False, reverse_bits=True, filename=fname) + self.circuit_drawer( + circuit, output="mpl", cregbundle=False, reverse_bits=True, filename=fname + ) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1097,7 +1104,9 @@ def test_reverse_bits_condition(self): FAILURE_PREFIX, ) fname2 = "reverse_bits_cond_false.png" - self.circuit_drawer(circuit, cregbundle=False, reverse_bits=False, filename=fname2) + self.circuit_drawer( + circuit, output="mpl", cregbundle=False, reverse_bits=False, filename=fname2 + ) ratio2 = VisualTestUtilities._save_diff( self._image_path(fname2), @@ -1131,6 +1140,7 @@ def cnotnot(gate_label): fname = "style_custom_gates.png" self.circuit_drawer( circuit, + output="mpl", style={ "displaycolor": {"CNOTNOT": ("#000000", "#FFFFFF"), "h": ("#A1A1A1", "#043812")}, "displaytext": {"CNOTNOT_PRIME": "$\\mathrm{CNOTNOT}'$"}, @@ -1157,6 +1167,7 @@ def test_6095(self): fname = "6095.png" self.circuit_drawer( circuit, + output="mpl", style={"displaycolor": {"cp": ("#A27486", "#000000"), "h": ("#A27486", "#000000")}}, filename=fname, ) @@ -1179,7 +1190,7 @@ def test_instruction_1q_1c(self): circuit.append(inst, [qr[0]], [cr[0]]) fname = "instruction_1q_1c.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1200,7 +1211,7 @@ def test_instruction_3q_3c_circ1(self): circuit.append(inst, [qr[0], qr[1], qr[2]], [cr2[0], cr[0], cr[1]]) fname = "instruction_3q_3c_circ1.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1221,7 +1232,7 @@ def test_instruction_3q_3c_circ2(self): circuit.append(inst, [qr[3], qr[0], qr[2]], [cr[0], cr[1], cr2[0]]) fname = "instruction_3q_3c_circ2.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1243,7 +1254,7 @@ def test_instruction_3q_3c_circ3(self): circuit.append(inst, [qr[3], qr[1], qr[2]], [cr3[1], cr[1], cr3[0]]) fname = "instruction_3q_3c_circ3.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1262,7 +1273,7 @@ def test_overwide_gates(self): circuit.initialize(initial_state) fname = "wide_params.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1284,7 +1295,7 @@ def test_one_bit_regs(self): circuit.measure(0, 0) fname = "one_bit_regs.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1313,7 +1324,7 @@ def test_user_ax_subplot(self): plt.close(fig) fname = "user_ax.png" - self.circuit_drawer(circuit, ax=ax2, filename=fname) + self.circuit_drawer(circuit, output="mpl", ax=ax2, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1334,7 +1345,7 @@ def test_figwidth(self): circuit.x(2) fname = "figwidth.png" - self.circuit_drawer(circuit, style={"figwidth": 5}, filename=fname) + self.circuit_drawer(circuit, output="mpl", style={"figwidth": 5}, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1353,7 +1364,7 @@ def test_registerless_one_bit(self): circuit = QuantumCircuit(qrx, [Qubit(), Qubit()], qry, [Clbit(), Clbit()], crx) fname = "registerless_one_bit.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1377,7 +1388,7 @@ def test_measures_with_conditions(self): circuit.h(0).c_if(cr2, 3) fname = "measure_cond_false.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1387,7 +1398,7 @@ def test_measures_with_conditions(self): FAILURE_PREFIX, ) fname2 = "measure_cond_true.png" - self.circuit_drawer(circuit, cregbundle=True, filename=fname2) + self.circuit_drawer(circuit, output="mpl", cregbundle=True, filename=fname2) ratio2 = VisualTestUtilities._save_diff( self._image_path(fname2), @@ -1410,7 +1421,7 @@ def test_conditions_measures_with_bits(self): circuit.measure(0, bits[3]) fname = "measure_cond_bits_false.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1420,7 +1431,7 @@ def test_conditions_measures_with_bits(self): FAILURE_PREFIX, ) fname2 = "measure_cond_bits_true.png" - self.circuit_drawer(circuit, cregbundle=True, filename=fname2) + self.circuit_drawer(circuit, output="mpl", cregbundle=True, filename=fname2) ratio2 = VisualTestUtilities._save_diff( self._image_path(fname2), @@ -1444,7 +1455,7 @@ def test_conditional_gates_right_of_measures_with_bits(self): circuit.h(qr[2]).c_if(cr[0], 0) fname = "measure_cond_bits_right.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1464,7 +1475,9 @@ def test_conditions_with_bits_reverse(self): circuit.x(0).c_if(bits[3], 0) fname = "cond_bits_reverse.png" - self.circuit_drawer(circuit, cregbundle=False, reverse_bits=True, filename=fname) + self.circuit_drawer( + circuit, output="mpl", cregbundle=False, reverse_bits=True, filename=fname + ) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1483,7 +1496,7 @@ def test_sidetext_with_condition(self): circuit.append(CPhaseGate(pi / 2), [qr[0], qr[1]]).c_if(cr[1], 1) fname = "sidetext_condition.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1518,7 +1531,7 @@ def test_fold_with_conditions(self): circuit.append(U1Gate(0).control(1), [1, 0]).c_if(cr, 31) fname = "fold_with_conditions.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1536,7 +1549,7 @@ def test_idle_wires_barrier(self): circuit.barrier() fname = "idle_wires_barrier.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1561,6 +1574,7 @@ def test_wire_order(self): fname = "wire_order.png" self.circuit_drawer( circuit, + output="mpl", cregbundle=False, wire_order=[2, 1, 3, 0, 6, 8, 9, 5, 4, 7], filename=fname, @@ -1586,7 +1600,7 @@ def test_barrier_label(self): circuit.barrier(label="End Y/X") fname = "barrier_label.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1608,7 +1622,7 @@ def test_if_op(self): circuit.cx(0, 1) fname = "if_op.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1632,7 +1646,7 @@ def test_if_else_op_bundle_false(self): circuit.cx(0, 1) fname = "if_else_op_false.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1657,7 +1671,7 @@ def test_if_else_op_bundle_true(self): circuit.cx(0, 1) fname = "if_else_op_true.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=True, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1681,7 +1695,9 @@ def test_if_else_op_textbook_style(self): circuit.cx(0, 1) fname = "if_else_op_textbook.png" - self.circuit_drawer(circuit, style="textbook", cregbundle=False, filename=fname) + self.circuit_drawer( + circuit, output="mpl", style="textbook", cregbundle=False, filename=fname + ) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1715,7 +1731,7 @@ def test_if_else_with_body(self): circuit.x(0, label="X1i") fname = "if_else_body.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1756,7 +1772,7 @@ def test_if_else_op_nested(self): circuit.x(0) fname = "if_else_op_nested.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=True, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1797,7 +1813,13 @@ def test_if_else_op_wire_order(self): circuit.x(0) fname = "if_else_op_wire_order.png" - self.circuit_drawer(circuit, wire_order=[2, 0, 3, 1, 4, 5, 6], filename=fname) + self.circuit_drawer( + circuit, + output="mpl", + cregbundle=False, + wire_order=[2, 0, 3, 1, 4, 5, 6], + filename=fname, + ) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1838,7 +1860,7 @@ def test_if_else_op_fold(self): circuit.x(0) fname = "if_else_op_fold.png" - self.circuit_drawer(circuit, fold=7, filename=fname) + self.circuit_drawer(circuit, output="mpl", fold=7, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1865,7 +1887,7 @@ def test_while_loop_op(self): circuit.x(0) fname = "while_loop.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1894,7 +1916,7 @@ def test_for_loop_op(self): circuit.z(0) fname = "for_loop.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1923,7 +1945,7 @@ def test_for_loop_op_range(self): circuit.z(0) fname = "for_loop_range.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1951,7 +1973,7 @@ def test_for_loop_op_1_qarg(self): circuit.z(0) fname = "for_loop_1_qarg.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -1983,7 +2005,7 @@ def test_switch_case_op(self): circuit.h(0) fname = "switch_case.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -2011,7 +2033,7 @@ def test_switch_case_op_1_qarg(self): circuit.h(0) fname = "switch_case_1_qarg.png" - self.circuit_drawer(circuit, cregbundle=False, filename=fname) + self.circuit_drawer(circuit, output="mpl", cregbundle=False, filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -2036,7 +2058,7 @@ def test_if_with_expression(self): circuit.z(0) fname = "if_op_expr.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -2063,7 +2085,7 @@ def test_if_with_expression_nested(self): circuit.z(1) fname = "if_op_expr_nested.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -2091,7 +2113,7 @@ def test_switch_with_expression(self): circuit.cx(0, 1) fname = "switch_expr.png" - self.circuit_drawer(circuit, filename=fname) + self.circuit_drawer(circuit, output="mpl", filename=fname) ratio = VisualTestUtilities._save_diff( self._image_path(fname), @@ -2122,7 +2144,8 @@ def test_control_flow_layout(self): backend.target.add_instruction(SwitchCaseOp, name="switch_case") tqc = transpile(qc, backend, optimization_level=2, seed_transpiler=671_42) fname = "layout_control_flow.png" - self.circuit_drawer(tqc, filename=fname) + self.circuit_drawer(tqc, output="mpl", filename=fname) + ratio = VisualTestUtilities._save_diff( self._image_path(fname), self._reference_path(fname), @@ -2154,8 +2177,10 @@ def test_control_flow_nested_layout(self): backend.target.add_instruction(SwitchCaseOp, name="switch_case") backend.target.add_instruction(IfElseOp, name="if_else") tqc = transpile(qc, backend, optimization_level=2, seed_transpiler=671_42) + fname = "nested_layout_control_flow.png" - self.circuit_drawer(tqc, filename=fname) + self.circuit_drawer(tqc, output="mpl", filename=fname) + ratio = VisualTestUtilities._save_diff( self._image_path(fname), self._reference_path(fname), @@ -2184,6 +2209,31 @@ def test_iqx_pendingdeprecation(self): ): qc.draw("mpl", style=style) + def test_no_qreg_names_after_layout(self): + """Test that full register names are not shown after transpilation. + See https://github.com/Qiskit/qiskit-terra/issues/11038""" + backend = FakeBelemV2() + + qc = QuantumCircuit(3) + qc.cx(0, 1) + qc.cx(1, 2) + qc.cx(2, 0) + circuit = transpile( + qc, backend, basis_gates=["rz", "sx", "cx"], layout_method="sabre", seed_transpiler=42 + ) + + fname = "qreg_names_after_layout.png" + self.circuit_drawer(circuit, output="mpl", filename=fname) + + ratio = VisualTestUtilities._save_diff( + self._image_path(fname), + self._reference_path(fname), + fname, + FAILURE_DIFF_DIR, + FAILURE_PREFIX, + ) + self.assertGreaterEqual(ratio, 0.9999) + if __name__ == "__main__": unittest.main(verbosity=1) diff --git a/test/visual/mpl/graph/references/state_city.png b/test/visual/mpl/graph/references/state_city.png index e8351c01b510..990c433c9162 100644 Binary files a/test/visual/mpl/graph/references/state_city.png and b/test/visual/mpl/graph/references/state_city.png differ diff --git a/tools/deploy_translatable_strings.sh b/tools/deploy_translatable_strings.sh index e295bbb01be2..afac6869447d 100755 --- a/tools/deploy_translatable_strings.sh +++ b/tools/deploy_translatable_strings.sh @@ -14,74 +14,96 @@ # Script for pushing the translatable messages to poBranch. +# DO NOT `set -x`. We have to pass secrets to `openssl` on the command line, +# and we don't want them appearing in the log. This script instead manually +# 'echo's its status at various points. +set -eu -o pipefail + +if [[ "$#" -ne 1 ]]; then + echo "Usage: deploy_translatable_string.sh /path/to/translations/artifact" >&2 + exit 1 +fi # Variables used by this script. # From github actions the docs/locale/en directory from the sphinx build # gets downloaded from the github actions artifacts as deploy directory -TARGET_REPOSITORY="git@github.com:qiskit-community/qiskit-translations.git" -SOURCE_DIR=`pwd` -SOURCE_LANG='en' - -SOURCE_REPOSITORY="git@github.com:Qiskit/qiskit.git" -TARGET_BRANCH_PO="main" -DOC_DIR_PO="deploy" -TARGET_DOCS_DIR_PO="docs/locale" - -echo "show current dir: " -pwd - -echo "Setup ssh keys" -pwd -set -e -# Add poBranch push key to ssh-agent -openssl enc -aes-256-cbc -d -in ../tools/github_poBranch_update_key.enc -out github_poBranch_deploy_key -K $encrypted_deploy_po_branch_key -iv $encrypted_deploy_po_branch_iv -chmod 600 github_poBranch_deploy_key -eval $(ssh-agent -s) -ssh-add github_poBranch_deploy_key - -# Clone to the working repository for .po and pot files -popd -pwd -echo "git clone for working repo" -git clone --depth 1 $TARGET_REPOSITORY temp --single-branch --branch $TARGET_BRANCH_PO -pushd temp +TARGET_REPO="git@github.com:qiskit-community/qiskit-translations.git" +TARGET_REPO_BRANCH="main" + +SOURCE_TOOLS_DIR="$(dirname "$(realpath "$0")")" +# Absolute paths to the git repository roots for the source repository (which +# this file lives in) and where we're going to clone the target repository. +SOURCE_REPO_ROOT="$(dirname "$SOURCE_TOOLS_DIR")" +TARGET_REPO_ROOT="${SOURCE_REPO_ROOT}/_qiskit_translations" + +SOURCE_LANG="en" +# Absolute paths to the source and target directories for the translations +# files. CI should feed the source in for us - it depends on the particulars of +# how it was built in a previous job. The target is under our control. +SOURCE_PO_DIR="$1" +TARGET_PO_DIR="${TARGET_REPO_ROOT}/docs/locale/${SOURCE_LANG}" + +# Add the SSH key needed to verify ourselves when pushing to the target remote. +echo "+ setup ssh keys" +eval "$(ssh-agent -s)" +openssl enc -aes-256-cbc -d \ + -in "${SOURCE_REPO_ROOT}/tools/github_poBranch_update_key.enc" \ + -K "$encrypted_deploy_po_branch_key" \ + -iv "$encrypted_deploy_po_branch_iv" \ + | ssh-add - + +# Clone the target repository so we can build our commit in it. +echo "+ 'git clone' translations target repository" +git clone --depth 1 "$TARGET_REPO" "$TARGET_REPO_ROOT" --single-branch --branch "$TARGET_REPO_BRANCH" +pushd "$TARGET_REPO_ROOT" + +echo "+ setup git configuration for commit" git config user.name "Qiskit Autodeploy" git config user.email "qiskit@qiskit.org" -echo "git rm -rf for the translation po files" -git rm -rf --ignore-unmatch $TARGET_DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/*.po \ - $TARGET_DOCS_DIR_PO/$SOURCE_LANG/LC_MESSAGES/api \ - $TARGET_DOCS_DIR_PO/$SOURCE_LANG/LC_MESSAGES/apidoc \ - $TARGET_DOCS_DIR_PO/$SOURCE_LANG/LC_MESSAGES/apidoc_legacy \ - $TARGET_DOCS_DIR_PO/$SOURCE_LANG/LC_MESSAGES/theme \ - $TARGET_DOCS_DIR_PO/$SOURCE_LANG/LC_MESSAGES/_* - -# Remove api/ and apidoc/ to avoid confusion while translating -rm -rf $SOURCE_DIR/$DOC_DIR_PO/LC_MESSAGES/api/ \ - $SOURCE_DIR/$DOC_DIR_PO/LC_MESSAGES/apidoc/ \ - $SOURCE_DIR/$DOC_DIR_PO/LC_MESSAGES/apidoc_legacy/ \ - $SOURCE_DIR/$DOC_DIR_PO/LC_MESSAGES/stubs/ \ - $SOURCE_DIR/$DOC_DIR_PO/LC_MESSAGES/theme/ +echo "+ 'git rm' current translations files" +# Remove existing versions of the translations, to ensure deletions in the source repository are recognised. +git rm -rf --ignore-unmatch \ + "$TARGET_PO_DIR/LC_MESSAGES/"*.po \ + "$TARGET_PO_DIR/LC_MESSAGES/api" \ + "$TARGET_PO_DIR/LC_MESSAGES/apidoc" \ + "$TARGET_PO_DIR/LC_MESSAGES/apidoc_legacy" \ + "$TARGET_PO_DIR/LC_MESSAGES/theme" \ + "$TARGET_PO_DIR/LC_MESSAGES/"_* + +echo "+ 'rm' unwanted files from source documentation" +# Remove files from the deployment that we don't want translating. +rm -rf \ + "$SOURCE_PO_DIR/LC_MESSAGES/api/" \ + "$SOURCE_PO_DIR/LC_MESSAGES/apidoc/" \ + "$SOURCE_PO_DIR/LC_MESSAGES/apidoc_legacy/" \ + "$SOURCE_PO_DIR/LC_MESSAGES/stubs/" \ + "$SOURCE_PO_DIR/LC_MESSAGES/theme/" +echo "+ 'cp' wanted files from source to target" # Copy the new rendered files and add them to the commit. -echo "copy directory" -cp -r $SOURCE_DIR/$DOC_DIR_PO/. $TARGET_DOCS_DIR_PO/$SOURCE_LANG -cp $SOURCE_DIR/qiskit_pkg/setup.py . -cp $SOURCE_DIR/requirements-dev.txt . -# Append optional requirements to the dev list as some are needed for -# docs builds -cat $SOURCE_DIR/requirements-optionals.txt >> requirements-dev.txt -cp $SOURCE_DIR/constraints.txt . - -echo "add to po files to target dir" -git add docs/ -git add setup.py -git add requirements-dev.txt constraints.txt +cp -r "$SOURCE_PO_DIR/." "$TARGET_PO_DIR" +# Copy files necessary to build the Qiskit metapackage. +cp "$SOURCE_REPO_ROOT/qiskit_pkg/setup.py" "${TARGET_REPO_ROOT}" +cat "$SOURCE_REPO_ROOT/requirements-dev.txt" "$SOURCE_REPO_ROOT/requirements-optional.txt" \ + > "${TARGET_REPO_ROOT}/requirements-dev.txt" +cp "$SOURCE_REPO_ROOT/constraints.txt" "${TARGET_REPO_ROOT}" +# Add commit hash to be able to run the build with the commit hash before the actual release +echo $GITHUB_SHA > "${TARGET_REPO_ROOT}/qiskit-commit-hash" +echo "+ 'git add' files to target commit" +git add docs/ setup.py requirements-dev.txt constraints.txt qiskit-commit-hash + +echo "+ 'git commit' wanted files" # Commit and push the changes. -git commit -m "Automated documentation update to add .po files from qiskit" -m "skip ci" -m "Commit: $GITHUB_SHA" -m "Github Actions Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" -echo "git push" -git push --quiet origin $TARGET_BRANCH_PO +git commit \ + -m "Automated documentation update to add .po files from qiskit" \ + -m "skip ci" \ + -m "Commit: $GITHUB_SHA" \ + -m "Github Actions Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + +echo "+ 'git push' to target repository" +git push --quiet origin "$TARGET_REPO_BRANCH" echo "********** End of pushing po to working repo! *************" popd diff --git a/tox.ini b/tox.ini index fa6d1017d3e2..0c799d28ba9a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,10 @@ envlist = py38, py39, py310, py311, lint-incr isolated_build = true [testenv] -usedevelop = True +# We pretend that we're not actually installing the package, because we need tox to let us have two +# packages ('qiskit' and 'qiskit-terra') under test at the same time. For that, we have to stuff +# them into 'deps'. +skip_install = true install_command = pip install -c{toxinidir}/constraints.txt -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} @@ -15,9 +18,12 @@ setenv = QISKIT_TEST_CAPTURE_STREAMS=1 QISKIT_PARALLEL=FALSE passenv = RAYON_NUM_THREADS, OMP_NUM_THREADS, QISKIT_PARALLEL, RUST_BACKTRACE, SETUPTOOLS_ENABLE_FEATURES, QISKIT_TESTS, QISKIT_IN_PARALLEL -deps = setuptools_rust # This is work around for the bug of tox 3 (see #8606 for more details.) - -r{toxinidir}/requirements.txt - -r{toxinidir}/requirements-dev.txt +deps = + setuptools_rust # This is work around for the bug of tox 3 (see #8606 for more details.) + -r{toxinidir}/requirements.txt + -r{toxinidir}/requirements-dev.txt + -e . + -e ./qiskit_pkg commands = stestr run {posargs} @@ -50,6 +56,9 @@ commands = reno lint [testenv:black] +skip_install = true +deps = + -r requirements-dev.txt commands = black {posargs} qiskit test tools examples setup.py qiskit_pkg [testenv:coverage] @@ -57,17 +66,15 @@ basepython = python3 setenv = {[testenv]setenv} PYTHON=coverage3 run --source qiskit --parallel-mode -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/requirements-dev.txt - -r{toxinidir}/requirements-optional.txt +deps = + {[testenv]deps} + -r{toxinidir}/requirements-optional.txt commands = stestr run {posargs} coverage3 combine coverage3 report [testenv:docs] -# Editable mode breaks macOS: https://github.com/sphinx-doc/sphinx/issues/10943 -usedevelop = False basepython = python3 setenv = {[testenv]setenv} @@ -75,15 +82,9 @@ setenv = RUST_DEBUG=1 # Faster to compile. passenv = {[testenv]passenv}, QISKIT_DOCS_BUILD_TUTORIALS, QISKIT_CELL_TIMEOUT, DOCS_PROD_BUILD deps = - setuptools_rust # This is work around for the bug of tox 3 (see #8606 for more details.) - -r{toxinidir}/requirements-dev.txt + {[testenv]deps} -r{toxinidir}/requirements-optional.txt -r{toxinidir}/requirements-tutorials.txt - # Some optionals depend on Terra. We want to make sure pip satisfies that requirement from a local - # installation, not from PyPI. But Tox normally doesn't install the local installation until - # after `deps` is installed. So, instead, we tell pip to do the local installation at the same - # time as the optionals. See https://github.com/Qiskit/qiskit-terra/pull/9477. - . commands = sphinx-build -W -j auto -T --keep-going -b html docs/ docs/_build/html {posargs}