diff --git a/.azure/lint-linux.yml b/.azure/lint-linux.yml index 5855fe1e6e67..a1809bd829bd 100644 --- a/.azure/lint-linux.yml +++ b/.azure/lint-linux.yml @@ -26,7 +26,6 @@ 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. @@ -38,7 +37,7 @@ jobs: set -e source test-job/bin/activate echo "Running black, any errors reported can be fixed with 'tox -eblack'" - black --check qiskit test tools examples setup.py qiskit_pkg + black --check qiskit test tools examples setup.py echo "Running rustfmt check, any errors reported can be fixed with 'cargo fmt'" cargo fmt --check displayName: "Formatting" @@ -47,7 +46,7 @@ jobs: set -e source test-job/bin/activate echo "Running ruff" - ruff qiskit test tools examples setup.py qiskit_pkg/setup.py + ruff qiskit test tools examples setup.py echo "Running pylint" pylint -rn qiskit test tools echo "Running Cargo Clippy" diff --git a/.azure/test-linux.yml b/.azure/test-linux.yml index eb456f8497ad..512e1d773c35 100644 --- a/.azure/test-linux.yml +++ b/.azure/test-linux.yml @@ -74,12 +74,11 @@ jobs: 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*.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" @@ -92,7 +91,6 @@ 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. diff --git a/.azure/test-macos.yml b/.azure/test-macos.yml index cbf2fc8b0e08..c241f51f57e9 100644 --- a/.azure/test-macos.yml +++ b/.azure/test-macos.yml @@ -43,7 +43,6 @@ 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. diff --git a/.azure/test-windows.yml b/.azure/test-windows.yml index 6ba2e442946c..f546fdb41dc3 100644 --- a/.azure/test-windows.yml +++ b/.azure/test-windows.yml @@ -42,7 +42,6 @@ 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. diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 221840b4e7c6..de3485c5fdce 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 . ./qiskit_pkg + run: python -m pip install -c constraints.txt -e . env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Cinstrument-coverage" diff --git a/.github/workflows/neko.yml b/.github/workflows/neko.yml index bda60fd39790..30921c8b051b 100644 --- a/.github/workflows/neko.yml +++ b/.github/workflows/neko.yml @@ -16,4 +16,6 @@ jobs: - uses: Qiskit/qiskit-neko@main with: test_selection: terra - repo_install_command: "pip install -c constraints.txt ." + # We have to forcibly uninstall any old version of qiskit or qiskit-terra during the + # changeover, because it's not possible to safely upgrade an existing installation to 1.0. + repo_install_command: "pip uninstall qiskit qiskit-terra && pip install -c constraints.txt ." diff --git a/.github/workflows/slow.yml b/.github/workflows/slow.yml index 528305740d95..62fa9ec19c62 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 . ./qiskit_pkg + python -m pip install -c constraints.txt -e . python -m pip install "qiskit-aer" "z3-solver" "cplex" -c constraints.txt env: SETUPTOOLS_ENABLE_FEATURES: "legacy-editable" diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 336b86c8db52..99a46a97b63e 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -21,7 +21,7 @@ jobs: python-version: '3.10' - uses: dtolnay/rust-toolchain@stable - name: Build wheels - uses: pypa/cibuildwheel@v2.13.0 + uses: pypa/cibuildwheel@v2.16.2 - uses: actions/upload-artifact@v3 with: path: ./wheelhouse/*.whl @@ -42,7 +42,7 @@ jobs: python-version: '3.10' - uses: dtolnay/rust-toolchain@stable - name: Build wheels - uses: pypa/cibuildwheel@v2.13.0 + uses: pypa/cibuildwheel@v2.16.2 env: CIBW_BEFORE_ALL: rustup target add aarch64-apple-darwin CIBW_ARCHS_MACOS: arm64 universal2 @@ -91,7 +91,7 @@ jobs: with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.13.0 + uses: pypa/cibuildwheel@v2.16.2 env: CIBW_ARCHS_LINUX: s390x CIBW_TEST_SKIP: "cp*" @@ -124,7 +124,7 @@ jobs: with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.13.0 + uses: pypa/cibuildwheel@v2.16.2 env: CIBW_ARCHS_LINUX: ppc64le CIBW_TEST_SKIP: "cp*" @@ -157,7 +157,7 @@ jobs: with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.13.0 + uses: pypa/cibuildwheel@v2.16.2 env: CIBW_ARCHS_LINUX: aarch64 - uses: actions/upload-artifact@v3 @@ -168,7 +168,7 @@ jobs: with: packages-dir: wheelhouse/ sdist: - name: Build and publish terra sdist + name: Build and publish sdist runs-on: ${{ matrix.os }} needs: ["upload_shared_wheels"] environment: release @@ -185,36 +185,8 @@ jobs: with: python-version: '3.10' - name: Install deps - run: pip install -U twine setuptools-rust wheel build + run: pip install -U build - name: Build sdist run: python -m build . --sdist - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - metapackage: - name: Build and publish terra sdist - runs-on: ${{ matrix.os }} - needs: ["sdist"] - environment: release - permissions: - id-token: write - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest] - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - name: Install Python - with: - python-version: '3.10' - - name: Install deps - run: pip install -U twine setuptools-rust wheel build - - name: Build packages - run: | - set -e - cd qiskit_pkg - python -m build . - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: qiskit_pkg/dist diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87b434413449..e50d61f73c76 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -651,7 +651,7 @@ def test_method2(self): self.assertEqual(result, ) ``` -`test_method1_deprecated` can be removed after `Obj.method1` is removed (following the [Qiskit Deprecation Policy](https://qiskit.org/documentation/contributing_to_qiskit.html#deprecation-policy)). +`test_method1_deprecated` can be removed after `Obj.method1` is removed (following the [Qiskit Deprecation Policy](./DEPRECATION.md)). ## Using dependencies diff --git a/README.md b/README.md index 739a03ca4e8e..a153957cb221 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ It also contains a transpiler that supports optimizing quantum circuits and a qu For more details on how to use Qiskit, refer to the documentation located here: - + ## Installation diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1c85874e9f10..8fc21b7d8db1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,7 +31,7 @@ parameters: - name: "supportedPythonVersions" displayName: "All supported versions of Python" type: object - default: ["3.8", "3.9", "3.10", "3.11"] + default: ["3.8", "3.9", "3.10", "3.11", "3.12"] - name: "minimumPythonVersion" displayName: "Minimum supported version of Python" @@ -41,7 +41,7 @@ parameters: - name: "maximumPythonVersion" displayName: "Maximum supported version of Python" type: string - default: "3.11" + default: "3.12" # These two versions of Python can be chosen somewhat arbitrarily, but we get # slightly better coverage per PR if they're neither the maximum nor minimum diff --git a/constraints.txt b/constraints.txt index 07b9862e426c..a879e5945844 100644 --- a/constraints.txt +++ b/constraints.txt @@ -2,17 +2,7 @@ # 4.0+. The pin can be removed after nbformat is updated. jsonschema==3.2.0 -# Numpy 1.25 deprecated some behaviours that we used, and caused the isometry -# tests to flake. See https://github.com/Qiskit/qiskit-terra/issues/10305, -# remove pin when resolving that. -numpy<1.25 - # Scipy 1.11 seems to have caused an instability in the Weyl coordinates # 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 +scipy<1.11; python_version<'3.12' diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 1a37528dbc8e..8fe0eaf87b45 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -380,22 +380,26 @@ fn circuit_rr( if !simplify { atol = -1.0; } - if theta.abs() < atol && phi.abs() < atol && lam.abs() < atol { - return OneQubitGateSequence { - gates: circuit, - global_phase: phase, - }; - } - if (theta - PI).abs() > atol { + + if mod_2pi((phi + lam) / 2., atol).abs() < atol { + // This can be expressed as a single R gate + if theta.abs() > atol { + circuit.push((String::from("r"), vec![theta, mod_2pi(PI / 2. + phi, atol)])); + } + } else { + // General case: use two R gates + if (theta - PI).abs() > atol { + circuit.push(( + String::from("r"), + vec![theta - PI, mod_2pi(PI / 2. - lam, atol)], + )); + } circuit.push(( String::from("r"), - vec![theta - PI, mod_2pi(PI / 2. - lam, atol)], + vec![PI, mod_2pi(0.5 * (phi - lam + PI), atol)], )); } - circuit.push(( - String::from("r"), - vec![PI, mod_2pi(0.5 * (phi - lam + PI), atol)], - )); + OneQubitGateSequence { gates: circuit, global_phase: phase, diff --git a/crates/accelerate/src/quantum_circuit/circuit_data.rs b/crates/accelerate/src/quantum_circuit/circuit_data.rs index 6d7dbbb03793..f7a8c02740aa 100644 --- a/crates/accelerate/src/quantum_circuit/circuit_data.rs +++ b/crates/accelerate/src/quantum_circuit/circuit_data.rs @@ -460,7 +460,7 @@ impl CircuitData { } // Note: we also rely on this to make us iterable! - pub fn __getitem__<'py>(&self, py: Python<'py>, index: &PyAny) -> PyResult { + pub fn __getitem__(&self, py: Python, index: &PyAny) -> PyResult { // Internal helper function to get a specific // instruction by index. fn get_at( @@ -491,7 +491,7 @@ impl CircuitData { } } - pub fn __delitem__(&mut self, py: Python<'_>, index: SliceOrInt) -> PyResult<()> { + pub fn __delitem__(&mut self, index: SliceOrInt) -> PyResult<()> { match index { SliceOrInt::Slice(slice) => { let slice = { @@ -504,7 +504,7 @@ impl CircuitData { s }; for i in slice.into_iter() { - self.__delitem__(py, SliceOrInt::Int(i))?; + self.__delitem__(SliceOrInt::Int(i))?; } Ok(()) } @@ -545,7 +545,7 @@ impl CircuitData { } for (i, v) in slice.iter().zip(values.iter()) { - self.__setitem__(py, SliceOrInt::Int(*i), *v)?; + self.__setitem__(py, SliceOrInt::Int(*i), v)?; } if slice.len() > values.len() { @@ -556,7 +556,7 @@ impl CircuitData { indices.stop, 1isize, ); - self.__delitem__(py, SliceOrInt::Slice(slice))?; + self.__delitem__(SliceOrInt::Slice(slice))?; } else { // Insert any extra values. for v in values.iter().skip(slice.len()).rev() { @@ -593,7 +593,7 @@ impl CircuitData { let index = index.unwrap_or_else(|| std::cmp::max(0, self.data.len() as isize - 1).into_py(py)); let item = self.__getitem__(py, index.as_ref(py))?; - self.__delitem__(py, index.as_ref(py).extract()?)?; + self.__delitem__(index.as_ref(py).extract()?)?; Ok(item) } diff --git a/crates/accelerate/src/sabre_swap/mod.rs b/crates/accelerate/src/sabre_swap/mod.rs index d4345a02e2c1..2d4dc40481e1 100644 --- a/crates/accelerate/src/sabre_swap/mod.rs +++ b/crates/accelerate/src/sabre_swap/mod.rs @@ -171,21 +171,35 @@ fn populate_extended_set( let mut decremented: IndexMap = IndexMap::with_hasher(ahash::RandomState::default()); let mut i = 0; + let mut visit_now: Vec = Vec::new(); while i < to_visit.len() && extended_set.len() < EXTENDED_SET_SIZE { - for edge in dag.dag.edges_directed(to_visit[i], Direction::Outgoing) { - let successor_node = edge.target(); - let successor_index = successor_node.index(); - *decremented.entry(successor_index).or_insert(0) += 1; - required_predecessors[successor_index] -= 1; - if required_predecessors[successor_index] == 0 { - if !dag.node_blocks.contains_key(&successor_index) { - if let [a, b] = dag.dag[successor_node].qubits[..] { - extended_set.push([a.to_phys(layout), b.to_phys(layout)]); + // Visit runs of non-2Q gates fully before moving on to children + // of 2Q gates. This way, traversal order is a BFS of 2Q gates rather + // than of all gates. + visit_now.push(to_visit[i]); + let mut j = 0; + while let Some(node) = visit_now.get(j) { + for edge in dag.dag.edges_directed(*node, Direction::Outgoing) { + let successor_node = edge.target(); + let successor_index = successor_node.index(); + *decremented.entry(successor_index).or_insert(0) += 1; + required_predecessors[successor_index] -= 1; + if required_predecessors[successor_index] == 0 { + if !dag.dag[successor_node].directive + && !dag.node_blocks.contains_key(&successor_index) + { + if let [a, b] = dag.dag[successor_node].qubits[..] { + extended_set.push([a.to_phys(layout), b.to_phys(layout)]); + to_visit.push(successor_node); + continue; + } } + visit_now.push(successor_node); } - to_visit.push(successor_node); } + j += 1; } + visit_now.clear(); i += 1; } for (node, amount) in decremented.iter() { @@ -582,41 +596,44 @@ fn route_reachable_nodes( let node_id = to_visit[i]; let node = &dag.dag[node_id]; i += 1; - - match dag.node_blocks.get(&node.py_node_id) { - Some(blocks) => { - // Control flow op. Route all blocks for current layout. - let mut block_results: Vec = Vec::with_capacity(blocks.len()); - for inner_dag in blocks { - let (inner_dag_routed, inner_final_layout) = route_block_dag(inner_dag, layout); - // For now, we always append a swap circuit that gets the inner block - // back to the parent's layout. - let swap_epilogue = - gen_swap_epilogue(coupling, inner_final_layout, layout, seed); - let block_result = BlockResult { - result: inner_dag_routed, - swap_epilogue, - }; - block_results.push(block_result); + // If the node is a directive that means it can be placed anywhere + if !node.directive { + match dag.node_blocks.get(&node.py_node_id) { + Some(blocks) => { + // Control flow op. Route all blocks for current layout. + let mut block_results: Vec = Vec::with_capacity(blocks.len()); + for inner_dag in blocks { + let (inner_dag_routed, inner_final_layout) = + route_block_dag(inner_dag, layout); + // For now, we always append a swap circuit that gets the inner block + // back to the parent's layout. + let swap_epilogue = + gen_swap_epilogue(coupling, inner_final_layout, layout, seed); + let block_result = BlockResult { + result: inner_dag_routed, + swap_epilogue, + }; + block_results.push(block_result); + } + node_block_results.insert_unique_unchecked(node.py_node_id, block_results); } - node_block_results.insert_unique_unchecked(node.py_node_id, block_results); + None => match node.qubits[..] { + // A gate op whose connectivity must match the device to be + // placed in the gate order. + [a, b] + if !coupling.contains_edge( + NodeIndex::new(a.to_phys(layout).index()), + NodeIndex::new(b.to_phys(layout).index()), + ) => + { + // 2Q op that cannot be placed. Add it to the front layer + // and move on. + front_layer.insert(node_id, [a.to_phys(layout), b.to_phys(layout)]); + continue; + } + _ => {} + }, } - None => match node.qubits[..] { - // A gate op whose connectivity must match the device to be - // placed in the gate order. - [a, b] - if !coupling.contains_edge( - NodeIndex::new(a.to_phys(layout).index()), - NodeIndex::new(b.to_phys(layout).index()), - ) => - { - // 2Q op that cannot be placed. Add it to the front layer - // and move on. - front_layer.insert(node_id, [a.to_phys(layout), b.to_phys(layout)]); - continue; - } - _ => {} - }, } gate_order.push(node.py_node_id); diff --git a/crates/accelerate/src/sabre_swap/sabre_dag.rs b/crates/accelerate/src/sabre_swap/sabre_dag.rs index 7178c8570aea..037470b9b79d 100644 --- a/crates/accelerate/src/sabre_swap/sabre_dag.rs +++ b/crates/accelerate/src/sabre_swap/sabre_dag.rs @@ -23,6 +23,7 @@ use crate::nlayout::VirtualQubit; pub struct DAGNode { pub py_node_id: usize, pub qubits: Vec, + pub directive: bool, } /// A DAG representation of the logical circuit to be routed. This represents the same dataflow @@ -41,7 +42,7 @@ pub struct SabreDAG { pub num_clbits: usize, pub dag: DiGraph, pub first_layer: Vec, - pub nodes: Vec<(usize, Vec, HashSet)>, + pub nodes: Vec<(usize, Vec, HashSet, bool)>, pub node_blocks: HashMap>, } @@ -52,7 +53,7 @@ impl SabreDAG { pub fn new( num_qubits: usize, num_clbits: usize, - nodes: Vec<(usize, Vec, HashSet)>, + nodes: Vec<(usize, Vec, HashSet, bool)>, node_blocks: HashMap>, ) -> PyResult { let mut qubit_pos: Vec> = vec![None; num_qubits]; @@ -65,6 +66,7 @@ impl SabreDAG { let gate_index = dag.add_node(DAGNode { py_node_id: node.0, qubits: qargs.clone(), + directive: node.3, }); let mut is_front = true; for x in qargs { @@ -118,12 +120,24 @@ mod test { #[test] fn no_panic_on_bad_qubits() { let bad_qubits = vec![VirtualQubit::new(0), VirtualQubit::new(2)]; - assert!(SabreDAG::new(2, 0, vec![(0, bad_qubits, HashSet::new())], HashMap::new()).is_err()) + assert!(SabreDAG::new( + 2, + 0, + vec![(0, bad_qubits, HashSet::new(), false)], + HashMap::new() + ) + .is_err()) } #[test] fn no_panic_on_bad_clbits() { let good_qubits = vec![VirtualQubit::new(0), VirtualQubit::new(1)]; - assert!(SabreDAG::new(2, 1, vec![(0, good_qubits, [0, 1].into())], HashMap::new()).is_err()) + assert!(SabreDAG::new( + 2, + 1, + vec![(0, good_qubits, [0, 1].into(), false)], + HashMap::new() + ) + .is_err()) } } diff --git a/docs/api_redirects.txt b/docs/api_redirects.txt index dd0bf35b1a9b..ff642bb8a94c 100644 --- a/docs/api_redirects.txt +++ b/docs/api_redirects.txt @@ -1,7 +1,3 @@ -qiskit.algorithms.AlgorithmError algorithms -qiskit.algorithms.eval_observables algorithms -qiskit.algorithms.estimate_observables algorithms - qiskit.assembler.assemble_circuits assembler qiskit.assembler.assemble_schedules assembler qiskit.assembler.disassemble assembler @@ -97,11 +93,6 @@ qiskit.converters.dagdependency_to_dag converters qiskit.dagcircuit.DAGCircuitError dagcircuit -qiskit.opflow.commutator opflow -qiskit.opflow.anti_commutator opflow -qiskit.opflow.double_commutator opflow -qiskit.opflow.OpflowError opflow - qiskit.providers.QiskitBackendNotFoundError providers qiskit.providers.BackendPropertyError providers qiskit.providers.JobError providers diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst index e957cabb12f3..9d1eb2f64960 100644 --- a/docs/apidoc/index.rst +++ b/docs/apidoc/index.rst @@ -30,7 +30,6 @@ API Reference primitives qasm2 qasm3 - qasm qobj qpy quantum_info @@ -44,17 +43,4 @@ API Reference transpiler_synthesis_plugins transpiler_builtin_plugins utils - utils_mitigation exceptions - -Deprecated Modules -================== - -.. warning:: - - These modules are going to be removed in Qiskit 1.0. Consider pinning ``qiskit~=0.45`` in your dependencies if you need them. - -.. toctree:: - :maxdepth: 1 - - opflow diff --git a/docs/apidoc/opflow.rst b/docs/apidoc/opflow.rst deleted file mode 100644 index f208e883a6d0..000000000000 --- a/docs/apidoc/opflow.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-opflow: - -.. automodule:: qiskit.opflow - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/docs/apidoc/qasm.rst b/docs/apidoc/qasm.rst deleted file mode 100644 index c1fea25947bb..000000000000 --- a/docs/apidoc/qasm.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-qasm: - -.. automodule:: qiskit.qasm - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/docs/apidoc/utils_mitigation.rst b/docs/apidoc/utils_mitigation.rst deleted file mode 100644 index a8af5992fd6a..000000000000 --- a/docs/apidoc/utils_mitigation.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-utils-mitigation: - -.. automodule:: qiskit.utils.mitigation - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 2b9f94d280fd..52e04ec7a990 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -198,7 +198,7 @@ Tier 1 platforms are currently: * Linux x86_64 (distributions compatible with the `manylinux 2014 `__ packaging specification). - * macOS x86_64 (10.9 or newer) + * macOS x86_64 (10.12 or newer) * Windows 64 bit Tier 2 @@ -211,10 +211,6 @@ functioning Python environment. Tier 2 platforms are currently: - * Linux i686 (distributions compatible with the - `manylinux 2014 `__ packaging - specification) for Python < 3.10 - * Windows 32 bit for Python < 3.10 * Linux aarch64 (distributions compatible with the `manylinux 2014 `__ packaging specification) @@ -240,8 +236,8 @@ Tier 3 platforms are currently: * macOS arm64 (10.15 or newer) * Linux i686 (distributions compatible with the `manylinux 2014 `__ packaging - specification) for Python >= 3.10 - * Windows 32 bit for Python >= 3.10 + specification) + * Windows 32 bit Ready to get going?... ====================== diff --git a/docs/migration_guides/opflow_migration.rst b/docs/migration_guides/opflow_migration.rst index 3d0f7fa67d22..4b5da682ad8d 100644 --- a/docs/migration_guides/opflow_migration.rst +++ b/docs/migration_guides/opflow_migration.rst @@ -54,7 +54,7 @@ The functional equivalency can be roughly summarized as follows: * - Opflow Module - Alternative - * - Operators (:class:`~qiskit.opflow.OperatorBase`, :ref:`operator_globals`, + * - Operators (:class:`~qiskit.opflow.OperatorBase`, ``operator_globals``, :mod:`~qiskit.opflow.primitive_ops`, :mod:`~qiskit.opflow.list_ops`) - ``qiskit.quantum_info`` :ref:`Operators ` @@ -134,7 +134,7 @@ Operator Globals *Back to* `Contents`_ Opflow provided shortcuts to define common single qubit states, operators, and non-parametrized gates in the -:ref:`operator_globals` module. +``operator_globals`` module. These were mainly used for didactic purposes or quick prototyping, and can easily be replaced by their corresponding :mod:`~qiskit.quantum_info` class: :class:`~qiskit.quantum_info.Pauli`, :class:`~qiskit.quantum_info.Clifford` or diff --git a/pyproject.toml b/pyproject.toml index 25ff0a5dade7..172c0625d25b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ target-version = ['py38', 'py39', 'py310', 'py311'] manylinux-x86_64-image = "manylinux2014" manylinux-i686-image = "manylinux2014" skip = "pp* cp36-* cp37-* *musllinux*" -test-skip = "cp310-win32 cp310-manylinux_i686 cp311-win32 cp311-manylinux_i686" +test-skip = "*win32 *linux_i686" test-command = "python {project}/examples/python/stochastic_swap.py" # We need to use pre-built versions of Numpy and Scipy in the tests; they have a # tendency to crash if they're installed from source by `pip install`, and since @@ -25,6 +25,7 @@ environment = 'PATH="$PATH:$HOME/.cargo/bin" CARGO_NET_GIT_FETCH_WITH_CLI="true" repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel} && pipx run abi3audit --strict --report {wheel}" [tool.cibuildwheel.macos] +environment = "MACOSX_DEPLOYMENT_TARGET=10.12" repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} && pipx run abi3audit --strict --report {wheel}" [tool.cibuildwheel.windows] diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 219d305321f3..664cd8c57c5c 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -80,10 +80,6 @@ from qiskit.compiler import transpile, assemble, schedule, sequence from .version import __version__ -from .version import QiskitVersion - - -__qiskit_version__ = QiskitVersion() class AerWrapper: diff --git a/qiskit/circuit/classical/expr/__init__.py b/qiskit/circuit/classical/expr/__init__.py index d2cd4bc5044e..b2b4d138fca7 100644 --- a/qiskit/circuit/classical/expr/__init__.py +++ b/qiskit/circuit/classical/expr/__init__.py @@ -39,10 +39,11 @@ These objects are mutable and should not be reused in a different location without a copy. -The entry point from general circuit objects to the expression system is by wrapping the object -in a :class:`Var` node and associating a :class:`~.types.Type` with it. +The base for dynamic variables is the :class:`Var`, which can be either an arbitrarily typed runtime +variable, or a wrapper around a :class:`.Clbit` or :class:`.ClassicalRegister`. .. autoclass:: Var + :members: var, name Similarly, literals used in comparison (such as integers) should be lifted to :class:`Value` nodes with associated types. @@ -86,10 +87,18 @@ The functions and methods described in this section are a more user-friendly way to build the expression tree, while staying close to the internal representation. All these functions will automatically lift valid Python scalar values into corresponding :class:`Var` or :class:`Value` -objects, and will resolve any required implicit casts on your behalf. +objects, and will resolve any required implicit casts on your behalf. If you want to directly use +some scalar value as an :class:`Expr` node, you can manually :func:`lift` it yourself. .. autofunction:: lift +Typically you should create memory-owning :class:`Var` instances by using the +:meth:`.QuantumCircuit.add_var` method to declare them in some circuit context, since a +:class:`.QuantumCircuit` will not accept an :class:`Expr` that contains variables that are not +already declared in it, since it needs to know how to allocate the storage and how the variable will +be initialized. However, should you want to do this manually, you should use the low-level +:meth:`Var.new` call to safely generate a named variable for usage. + You can manually specify casts in cases where the cast is allowed in explicit form, but may be lossy (such as the cast of a higher precision :class:`~.types.Uint` to a lower precision one). diff --git a/qiskit/circuit/classical/expr/expr.py b/qiskit/circuit/classical/expr/expr.py index b9e9aad4a2b7..3adbacfd6926 100644 --- a/qiskit/circuit/classical/expr/expr.py +++ b/qiskit/circuit/classical/expr/expr.py @@ -31,6 +31,7 @@ import abc import enum import typing +import uuid from .. import types @@ -108,24 +109,56 @@ def __repr__(self): @typing.final class Var(Expr): - """A classical variable.""" + """A classical variable. - __slots__ = ("var",) + These variables take two forms: a new-style variable that owns its storage location and has an + associated name; and an old-style variable that wraps a :class:`.Clbit` or + :class:`.ClassicalRegister` instance that is owned by some containing circuit. In general, + construction of variables for use in programs should use :meth:`Var.new` or + :meth:`.QuantumCircuit.add_var`.""" + + __slots__ = ("var", "name") def __init__( - self, var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister, type: types.Type + self, + var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister | uuid.UUID, + type: types.Type, + *, + name: str | None = None, ): self.type = type self.var = var + """A reference to the backing data storage of the :class:`Var` instance. When lifting + old-style :class:`.Clbit` or :class:`.ClassicalRegister` instances into a :class:`Var`, + this is exactly the :class:`.Clbit` or :class:`.ClassicalRegister`. If the variable is a + new-style classical variable (one that owns its own storage separate to the old + :class:`.Clbit`/:class:`.ClassicalRegister` model), this field will be a :class:`~uuid.UUID` + to uniquely identify it.""" + self.name = name + """The name of the variable. This is required to exist if the backing :attr:`var` attribute + is a :class:`~uuid.UUID`, i.e. if it is a new-style variable, and must be ``None`` if it is + an old-style variable.""" + + @classmethod + def new(cls, name: str, type: types.Type) -> typing.Self: + """Generate a new named variable that owns its own backing storage.""" + return cls(uuid.uuid4(), type, name=name) def accept(self, visitor, /): return visitor.visit_var(self) def __eq__(self, other): - return isinstance(other, Var) and self.type == other.type and self.var == other.var + return ( + isinstance(other, Var) + and self.type == other.type + and self.var == other.var + and self.name == other.name + ) def __repr__(self): - return f"Var({self.var}, {self.type})" + if self.name is None: + return f"Var({self.var}, {self.type})" + return f"Var({self.var}, {self.type}, name='{self.name}')" @typing.final diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 84e3f344bf07..9fbe77a04f82 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -618,12 +618,11 @@ def repeat(self, n): @property def condition_bits(self) -> List[Clbit]: """Get Clbits in condition.""" + from qiskit.circuit.controlflow import condition_resources # pylint: disable=cyclic-import + if self.condition is None: return [] - if isinstance(self.condition[0], Clbit): - return [self.condition[0]] - else: # ClassicalRegister - return list(self.condition[0]) + return list(condition_resources(self.condition).clbits) @property def name(self): diff --git a/qiskit/circuit/library/arithmetic/integer_comparator.py b/qiskit/circuit/library/arithmetic/integer_comparator.py index 699e8c221ff3..1324d512ea6f 100644 --- a/qiskit/circuit/library/arithmetic/integer_comparator.py +++ b/qiskit/circuit/library/arithmetic/integer_comparator.py @@ -14,7 +14,6 @@ """Integer Comparator.""" from __future__ import annotations -import warnings import numpy as np from qiskit.circuit import QuantumCircuit, QuantumRegister, AncillaRegister @@ -100,16 +99,6 @@ def geq(self, geq: bool) -> None: self._invalidate() self._geq = geq - @property - def num_ancilla_qubits(self): - """Deprecated. Use num_ancillas instead.""" - warnings.warn( - "The IntegerComparator.num_ancilla_qubits property is deprecated " - "as of 0.16.0. It will be removed no earlier than 3 months after the release " - "date. You should use the num_ancillas property instead." - ) - return self.num_ancillas - @property def num_state_qubits(self) -> int: """The number of qubits encoding the state for the comparison. diff --git a/qiskit/circuit/library/blueprintcircuit.py b/qiskit/circuit/library/blueprintcircuit.py index 51f6e5826bfc..4244a1f2340f 100644 --- a/qiskit/circuit/library/blueprintcircuit.py +++ b/qiskit/circuit/library/blueprintcircuit.py @@ -122,11 +122,6 @@ def parameters(self) -> ParameterView: self._build() return super().parameters - def qasm(self, formatted=False, filename=None, encoding=None): - if not self._is_built: - self._build() - return super().qasm(formatted, filename, encoding) - def _append(self, instruction, _qargs=None, _cargs=None): if not self._is_built: self._build() diff --git a/qiskit/circuit/library/evolved_operator_ansatz.py b/qiskit/circuit/library/evolved_operator_ansatz.py index a3832bfb71a6..572213089451 100644 --- a/qiskit/circuit/library/evolved_operator_ansatz.py +++ b/qiskit/circuit/library/evolved_operator_ansatz.py @@ -20,7 +20,6 @@ from qiskit.circuit.parameter import Parameter from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.exceptions import QiskitError from qiskit.quantum_info import Operator, Pauli, SparsePauliOp from qiskit.synthesis.evolution import LieTrotter @@ -44,15 +43,14 @@ def __init__( ): """ Args: - operators (BaseOperator | OperatorBase | QuantumCircuit | list | None): The operators + operators (BaseOperator | QuantumCircuit | list | None): The operators to evolve. If a circuit is passed, we assume it implements an already evolved operator and thus the circuit is not evolved again. Can be a single operator (circuit) or a list of operators (and circuits). reps: The number of times to repeat the evolved operators. evolution (EvolutionBase | EvolutionSynthesis | None): A specification of which evolution synthesis to use for the - :class:`.PauliEvolutionGate`, if the operator is from :mod:`qiskit.quantum_info` - or an opflow converter object if the operator is from :mod:`qiskit.opflow`. + :class:`.PauliEvolutionGate`. Defaults to first order Trotterization. insert_barriers: Whether to insert barriers in between each evolution. name: The name of the circuit. @@ -113,13 +111,8 @@ def evolution(self): """The evolution converter used to compute the evolution. Returns: - EvolutionBase or EvolutionSynthesis: The evolution converter used to compute the evolution. + EvolutionSynthesis: The evolution converter used to compute the evolution. """ - if self._evolution is None: - # pylint: disable=cyclic-import - from qiskit.opflow import PauliTrotterEvolution - - return PauliTrotterEvolution() return self._evolution @@ -128,8 +121,7 @@ def evolution(self, evol) -> None: """Sets the evolution converter used to compute the evolution. Args: - evol (EvolutionBase | EvolutionSynthesis): An evolution synthesis object or - opflow converter object to construct the evolution. + evol (EvolutionSynthesis): An evolution synthesis object """ self._invalidate() self._evolution = evol @@ -147,7 +139,7 @@ def operators(self): def operators(self, operators=None) -> None: """Set the operators to be evolved. - operators (Optional[Union[OperatorBase, QuantumCircuit, list]): The operators to evolve. + operators (Optional[Union[QuantumCircuit, list]]): The operators to evolve. If a circuit is passed, we assume it implements an already evolved operator and thus the circuit is not evolved again. Can be a single operator (circuit) or a list of operators (and circuits). @@ -174,21 +166,10 @@ def preferred_init_points(self): return np.zeros(self.reps * len(self.operators), dtype=float) def _evolve_operator(self, operator, time): - from qiskit.opflow import OperatorBase, EvolutionBase # pylint: disable=cyclic-import from qiskit.circuit.library.hamiltonian_gate import HamiltonianGate - if isinstance(operator, OperatorBase): - if not isinstance(self.evolution, EvolutionBase): - raise QiskitError( - "If qiskit.opflow operators are evolved the evolution must be a " - f"qiskit.opflow.EvolutionBase, not a {type(self.evolution)}." - ) - - evolved = self.evolution.convert((time * operator).exp_i()) - return evolved.reduce().to_circuit() - # if the operator is specified as matrix use exact matrix exponentiation if isinstance(operator, Operator): gate = HamiltonianGate(operator, time) @@ -254,17 +235,11 @@ def _validate_prefix(parameter_prefix, operators): def _is_pauli_identity(operator): - from qiskit.opflow import PauliOp, PauliSumOp - - if isinstance(operator, PauliSumOp): - operator = operator.to_pauli_op() if isinstance(operator, SparsePauliOp): if len(operator.paulis) == 1: operator = operator.paulis[0] # check if the single Pauli is identity below else: return False - if isinstance(operator, PauliOp): - operator = operator.primitive if isinstance(operator, Pauli): return not np.any(np.logical_or(operator.x, operator.z)) return False diff --git a/qiskit/circuit/library/pauli_evolution.py b/qiskit/circuit/library/pauli_evolution.py index 3fa52c00569f..88f090f29a5d 100644 --- a/qiskit/circuit/library/pauli_evolution.py +++ b/qiskit/circuit/library/pauli_evolution.py @@ -50,7 +50,10 @@ class PauliEvolutionGate(Gate): from qiskit.circuit import QuantumCircuit from qiskit.circuit.library import PauliEvolutionGate - from qiskit.opflow import I, Z, X + from qiskit.quantum_info import SparsePauliOp + + X = SparsePauliOp("X") + Z = SparsePauliOp("Z") # build the evolution gate operator = (Z ^ Z) - 0.1 * (X ^ I) @@ -86,7 +89,7 @@ def __init__( ) -> None: """ Args: - operator (Pauli | PauliOp | SparsePauliOp | PauliSumOp | list): + operator (Pauli | SparsePauliOp | list): The operator to evolve. Can also be provided as list of non-commuting operators where the elements are sums of commuting operators. For example: ``[XY + YX, ZZ + ZI + IZ, YY]``. @@ -147,22 +150,9 @@ def validate_parameter( def _to_sparse_pauli_op(operator): - """Cast the operator to a SparsePauliOp. + """Cast the operator to a SparsePauliOp.""" - For Opflow objects, return a global coefficient that must be multiplied to the evolution time. - Since this coefficient might contain unbound parameters it cannot be absorbed into the - coefficients of the SparsePauliOp. - """ - # pylint: disable=cyclic-import - from qiskit.opflow import PauliSumOp, PauliOp - - if isinstance(operator, PauliSumOp): - sparse_pauli = operator.primitive - sparse_pauli._coeffs *= operator.coeff - elif isinstance(operator, PauliOp): - sparse_pauli = SparsePauliOp(operator.primitive) - sparse_pauli._coeffs *= operator.coeff - elif isinstance(operator, Pauli): + if isinstance(operator, Pauli): sparse_pauli = SparsePauliOp(operator) elif isinstance(operator, SparsePauliOp): sparse_pauli = operator diff --git a/qiskit/circuit/library/phase_estimation.py b/qiskit/circuit/library/phase_estimation.py index 453afb4d19e7..ab23ee2218ab 100644 --- a/qiskit/circuit/library/phase_estimation.py +++ b/qiskit/circuit/library/phase_estimation.py @@ -44,7 +44,8 @@ class PhaseEstimation(QuantumCircuit): Cambridge University Press, New York, NY, USA. [3]: Qiskit - `textbook `_ + `textbook `_ """ diff --git a/qiskit/circuit/parameter.py b/qiskit/circuit/parameter.py index e354a708eace..42ffad5ebf0a 100644 --- a/qiskit/circuit/parameter.py +++ b/qiskit/circuit/parameter.py @@ -17,8 +17,9 @@ from uuid import uuid4, UUID +import symengine + from qiskit.circuit.exceptions import CircuitError -from qiskit.utils import optionals as _optionals from .parameterexpression import ParameterExpression @@ -75,14 +76,7 @@ def __init__( """ self._name = name self._uuid = uuid4() if uuid is None else uuid - if not _optionals.HAS_SYMENGINE: - from sympy import Symbol - - symbol = Symbol(name) - else: - import symengine - - symbol = symengine.Symbol(name) + symbol = symengine.Symbol(name) self._symbol_expr = symbol self._parameter_keys = frozenset((self._hash_key(),)) @@ -102,11 +96,7 @@ def assign(self, parameter, value): return value # This is the `super().bind` case, where we're required to return a `ParameterExpression`, # so we need to lift the given value to a symbolic expression. - if _optionals.HAS_SYMENGINE: - from symengine import sympify - else: - from sympy import sympify - return ParameterExpression({}, sympify(value)) + return ParameterExpression({}, symengine.sympify(value)) def subs(self, parameter_map: dict, allow_unknown_parameters: bool = False): """Substitute self with the corresponding parameter in ``parameter_map``.""" diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 6b88cede34e1..237d6e9d8007 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -20,9 +20,9 @@ import operator import numpy +import symengine from qiskit.circuit.exceptions import CircuitError -from qiskit.utils import optionals as _optionals # This type is redefined at the bottom to insert the full reference to "ParameterExpression", so it # can safely be used by runtime type-checkers like Sphinx. Mypy does not need this because it @@ -69,14 +69,9 @@ def _names(self) -> dict: def conjugate(self) -> "ParameterExpression": """Return the conjugate.""" - if _optionals.HAS_SYMENGINE: - import symengine - - conjugated = ParameterExpression( - self._parameter_symbols, symengine.conjugate(self._symbol_expr) - ) - else: - conjugated = ParameterExpression(self._parameter_symbols, self._symbol_expr.conjugate()) + conjugated = ParameterExpression( + self._parameter_symbols, symengine.conjugate(self._symbol_expr) + ) return conjugated def assign(self, parameter, value: ParameterValueType) -> "ParameterExpression": @@ -185,15 +180,7 @@ def subs( new_parameter_symbols = { p: s for p, s in self._parameter_symbols.items() if p not in parameter_map } - - if _optionals.HAS_SYMENGINE: - import symengine - - symbol_type = symengine.Symbol - else: - from sympy import Symbol - - symbol_type = Symbol + symbol_type = symengine.Symbol # If new_param is an expr, we'll need to construct a matching sympy expr # but with our sympy symbols instead of theirs. @@ -306,15 +293,7 @@ def gradient(self, param) -> Union["ParameterExpression", complex]: # Compute the gradient of the parameter expression w.r.t. param key = self._parameter_symbols[param] - if _optionals.HAS_SYMENGINE: - import symengine - - expr_grad = symengine.Derivative(self._symbol_expr, key) - else: - # TODO enable nth derivative - from sympy import Derivative - - expr_grad = Derivative(self._symbol_expr, key).doit() + expr_grad = symengine.Derivative(self._symbol_expr, key) # generate the new dictionary of symbols # this needs to be done since in the derivative some symbols might disappear (e.g. @@ -362,107 +341,50 @@ def __truediv__(self, other): def __rtruediv__(self, other): return self._apply_operation(operator.truediv, other, reflected=True) + def __pow__(self, other): + return self._apply_operation(pow, other) + + def __rpow__(self, other): + return self._apply_operation(pow, other, reflected=True) + def _call(self, ufunc): return ParameterExpression(self._parameter_symbols, ufunc(self._symbol_expr)) def sin(self): """Sine of a ParameterExpression""" - if _optionals.HAS_SYMENGINE: - import symengine - - return self._call(symengine.sin) - else: - from sympy import sin as _sin - - return self._call(_sin) + return self._call(symengine.sin) def cos(self): """Cosine of a ParameterExpression""" - if _optionals.HAS_SYMENGINE: - import symengine - - return self._call(symengine.cos) - else: - from sympy import cos as _cos - - return self._call(_cos) + return self._call(symengine.cos) def tan(self): """Tangent of a ParameterExpression""" - if _optionals.HAS_SYMENGINE: - import symengine - - return self._call(symengine.tan) - else: - from sympy import tan as _tan - - return self._call(_tan) + return self._call(symengine.tan) def arcsin(self): """Arcsin of a ParameterExpression""" - if _optionals.HAS_SYMENGINE: - import symengine - - return self._call(symengine.asin) - else: - from sympy import asin as _asin - - return self._call(_asin) + return self._call(symengine.asin) def arccos(self): """Arccos of a ParameterExpression""" - if _optionals.HAS_SYMENGINE: - import symengine - - return self._call(symengine.acos) - else: - from sympy import acos as _acos - - return self._call(_acos) + return self._call(symengine.acos) def arctan(self): """Arctan of a ParameterExpression""" - if _optionals.HAS_SYMENGINE: - import symengine - - return self._call(symengine.atan) - else: - from sympy import atan as _atan - - return self._call(_atan) + return self._call(symengine.atan) def exp(self): """Exponential of a ParameterExpression""" - if _optionals.HAS_SYMENGINE: - import symengine - - return self._call(symengine.exp) - else: - from sympy import exp as _exp - - return self._call(_exp) + return self._call(symengine.exp) def log(self): """Logarithm of a ParameterExpression""" - if _optionals.HAS_SYMENGINE: - import symengine - - return self._call(symengine.log) - else: - from sympy import log as _log - - return self._call(_log) + return self._call(symengine.log) def sign(self): """Sign of a ParameterExpression""" - if _optionals.HAS_SYMENGINE: - import symengine - - return self._call(symengine.sign) - else: - from sympy import sign as _sign - - return self._call(_sign) + return self._call(symengine.sign) def __repr__(self): return f"{self.__class__.__name__}({str(self)})" @@ -494,24 +416,21 @@ def __float__(self): "ParameterExpression with unbound parameters ({}) " "cannot be cast to a float.".format(self.parameters) ) from None - try: - # In symengine, if an expression was complex at any time, its type is likely to have - # stayed "complex" even when the imaginary part symbolically (i.e. exactly) - # cancelled out. Sympy tends to more aggressively recognise these as symbolically - # real. This second attempt at a cast is a way of unifying the behaviour to the - # more expected form for our users. - cval = complex(self) - if cval.imag == 0.0: - return cval.real - except TypeError: - pass + # In symengine, if an expression was complex at any time, its type is likely to have + # stayed "complex" even when the imaginary part symbolically (i.e. exactly) + # cancelled out. Sympy tends to more aggressively recognise these as symbolically + # real. This second attempt at a cast is a way of unifying the behaviour to the + # more expected form for our users. + cval = complex(self) + if cval.imag == 0.0: + return cval.real raise TypeError("could not cast expression to float") from exc def __int__(self): try: return int(self._symbol_expr) - # TypeError is for sympy, RuntimeError for symengine - except (TypeError, RuntimeError) as exc: + # TypeError is for backwards compatibility, RuntimeError is raised by symengine + except RuntimeError as exc: if self.parameters: raise TypeError( "ParameterExpression with unbound parameters ({}) " @@ -530,14 +449,7 @@ def __deepcopy__(self, memo=None): def __abs__(self): """Absolute of a ParameterExpression""" - if _optionals.HAS_SYMENGINE: - import symengine - - return self._call(symengine.Abs) - else: - from sympy import Abs as _abs - - return self._call(_abs) + return self._call(symengine.Abs) def abs(self): """Absolute of a ParameterExpression""" @@ -555,12 +467,9 @@ def __eq__(self, other): if isinstance(other, ParameterExpression): if self.parameters != other.parameters: return False - if _optionals.HAS_SYMENGINE: - from sympy import sympify + from sympy import sympify - return sympify(self._symbol_expr).equals(sympify(other._symbol_expr)) - else: - return self._symbol_expr.equals(other._symbol_expr) + return sympify(self._symbol_expr).equals(sympify(other._symbol_expr)) elif isinstance(other, numbers.Number): return len(self.parameters) == 0 and complex(self._symbol_expr) == other return False @@ -570,7 +479,7 @@ def is_real(self): # workaround for symengine behavior that const * (0 + 1 * I) is not real # see https://github.com/symengine/symengine.py/issues/414 - if _optionals.HAS_SYMENGINE and self._symbol_expr.is_real is None: + if self._symbol_expr.is_real is None: symbol_expr = self._symbol_expr.evalf() else: symbol_expr = self._symbol_expr @@ -581,9 +490,8 @@ def is_real(self): # but the parameter will evaluate as real. Check that if the # expression's is_real attribute returns false that we have a # non-zero imaginary - if _optionals.HAS_SYMENGINE: - if symbol_expr.imag == 0.0: - return True + if symbol_expr.imag == 0.0: + return True return False return symbol_expr.is_real diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 12a70edef3cb..2f5fcc4125f1 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -44,7 +44,6 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.parameter import Parameter from qiskit.circuit.exceptions import CircuitError -from qiskit.utils import optionals as _optionals from qiskit.utils.deprecation import deprecate_func from . import _classical_resource_map from ._utils import sort_parameters @@ -1029,7 +1028,7 @@ def tensor(self, other: "QuantumCircuit", inplace: bool = False) -> Optional["Qu Remember that in the little-endian convention the leftmost operation will be at the bottom of the circuit. See also - `the docs `__ + `the docs `__ for more information. .. parsed-literal:: @@ -1643,65 +1642,6 @@ def decompose( # do not copy operations, this is done in the conversion with circuit_to_dag return dag_to_circuit(dag, copy_operations=False) - def qasm( - self, - formatted: bool = False, - filename: str | None = None, - encoding: str | None = None, - ) -> str | None: - """Return OpenQASM 2.0 string. - - .. seealso:: - - :func:`.qasm2.dump` and :func:`.qasm2.dumps` - The preferred entry points to the OpenQASM 2 export capabilities. These match the - interface for other serialisers in Qiskit. - - Args: - formatted (bool): Return formatted OpenQASM 2.0 string. - filename (str): Save OpenQASM 2.0 to file with name 'filename'. - encoding (str): Optionally specify the encoding to use for the - output file if ``filename`` is specified. By default this is - set to the system's default encoding (ie whatever - ``locale.getpreferredencoding()`` returns) and can be set to - any valid codec or alias from stdlib's - `codec module `__ - - Returns: - str: If formatted=False. - - Raises: - MissingOptionalLibraryError: If pygments is not installed and ``formatted`` is - ``True``. - QASM2ExportError: If circuit has free parameters. - QASM2ExportError: If an operation that has no OpenQASM 2 representation is encountered. - """ - from qiskit import qasm2 # pylint: disable=cyclic-import - - out = qasm2.dumps(self) - if filename is not None: - with open(filename, "w+", encoding=encoding) as file: - print(out, file=file) - - if formatted: - _optionals.HAS_PYGMENTS.require_now("formatted OpenQASM 2.0 output") - - import pygments - from pygments.formatters import ( # pylint: disable=no-name-in-module - Terminal256Formatter, - ) - from qiskit.qasm.pygments import OpenQASMLexer - from qiskit.qasm.pygments import QasmTerminalStyle - - code = pygments.highlight( - out, OpenQASMLexer(), Terminal256Formatter(style=QasmTerminalStyle) - ) - print(code) - return None - # The old `QuantumCircuit.qasm()` method included a terminating new line that `qasm2.dumps` - # doesn't, so for full compatibility we add it back here. - return out + "\n" - def draw( self, output: str | None = None, diff --git a/qiskit/converters/__init__.py b/qiskit/converters/__init__.py index afac4cd2231f..459b739ee011 100644 --- a/qiskit/converters/__init__.py +++ b/qiskit/converters/__init__.py @@ -21,7 +21,6 @@ .. autofunction:: dag_to_circuit .. autofunction:: circuit_to_instruction .. autofunction:: circuit_to_gate -.. autofunction:: ast_to_dag .. autofunction:: dagdependency_to_circuit .. autofunction:: circuit_to_dagdependency .. autofunction:: dag_to_dagdependency @@ -32,7 +31,6 @@ from .dag_to_circuit import dag_to_circuit from .circuit_to_instruction import circuit_to_instruction from .circuit_to_gate import circuit_to_gate -from .ast_to_dag import ast_to_dag from .circuit_to_dagdependency import circuit_to_dagdependency from .dagdependency_to_circuit import dagdependency_to_circuit from .dag_to_dagdependency import dag_to_dagdependency diff --git a/qiskit/converters/ast_to_dag.py b/qiskit/converters/ast_to_dag.py deleted file mode 100644 index 4cd60f56573c..000000000000 --- a/qiskit/converters/ast_to_dag.py +++ /dev/null @@ -1,418 +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. - -""" -AST (abstract syntax tree) to DAG (directed acyclic graph) converter. - -Acts as an OpenQASM interpreter. -""" -from collections import OrderedDict -from qiskit.dagcircuit import DAGCircuit -from qiskit.exceptions import QiskitError - -from qiskit.circuit import QuantumRegister, ClassicalRegister, Gate, QuantumCircuit -from qiskit.qasm.node.real import Real -from qiskit.circuit.measure import Measure -from qiskit.circuit.reset import Reset -from qiskit.circuit.barrier import Barrier -from qiskit.circuit.delay import Delay -from qiskit.circuit.library import standard_gates as std - - -def ast_to_dag(ast): - """Build a ``DAGCircuit`` object from an AST ``Node`` object. - - Args: - ast (Program): a Program Node of an AST (parser's output) - - Return: - DAGCircuit: the DAG representing an OpenQASM's AST - - Raises: - QiskitError: if the AST is malformed. - - Example: - .. code-block:: - - from qiskit.converters import ast_to_dag - from qiskit import qasm, QuantumCircuit, ClassicalRegister, QuantumRegister - - q = QuantumRegister(3, 'q') - c = ClassicalRegister(3, 'c') - circ = QuantumCircuit(q, c) - circ.h(q[0]) - circ.cx(q[0], q[1]) - circ.measure(q[0], c[0]) - circ.rz(0.5, q[1]).c_if(c, 2) - qasm_str = circ.qasm() - ast = qasm.Qasm(data=qasm_str).parse() - dag = ast_to_dag(ast) - """ - dag = DAGCircuit() - AstInterpreter(dag)._process_node(ast) - - return dag - - -class AstInterpreter: - """Interprets an OpenQASM by expanding subroutines and unrolling loops.""" - - standard_extension = { - "u1": std.U1Gate, - "u2": std.U2Gate, - "u3": std.U3Gate, - "u": std.UGate, - "p": std.PhaseGate, - "x": std.XGate, - "y": std.YGate, - "z": std.ZGate, - "t": std.TGate, - "tdg": std.TdgGate, - "s": std.SGate, - "sdg": std.SdgGate, - "sx": std.SXGate, - "sxdg": std.SXdgGate, - "swap": std.SwapGate, - "rx": std.RXGate, - "rxx": std.RXXGate, - "ry": std.RYGate, - "rz": std.RZGate, - "rzz": std.RZZGate, - "id": std.IGate, - "h": std.HGate, - "cx": std.CXGate, - "cy": std.CYGate, - "cz": std.CZGate, - "ch": std.CHGate, - "crx": std.CRXGate, - "cry": std.CRYGate, - "crz": std.CRZGate, - "csx": std.CSXGate, - "cu1": std.CU1Gate, - "cp": std.CPhaseGate, - "cu": std.CUGate, - "cu3": std.CU3Gate, - "ccx": std.CCXGate, - "cswap": std.CSwapGate, - "delay": Delay, - "rccx": std.RCCXGate, - "rc3x": std.RC3XGate, - "c3x": std.C3XGate, - "c3sqrtx": std.C3SXGate, - "c4x": std.C4XGate, - } - - def __init__(self, dag): - """Initialize interpreter's data.""" - # DAG object to populate - self.dag = dag - # OPENQASM version number (ignored for now) - self.version = 0.0 - # Dict of gates names and properties - self.gates = OrderedDict() - # Keeping track of conditional gates - self.condition = None - # List of dictionaries mapping local parameter ids to expression Nodes - self.arg_stack = [{}] - # List of dictionaries mapping local bit ids to global ids (name, idx) - self.bit_stack = [{}] - - def _process_bit_id(self, node): - """Process an Id or IndexedId node as a bit or register type. - - Return a list of tuples (Register,index). - """ - reg = None - - if node.name in self.dag.qregs: - reg = self.dag.qregs[node.name] - elif node.name in self.dag.cregs: - reg = self.dag.cregs[node.name] - else: - raise QiskitError( - "expected qreg or creg name:", "line=%s" % node.line, "file=%s" % node.file - ) - - if node.type == "indexed_id": - # An indexed bit or qubit - return [reg[node.index]] - elif node.type == "id": - # A qubit or qreg or creg - if not self.bit_stack[-1]: - # Global scope - return list(reg) - else: - # local scope - if node.name in self.bit_stack[-1]: - return [self.bit_stack[-1][node.name]] - raise QiskitError( - "expected local bit name:", "line=%s" % node.line, "file=%s" % node.file - ) - return None - - def _process_custom_unitary(self, node): - """Process a custom unitary node.""" - name = node.name - if node.arguments is not None: - args = self._process_node(node.arguments) - else: - args = [] - bits = [self._process_bit_id(node_element) for node_element in node.bitlist.children] - - if name in self.gates: - self._arguments(name, bits, args) - else: - raise QiskitError( - "internal error undefined gate:", "line=%s" % node.line, "file=%s" % node.file - ) - - def _process_u(self, node): - """Process a U gate node.""" - args = self._process_node(node.arguments) - bits = [self._process_bit_id(node.bitlist)] - - self._arguments("u", bits, args) - - def _arguments(self, name, bits, args): - gargs = self.gates[name]["args"] - gbits = self.gates[name]["bits"] - - maxidx = max(map(len, bits)) - for idx in range(maxidx): - self.arg_stack.append({gargs[j]: args[j] for j in range(len(gargs))}) - # Only index into register arguments. - element = [idx * x for x in [len(bits[j]) > 1 for j in range(len(bits))]] - self.bit_stack.append({gbits[j]: bits[j][element[j]] for j in range(len(gbits))}) - self._create_dag_op( - name, - [self.arg_stack[-1][s].sym() for s in gargs], - [self.bit_stack[-1][s] for s in gbits], - ) - self.arg_stack.pop() - self.bit_stack.pop() - - def _process_gate(self, node, opaque=False): - """Process a gate node. - - If opaque is True, process the node as an opaque gate node. - """ - self.gates[node.name] = {} - de_gate = self.gates[node.name] - de_gate["print"] = True # default - de_gate["opaque"] = opaque - de_gate["n_args"] = node.n_args() - de_gate["n_bits"] = node.n_bits() - if node.n_args() > 0: - de_gate["args"] = [element.name for element in node.arguments.children] - else: - de_gate["args"] = [] - de_gate["bits"] = [c.name for c in node.bitlist.children] - if node.name in self.standard_extension: - return - if opaque: - de_gate["body"] = None - else: - de_gate["body"] = node.body - - def _process_cnot(self, node): - """Process a CNOT gate node.""" - id0 = self._process_bit_id(node.children[0]) - id1 = self._process_bit_id(node.children[1]) - if not (len(id0) == len(id1) or len(id0) == 1 or len(id1) == 1): - raise QiskitError( - "internal error: qreg size mismatch", "line=%s" % node.line, "file=%s" % node.file - ) - maxidx = max([len(id0), len(id1)]) - for idx in range(maxidx): - cx_gate = std.CXGate() - if self.condition: - cx_gate = cx_gate.c_if(*self.condition) - if len(id0) > 1 and len(id1) > 1: - self.dag.apply_operation_back(cx_gate, [id0[idx], id1[idx]], [], check=False) - elif len(id0) > 1: - self.dag.apply_operation_back(cx_gate, [id0[idx], id1[0]], [], check=False) - else: - self.dag.apply_operation_back(cx_gate, [id0[0], id1[idx]], [], check=False) - - def _process_measure(self, node): - """Process a measurement node.""" - id0 = self._process_bit_id(node.children[0]) - id1 = self._process_bit_id(node.children[1]) - if len(id0) != len(id1): - raise QiskitError( - "internal error: reg size mismatch", "line=%s" % node.line, "file=%s" % node.file - ) - for idx, idy in zip(id0, id1): - meas_gate = Measure() - if self.condition: - meas_gate = meas_gate.c_if(*self.condition) - self.dag.apply_operation_back(meas_gate, [idx], [idy], check=False) - - def _process_if(self, node): - """Process an if node.""" - creg_name = node.children[0].name - creg = self.dag.cregs[creg_name] - cval = node.children[1].value - self.condition = (creg, cval) - self._process_node(node.children[2]) - self.condition = None - - def _process_children(self, node): - """Call process_node for all children of node.""" - for kid in node.children: - self._process_node(kid) - - def _process_node(self, node): - """Carry out the action associated with a node.""" - if node.type == "program": - self._process_children(node) - - elif node.type == "qreg": - qreg = QuantumRegister(node.index, node.name) - self.dag.add_qreg(qreg) - - elif node.type == "creg": - creg = ClassicalRegister(node.index, node.name) - self.dag.add_creg(creg) - - elif node.type == "id": - raise QiskitError("internal error: _process_node on id") - - elif node.type == "int": - raise QiskitError("internal error: _process_node on int") - - elif node.type == "real": - raise QiskitError("internal error: _process_node on real") - - elif node.type == "indexed_id": - raise QiskitError("internal error: _process_node on indexed_id") - - elif node.type == "id_list": - # We process id_list nodes when they are leaves of barriers. - return [self._process_bit_id(node_children) for node_children in node.children] - - elif node.type == "primary_list": - # We should only be called for a barrier. - return [self._process_bit_id(m) for m in node.children] - - elif node.type == "gate": - self._process_gate(node) - - elif node.type == "custom_unitary": - self._process_custom_unitary(node) - - elif node.type == "universal_unitary": - self._process_u(node) - - elif node.type == "cnot": - self._process_cnot(node) - - elif node.type == "expression_list": - return node.children - - elif node.type == "binop": - raise QiskitError("internal error: _process_node on binop") - - elif node.type == "prefix": - raise QiskitError("internal error: _process_node on prefix") - - elif node.type == "measure": - self._process_measure(node) - - elif node.type == "format": - self.version = node.version() - - elif node.type == "barrier": - ids = self._process_node(node.children[0]) - qubits = [] - for qubit in ids: - for j, _ in enumerate(qubit): - qubits.append(qubit[j]) - self.dag.apply_operation_back(Barrier(len(qubits)), qubits, [], check=False) - - elif node.type == "reset": - id0 = self._process_bit_id(node.children[0]) - for i, _ in enumerate(id0): - reset = Reset() - if self.condition: - reset = reset.c_if(*self.condition) - self.dag.apply_operation_back(reset, [id0[i]], [], check=False) - - elif node.type == "if": - self._process_if(node) - - elif node.type == "opaque": - self._process_gate(node, opaque=True) - - elif node.type == "external": - raise QiskitError("internal error: _process_node on external") - - else: - raise QiskitError( - "internal error: undefined node type", - node.type, - "line=%s" % node.line, - "file=%s" % node.file, - ) - return None - - def _gate_rules_to_qiskit_circuit(self, node, params): - """From a gate definition in OpenQASM, to a QuantumCircuit format.""" - rules = [] - qreg = QuantumRegister(node["n_bits"]) - bit_args = {node["bits"][i]: q for i, q in enumerate(qreg)} - exp_args = {node["args"][i]: Real(q) for i, q in enumerate(params)} - - for child_op in node["body"].children: - qparams = [] - eparams = [] - for param_list in child_op.children[1:]: - if param_list.type == "id_list": - qparams = [bit_args[param.name] for param in param_list.children] - elif param_list.type == "expression_list": - for param in param_list.children: - eparams.append(param.sym(nested_scope=[exp_args])) - op = self._create_op(child_op.name, params=eparams) - rules.append((op, qparams, [])) - circ = QuantumCircuit(qreg) - for instr, qargs, cargs in rules: - circ._append(instr, qargs, cargs) - return circ - - def _create_dag_op(self, name, params, qargs): - """ - Create a DAG node out of a parsed AST op node. - - Args: - name (str): operation name to apply to the DAG - params (list): op parameters - qargs (list(Qubit)): qubits to attach to - - Raises: - QiskitError: if encountering a non-basis opaque gate - """ - op = self._create_op(name, params) - if self.condition: - op = op.c_if(*self.condition) - self.dag.apply_operation_back(op, qargs, [], check=False) - - def _create_op(self, name, params): - if name in self.standard_extension: - op = self.standard_extension[name](*params) - elif name in self.gates: - op = Gate(name=name, num_qubits=self.gates[name]["n_bits"], params=params) - if not self.gates[name]["opaque"]: - # call a custom gate (otherwise, opaque) - op.definition = self._gate_rules_to_qiskit_circuit(self.gates[name], params=params) - else: - raise QiskitError("unknown operation for ast node name %s" % name) - return op diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 126f19f1bb5d..21e1464d06ed 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -2082,8 +2082,12 @@ def draw(self, scale=0.7, filename=None, style="color"): """ Draws the dag circuit. - This function needs `pydot `_, which in turn needs - `Graphviz `_ to be installed. + This function needs `Graphviz `_ to be + installed. Graphviz is not a python package and can't be pip installed + (the ``graphviz`` package on PyPI is a Python interface library for + Graphviz and does not actually install Graphviz). You can refer to + `the Graphviz documentation `__ on + how to install it. Args: scale (float): scaling factor diff --git a/qiskit/opflow/__init__.py b/qiskit/opflow/__init__.py deleted file mode 100644 index babed3da26b5..000000000000 --- a/qiskit/opflow/__init__.py +++ /dev/null @@ -1,332 +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. - -r""" -================================ -Operators (:mod:`qiskit.opflow`) -================================ - -.. currentmodule:: qiskit.opflow - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Operators and State functions are the building blocks of Quantum Algorithms. - -A library for Quantum Algorithms & Applications is more than a collection of -algorithms wrapped in Python functions. It needs to provide tools to make writing -algorithms simple and easy. This is the layer of modules between the circuits and algorithms, -providing the language and computational primitives for QA&A research. - -We call this layer the Operator Flow. It works by unifying computation with theory -through the common language of functions and operators, in a way which preserves physical -intuition and programming freedom. In the Operator Flow, we construct functions over binary -variables, manipulate those functions with operators, and evaluate properties of these functions -with measurements. - -The Operator Flow is meant to serve as a lingua franca between the theory and implementation -of Quantum Algorithms & Applications. Meaning, the ultimate goal is that when theorists speak -their theory in the Operator Flow, they are speaking valid implementation, and when the engineers -speak their implementation in the Operator Flow, they are speaking valid physical formalism. To -be successful, it must be fast and physically formal enough for theorists to find it easier and -more natural than hacking Matlab or NumPy, and the engineers must find it straightforward enough -that they can learn it as a typical software library, and learn the physics naturally and -effortlessly as they learn the code. There can never be a point where we say "below this level -this is all hacked out, don't come down here, stay in the interface layer above." It all must -be clear and learnable. - -Before getting into the details of the code, it's important to note that three mathematical -concepts unpin the Operator Flow. We derive most of the inspiration for the code structure from -`John Watrous's formalism `__ (but do not follow it exactly), -so it may be worthwhile to review Chapters I and II, which are free online, if you feel the -concepts are not clicking. - -1. An n-qubit State function is a complex function over n binary variables, which we will -often refer to as *n-qubit binary strings*. For example, the traditional quantum "zero state" is -a 1-qubit state function, with a definition of f(0) = 1 and f(1) = 0. - -2. An n-qubit Operator is a linear function taking n-qubit state functions to n-qubit state -functions. For example, the Pauli X Operator is defined by f(Zero) = One and f(One) = Zero. -Equivalently, an Operator can be defined as a complex function over two n-qubit binary strings, -and it is sometimes convenient to picture things this way. By this definition, our Pauli X can -be defined by its typical matrix elements, f(0, 0) = 0, f(1, 0) = 1, f(0, 1) = 1, -f(1, 1) = 0. - -3. An n-qubit Measurement is a functional taking n-qubit State functions to complex values. -For example, a Pauli Z Measurement can be defined by f(Zero) = 0 and f(One) = 1. - -.. note:: - - While every effort has been made to make programming the Operator Flow similar to mathematical - notation, in some places our hands are tied by the design of Python. In particular, when using - mathematical operators such as ``+`` and ``^`` (tensor product), beware that these follow - `Python operator precedence rules - `__. For example, - ``I^X + X^I`` will actually be interpreted as ``I ^ (X+X) ^ I == 2 * I^X^I``. In these cases, - you should use extra parentheses, like ``(I ^ X) + (X ^ I)``, or use the relevant method calls. - -Below, you'll find a base class for all Operators, some convenience immutable global variables -which simplify Operator construction, and two groups of submodules: Operators and Converters. - -Operator Base Class -=================== - -The OperatorBase serves as the base class for all Operators, State functions -and measurements, and enforces the presence and consistency of methods to manipulate these -objects conveniently. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - OperatorBase - -.. _operator_globals: - -Operator Globals -================ - -The :mod:`operator_globals` is a set of immutable Operator instances that are -convenient building blocks to reach for while working with the Operator flow. - -One qubit Pauli operators: - :attr:`X`, :attr:`Y`, :attr:`Z`, :attr:`I` - -Clifford+T, and some other common non-parameterized gates: - :attr:`CX`, :attr:`S`, :attr:`H`, :attr:`T`, :attr:`Swap`, :attr:`CZ` - -One qubit states: - :attr:`Zero`, :attr:`One`, :attr:`Plus`, :attr:`Minus` - -Submodules -========== - -Operators ---------- - -The Operators submodules include the PrimitiveOp, ListOp, and StateFn class -groups which represent the primary Operator modules. - -.. autosummary:: - :toctree: ../stubs/ - - primitive_ops - list_ops - state_fns - - -Converters ----------- - -The Converter submodules include objects which manipulate Operators, -usually recursing over an Operator structure and changing certain Operators' representation. -For example, the :class:`~.expectations.PauliExpectation` traverses an Operator structure, and -replaces all of the :class:`~.state_fns.OperatorStateFn` measurements containing non-diagonal -Pauli terms into diagonalizing circuits following by :class:`~.state_fns.OperatorStateFn` -measurement containing only diagonal Paulis. - -.. autosummary:: - :toctree: ../stubs/ - - converters - evolutions - expectations - gradients - - -Utility functions -================= - -.. autofunction:: commutator -.. autofunction:: anti_commutator -.. autofunction:: double_commutator - - -Exceptions -========== - -.. autoexception:: OpflowError -""" -import warnings - -# New Operators -from .operator_base import OperatorBase -from .primitive_ops import ( - PrimitiveOp, - PauliOp, - MatrixOp, - CircuitOp, - PauliSumOp, - TaperedPauliSumOp, - Z2Symmetries, -) -from .state_fns import ( - StateFn, - DictStateFn, - VectorStateFn, - CVaRMeasurement, - CircuitStateFn, - OperatorStateFn, - SparseVectorStateFn, -) -from .list_ops import ListOp, SummedOp, ComposedOp, TensoredOp -from .converters import ( - ConverterBase, - CircuitSampler, - PauliBasisChange, - DictToCircuitSum, - AbelianGrouper, - TwoQubitReduction, -) -from .expectations import ( - ExpectationBase, - ExpectationFactory, - PauliExpectation, - MatrixExpectation, - AerPauliExpectation, - CVaRExpectation, -) -from .evolutions import ( - EvolutionBase, - EvolutionFactory, - EvolvedOp, - PauliTrotterEvolution, - MatrixEvolution, - TrotterizationBase, - TrotterizationFactory, - Trotter, - Suzuki, - QDrift, -) -from .utils import commutator, anti_commutator, double_commutator - -# Convenience immutable instances -from .operator_globals import ( - EVAL_SIG_DIGITS, - X, - Y, - Z, - I, - CX, - S, - H, - T, - Swap, - CZ, - Zero, - One, - Plus, - Minus, -) - -# Gradients -from .gradients import ( - DerivativeBase, - GradientBase, - Gradient, - NaturalGradient, - HessianBase, - Hessian, - QFIBase, - QFI, - CircuitGradient, - CircuitQFI, -) - -# Exceptions -from .exceptions import OpflowError - -__all__ = [ - # Operators - "OperatorBase", - "PrimitiveOp", - "PauliOp", - "MatrixOp", - "CircuitOp", - "PauliSumOp", - "TaperedPauliSumOp", - "StateFn", - "DictStateFn", - "VectorStateFn", - "CircuitStateFn", - "OperatorStateFn", - "SparseVectorStateFn", - "CVaRMeasurement", - "ListOp", - "SummedOp", - "ComposedOp", - "TensoredOp", - # Converters - "ConverterBase", - "CircuitSampler", - "AbelianGrouper", - "DictToCircuitSum", - "PauliBasisChange", - "ExpectationBase", - "ExpectationFactory", - "PauliExpectation", - "MatrixExpectation", - "AerPauliExpectation", - "CVaRExpectation", - "EvolutionBase", - "EvolvedOp", - "EvolutionFactory", - "PauliTrotterEvolution", - "MatrixEvolution", - "TrotterizationBase", - "TrotterizationFactory", - "Trotter", - "Suzuki", - "QDrift", - "TwoQubitReduction", - "Z2Symmetries", - # Convenience immutable instances - "X", - "Y", - "Z", - "I", - "CX", - "S", - "H", - "T", - "Swap", - "CZ", - "Zero", - "One", - "Plus", - "Minus", - # Gradients - "DerivativeBase", - "GradientBase", - "Gradient", - "NaturalGradient", - "HessianBase", - "Hessian", - "QFIBase", - "QFI", - "OpflowError", - # utils - "commutator", - "anti_commutator", - "double_commutator", -] - -warnings.warn( - "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.", - category=DeprecationWarning, - stacklevel=2, -) diff --git a/qiskit/opflow/converters/__init__.py b/qiskit/opflow/converters/__init__.py deleted file mode 100644 index 8c2236eaec77..000000000000 --- a/qiskit/opflow/converters/__init__.py +++ /dev/null @@ -1,85 +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. - -""" -Converters (:mod:`qiskit.opflow.converters`) -============================================ - -.. currentmodule:: qiskit.opflow.converters - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Converters are objects which manipulate Operators, usually traversing an Operator to -change certain sub-Operators into a desired representation. Often the converted Operator is -isomorphic or approximate to the original Operator in some way, but not always. For example, -a converter may accept :class:`~qiskit.opflow.primitive_ops.CircuitOp` and return a -:class:`~qiskit.opflow.list_ops.SummedOp` of -:class:`~qiskit.opflow.primitive_ops.PauliOp`'s representing the -circuit unitary. Converters may not have polynomial space or time scaling in their operations. -On the contrary, many converters, such as a -:class:`~qiskit.opflow.expectations.MatrixExpectation` or -:class:`~qiskit.opflow.evolutions.MatrixEvolution`, -which convert :class:`~qiskit.opflow.primitive_ops.PauliOp`'s to -:class:`~qiskit.opflow.primitive_ops.MatrixOp`'s internally, will require time or space -exponential in the number of qubits unless a clever trick is known -(such as the use of sparse matrices). - - -Note: - Not all converters are in this module, as :mod:`~qiskit.opflow.expectations` - and :mod:`~qiskit.opflow.evolutions` are also converters. - -Converter Base Class --------------------- -The converter base class simply enforces the presence of a :meth:`~ConverterBase.convert` method. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - ConverterBase - -Converters ----------- -In addition to the base class, directory holds a few miscellaneous converters which are used -frequently around the Operator flow. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - CircuitSampler - AbelianGrouper - DictToCircuitSum - PauliBasisChange - TwoQubitReduction -""" - -from .converter_base import ConverterBase -from .circuit_sampler import CircuitSampler -from .pauli_basis_change import PauliBasisChange -from .dict_to_circuit_sum import DictToCircuitSum -from .abelian_grouper import AbelianGrouper -from .two_qubit_reduction import TwoQubitReduction - -__all__ = [ - "ConverterBase", - "CircuitSampler", - "PauliBasisChange", - "DictToCircuitSum", - "AbelianGrouper", - "TwoQubitReduction", -] diff --git a/qiskit/opflow/converters/abelian_grouper.py b/qiskit/opflow/converters/abelian_grouper.py deleted file mode 100644 index b267fc699411..000000000000 --- a/qiskit/opflow/converters/abelian_grouper.py +++ /dev/null @@ -1,165 +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. - -"""AbelianGrouper Class""" - -from collections import defaultdict -from typing import List, Tuple, Union, cast - -import numpy as np -import rustworkx as rx - -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.opflow.evolutions.evolved_op import EvolvedOp -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.utils.deprecation import deprecate_func - - -class AbelianGrouper(ConverterBase): - """Deprecated: The AbelianGrouper converts SummedOps into a sum of Abelian sums. - - Meaning, it will traverse the Operator, and when it finds a SummedOp, it will evaluate which of - the summed sub-Operators commute with one another. It will then convert each of the groups of - commuting Operators into their own SummedOps, and return the sum-of-commuting-SummedOps. - This is particularly useful for cases where mutually commuting groups can be handled - similarly, as in the case of Pauli Expectations, where commuting Paulis have the same - diagonalizing circuit rotation, or Pauli Evolutions, where commuting Paulis can be - diagonalized together. - """ - - @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: - """ - Args: - traverse: Whether to convert only the Operator passed to ``convert``, or traverse - down that Operator. - """ - super().__init__() - self._traverse = traverse - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Check if operator is a SummedOp, in which case covert it into a sum of mutually - commuting sums, or if the Operator contains sub-Operators and ``traverse`` is True, - attempt to convert any sub-Operators. - - Args: - operator: The Operator to attempt to convert. - - Returns: - The converted Operator. - """ - if isinstance(operator, PauliSumOp): - return self.group_subops(operator) - - if isinstance(operator, ListOp): - if isinstance(operator, SummedOp) and all( - isinstance(op, PauliOp) for op in operator.oplist - ): - # For now, we only support graphs over Paulis. - return self.group_subops(operator) - elif self._traverse: - return operator.traverse(self.convert) - elif isinstance(operator, OperatorStateFn) and self._traverse: - return OperatorStateFn( - self.convert(operator.primitive), - is_measurement=operator.is_measurement, - coeff=operator.coeff, - ) - elif isinstance(operator, EvolvedOp) and self._traverse: - return EvolvedOp(self.convert(operator.primitive), coeff=operator.coeff) - return operator - - @classmethod - def group_subops(cls, list_op: Union[ListOp, PauliSumOp]) -> ListOp: - """Given a ListOp, attempt to group into Abelian ListOps of the same type. - - Args: - list_op: The Operator to group into Abelian groups - - Returns: - The grouped Operator. - - Raises: - OpflowError: If any of list_op's sub-ops is not ``PauliOp``. - """ - if isinstance(list_op, ListOp): - for op in list_op.oplist: - if not isinstance(op, PauliOp): - raise OpflowError( - "Cannot determine Abelian groups if any Operator in list_op is not " - f"`PauliOp`. E.g., {op} ({type(op)})" - ) - - edges = cls._anti_commutation_graph(list_op) - nodes = range(len(list_op)) - - graph = rx.PyGraph() - graph.add_nodes_from(nodes) - graph.add_edges_from_no_data(edges) - # Keys in coloring_dict are nodes, values are colors - coloring_dict = rx.graph_greedy_color(graph) - groups = defaultdict(list) - for idx, color in coloring_dict.items(): - groups[color].append(idx) - - if isinstance(list_op, PauliSumOp): - primitive = list_op.primitive - return SummedOp( - [PauliSumOp(primitive[group], grouping_type="TPB") for group in groups.values()], - coeff=list_op.coeff, - ) - - group_ops: List[ListOp] = [ - list_op.__class__([list_op[idx] for idx in group], abelian=True) - for group in groups.values() - ] - if len(group_ops) == 1: - return group_ops[0].mul(list_op.coeff) - return list_op.__class__(group_ops, coeff=list_op.coeff) - - @staticmethod - def _anti_commutation_graph(ops: Union[ListOp, PauliSumOp]) -> List[Tuple[int, int]]: - """Create edges (i, j) if i and j are not commutable. - - Note: - This method is applicable to only PauliOps. - - Args: - ops: operators - - Returns: - A list of pairs of indices of the operators that are not commutable - """ - # convert a Pauli operator into int vector where {I: 0, X: 2, Y: 3, Z: 1} - if isinstance(ops, PauliSumOp): - mat1 = np.array( - [op.primitive.paulis.z[0] + 2 * op.primitive.paulis.x[0] for op in ops], - dtype=np.int8, - ) - else: - mat1 = np.array([op.primitive.z + 2 * op.primitive.x for op in ops], dtype=np.int8) - - mat2 = mat1[:, None] - # mat3[i, j] is True if i and j are commutable with TPB - mat3 = (((mat1 * mat2) * (mat1 - mat2)) == 0).all(axis=2) - # return [(i, j) if mat3[i, j] is False and i < j] - return cast(List[Tuple[int, int]], list(zip(*np.where(np.triu(np.logical_not(mat3), k=1))))) diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py deleted file mode 100644 index 04116c93b31f..000000000000 --- a/qiskit/opflow/converters/circuit_sampler.py +++ /dev/null @@ -1,469 +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. - -"""CircuitSampler Class""" - - -import logging -from functools import partial -from time import time -from typing import Any, Dict, List, Optional, Tuple, Union, cast - -import numpy as np - -from qiskit import QiskitError -from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn -from qiskit.opflow.state_fns.dict_state_fn import DictStateFn -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.providers import Backend -from qiskit.utils.backend_utils import is_aer_provider, is_statevector_backend -from qiskit.utils.quantum_instance import QuantumInstance -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class CircuitSampler(ConverterBase): - """ - Deprecated: The CircuitSampler traverses an Operator and converts any CircuitStateFns into - approximations of the 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 - 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. - - The CircuitSampler aggressively caches transpiled circuits to handle re-parameterization of - the same circuit efficiently. If you are converting multiple different Operators, - you are better off using a different CircuitSampler for each Operator to avoid cache thrashing. - """ - - @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, - backend: Union[Backend, QuantumInstance], - statevector: Optional[bool] = None, - param_qobj: bool = False, - attach_results: bool = False, - caching: str = "last", - ) -> None: - """ - Args: - backend: The quantum backend or QuantumInstance to use to sample the circuits. - statevector: If backend is a statevector backend, whether to replace the - CircuitStateFns with DictStateFns (from the counts) or VectorStateFns (from the - statevector). ``None`` will set this argument automatically based on the backend. - attach_results: Whether to attach the data from the backend ``Results`` object for - a given ``CircuitStateFn``` to an ``execution_results`` field added the converted - ``DictStateFn`` or ``VectorStateFn``. - param_qobj: Whether to use Aer's parameterized Qobj capability to avoid re-assembling - the circuits. - caching: The caching strategy. Can be `'last'` (default) to store the last operator - that was converted, set to `'all'` to cache all processed operators. - - Raises: - ValueError: Set statevector or param_qobj True when not supported by backend. - """ - super().__init__() - - self._quantum_instance = ( - backend if isinstance(backend, QuantumInstance) else QuantumInstance(backend=backend) - ) - self._statevector = ( - statevector if statevector is not None else self.quantum_instance.is_statevector - ) - self._param_qobj = param_qobj - self._attach_results = attach_results - - self._check_quantum_instance_and_modes_consistent() - - # Object state variables - self._caching = caching - self._cached_ops: Dict[int, OperatorCache] = {} - - self._last_op: Optional[OperatorBase] = None - self._reduced_op_cache = None - self._circuit_ops_cache: Dict[int, CircuitStateFn] = {} - self._transpiled_circ_cache: Optional[List[Any]] = None - self._transpiled_circ_templates: Optional[List[Any]] = None - self._transpile_before_bind = True - - def _check_quantum_instance_and_modes_consistent(self) -> None: - """Checks whether the statevector and param_qobj settings are compatible with the - backend - - Raises: - ValueError: statevector or param_qobj are True when not supported by backend. - """ - if self._statevector and not is_statevector_backend(self.quantum_instance.backend): - raise ValueError( - "Statevector mode for circuit sampling requires statevector " - "backend, not {}.".format(self.quantum_instance.backend) - ) - - if self._param_qobj and not is_aer_provider(self.quantum_instance.backend): - raise ValueError( - "Parameterized Qobj mode requires Aer " - "backend, not {}.".format(self.quantum_instance.backend) - ) - - @property - def quantum_instance(self) -> QuantumInstance: - """Returns the quantum instance. - - Returns: - The QuantumInstance used by the CircuitSampler - """ - return self._quantum_instance - - @quantum_instance.setter - def quantum_instance(self, quantum_instance: Union[QuantumInstance, Backend]) -> None: - """Sets the QuantumInstance. - - Raises: - ValueError: statevector or param_qobj are True when not supported by backend. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - self._check_quantum_instance_and_modes_consistent() - - def convert( - self, - operator: OperatorBase, - params: Optional[Dict[Parameter, Union[float, List[float], List[List[float]]]]] = None, - ) -> OperatorBase: - r""" - Converts the Operator to one in which the CircuitStateFns are replaced by - DictStateFns or VectorStateFns. Extracts the CircuitStateFns out of the Operator, - caches them, calls ``sample_circuits`` below to get their converted replacements, - and replaces the CircuitStateFns in operator with the replacement StateFns. - - Args: - operator: The Operator to convert - params: A dictionary mapping parameters to either single binding values or lists of - binding values. - - Returns: - The converted Operator with CircuitStateFns replaced by DictStateFns or VectorStateFns. - Raises: - OpflowError: if extracted circuits are empty. - """ - # check if the operator should be cached - op_id = operator.instance_id - # op_id = id(operator) - if op_id not in self._cached_ops.keys(): - # delete cache if we only want to cache one operator - if self._caching == "last": - self.clear_cache() - - # convert to circuit and reduce - operator_dicts_replaced = operator.to_circuit_op() - self._reduced_op_cache = operator_dicts_replaced.reduce() - - # extract circuits - self._circuit_ops_cache = {} - self._extract_circuitstatefns(self._reduced_op_cache) - if not self._circuit_ops_cache: - raise OpflowError( - "Circuits are empty. " - "Check that the operator is an instance of CircuitStateFn or its ListOp." - ) - self._transpiled_circ_cache = None - self._transpile_before_bind = True - else: - # load the cached circuits - self._reduced_op_cache = self._cached_ops[op_id].reduced_op_cache - self._circuit_ops_cache = self._cached_ops[op_id].circuit_ops_cache - self._transpiled_circ_cache = self._cached_ops[op_id].transpiled_circ_cache - self._transpile_before_bind = self._cached_ops[op_id].transpile_before_bind - self._transpiled_circ_templates = self._cached_ops[op_id].transpiled_circ_templates - - return_as_list = False - if params is not None and len(params.keys()) > 0: - 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) - ] - return_as_list = True - else: - num_parameterizations = 1 - param_bindings = [params] - - else: - param_bindings = None - num_parameterizations = 1 - - # Don't pass circuits if we have in the cache, the sampling function knows to use the cache - circs = list(self._circuit_ops_cache.values()) if not self._transpiled_circ_cache else None - p_b = cast(List[Dict[Parameter, float]], param_bindings) - sampled_statefn_dicts = self.sample_circuits(circuit_sfns=circs, param_bindings=p_b) - - def replace_circuits_with_dicts(operator, param_index=0): - if isinstance(operator, CircuitStateFn): - return sampled_statefn_dicts[id(operator)][param_index] - elif isinstance(operator, ListOp): - return operator.traverse( - partial(replace_circuits_with_dicts, param_index=param_index) - ) - else: - return operator - - # store the operator we constructed, if it isn't stored already - if op_id not in self._cached_ops.keys(): - op_cache = OperatorCache() - op_cache.reduced_op_cache = self._reduced_op_cache - op_cache.circuit_ops_cache = self._circuit_ops_cache - op_cache.transpiled_circ_cache = self._transpiled_circ_cache - op_cache.transpile_before_bind = self._transpile_before_bind - op_cache.transpiled_circ_templates = self._transpiled_circ_templates - self._cached_ops[op_id] = op_cache - - if return_as_list: - return ListOp( - [ - replace_circuits_with_dicts(self._reduced_op_cache, param_index=i) - for i in range(num_parameterizations) - ] - ) - else: - return replace_circuits_with_dicts(self._reduced_op_cache, param_index=0) - - def clear_cache(self) -> None: - """Clear the cache of sampled operator expressions.""" - self._cached_ops = {} - - def _extract_circuitstatefns(self, operator: OperatorBase) -> None: - r""" - Recursively extract the ``CircuitStateFns`` contained in operator into the - ``_circuit_ops_cache`` field. - """ - if isinstance(operator, CircuitStateFn): - self._circuit_ops_cache[id(operator)] = operator - elif isinstance(operator, ListOp): - for op in operator.oplist: - self._extract_circuitstatefns(op) - - def sample_circuits( - self, - circuit_sfns: Optional[List[CircuitStateFn]] = None, - param_bindings: Optional[List[Dict[Parameter, float]]] = None, - ) -> Dict[int, List[StateFn]]: - r""" - Samples the CircuitStateFns and returns a dict associating their ``id()`` values to their - replacement DictStateFn or VectorStateFn. If param_bindings is provided, - the CircuitStateFns are broken into their parameterizations, and a list of StateFns is - returned in the dict for each circuit ``id()``. Note that param_bindings is provided here - in a different format than in ``convert``, and lists of parameters within the dict is not - supported, and only binding dicts which are valid to be passed into Terra can be included - in this list. - - Args: - circuit_sfns: The list of CircuitStateFns to sample. - param_bindings: The parameterizations to bind to each CircuitStateFn. - - Returns: - The dictionary mapping ids of the CircuitStateFns to their replacement StateFns. - Raises: - OpflowError: if extracted circuits are empty. - """ - if not circuit_sfns and not self._transpiled_circ_cache: - raise OpflowError("CircuitStateFn is empty and there is no cache.") - - if circuit_sfns: - self._transpiled_circ_templates = None - if self._statevector or circuit_sfns[0].from_operator: - circuits = [op_c.to_circuit(meas=False) for op_c in circuit_sfns] - else: - circuits = [op_c.to_circuit(meas=True) for op_c in circuit_sfns] - - try: - self._transpiled_circ_cache = self.quantum_instance.transpile( - circuits, pass_manager=self.quantum_instance.unbound_pass_manager - ) - except QiskitError: - logger.debug( - r"CircuitSampler failed to transpile circuits with unbound " - r"parameters. Attempting to transpile only when circuits are bound " - r"now, but this can hurt performance due to repeated transpilation." - ) - self._transpile_before_bind = False - self._transpiled_circ_cache = circuits - else: - circuit_sfns = list(self._circuit_ops_cache.values()) - - if param_bindings is not None: - if self._param_qobj: - start_time = time() - ready_circs = self._prepare_parameterized_run_config(param_bindings) - end_time = time() - logger.debug("Parameter conversion %.5f (ms)", (end_time - start_time) * 1000) - else: - start_time = time() - ready_circs = [ - circ.assign_parameters(_filter_params(circ, binding)) - for circ in self._transpiled_circ_cache - for binding in param_bindings - ] - end_time = time() - logger.debug("Parameter binding %.5f (ms)", (end_time - start_time) * 1000) - else: - ready_circs = self._transpiled_circ_cache - - # run transpiler passes on bound circuits - if self._transpile_before_bind and self.quantum_instance.bound_pass_manager is not None: - ready_circs = self.quantum_instance.transpile( - ready_circs, pass_manager=self.quantum_instance.bound_pass_manager - ) - - results = self.quantum_instance.execute( - ready_circs, had_transpiled=self._transpile_before_bind - ) - - if param_bindings is not None and self._param_qobj: - self._clean_parameterized_run_config() - - # Wipe parameterizations, if any - # self.quantum_instance._run_config.parameterizations = None - - sampled_statefn_dicts = {} - for i, op_c in enumerate(circuit_sfns): - # Taking square root because we're replacing a statevector - # representation of probabilities. - reps = len(param_bindings) if param_bindings is not None else 1 - c_statefns = [] - for j in range(reps): - circ_index = (i * reps) + j - circ_results = results.data(circ_index) - - if "expval_measurement" in circ_results: - avg = circ_results["expval_measurement"] - # Will be replaced with just avg when eval is called later - num_qubits = circuit_sfns[0].num_qubits - result_sfn = DictStateFn( - "0" * num_qubits, - coeff=avg * op_c.coeff, - is_measurement=op_c.is_measurement, - from_operator=op_c.from_operator, - ) - elif self._statevector: - result_sfn = StateFn( - op_c.coeff * results.get_statevector(circ_index), - is_measurement=op_c.is_measurement, - ) - else: - shots = self.quantum_instance._run_config.shots - result_sfn = DictStateFn( - { - b: (v / shots) ** 0.5 * op_c.coeff - for (b, v) in results.get_counts(circ_index).items() - }, - is_measurement=op_c.is_measurement, - from_operator=op_c.from_operator, - ) - if self._attach_results: - result_sfn.execution_results = circ_results - c_statefns.append(result_sfn) - sampled_statefn_dicts[id(op_c)] = c_statefns - return sampled_statefn_dicts - - def _build_aer_params( - self, - circuit: QuantumCircuit, - building_param_tables: Dict[Tuple[int, int], List[float]], - input_params: Dict[Parameter, float], - ) -> None: - def resolve_param(inst_param): - if not isinstance(inst_param, ParameterExpression): - return None - param_mappings = {} - for param in inst_param._parameter_symbols.keys(): - if param not in input_params: - raise ValueError(f"unexpected parameter: {param}") - param_mappings[param] = input_params[param] - return float(inst_param.bind(param_mappings)) - - gate_index = 0 - for instruction in circuit.data: - param_index = 0 - for inst_param in instruction.operation.params: - val = resolve_param(inst_param) - if val is not None: - param_key = (gate_index, param_index) - if param_key in building_param_tables: - building_param_tables[param_key].append(val) - else: - building_param_tables[param_key] = [val] - param_index += 1 - gate_index += 1 - - def _prepare_parameterized_run_config( - self, param_bindings: List[Dict[Parameter, float]] - ) -> List[Any]: - - self.quantum_instance._run_config.parameterizations = [] - - if self._transpiled_circ_templates is None or len(self._transpiled_circ_templates) != len( - self._transpiled_circ_cache - ): - - # temporally resolve parameters of self._transpiled_circ_cache - # They will be overridden in Aer from the next iterations - self._transpiled_circ_templates = [ - circ.assign_parameters(_filter_params(circ, param_bindings[0])) - for circ in self._transpiled_circ_cache - ] - - for circ in self._transpiled_circ_cache: - building_param_tables: Dict[Tuple[int, int], List[float]] = {} - for param_binding in param_bindings: - self._build_aer_params(circ, building_param_tables, param_binding) - param_tables = [] - for gate_and_param_indices in building_param_tables: - gate_index = gate_and_param_indices[0] - param_index = gate_and_param_indices[1] - param_tables.append( - [[gate_index, param_index], building_param_tables[(gate_index, param_index)]] - ) - self.quantum_instance._run_config.parameterizations.append(param_tables) - - return self._transpiled_circ_templates - - def _clean_parameterized_run_config(self) -> None: - self.quantum_instance._run_config.parameterizations = [] - - -def _filter_params(circuit, param_dict): - """Remove all parameters from ``param_dict`` that are not in ``circuit``.""" - return {param: value for param, value in param_dict.items() if param in circuit.parameters} - - -class OperatorCache: - """A struct to cache an operator along with the circuits in contains.""" - - reduced_op_cache = None # the reduced operator - circuit_ops_cache: Optional[Dict[int, CircuitStateFn]] = None # the extracted circuits - transpiled_circ_cache = None # the transpiled circuits - transpile_before_bind = True # whether to transpile before binding parameters in the operator - transpiled_circ_templates: Optional[List[Any]] = None # transpiled circuit templates for Aer diff --git a/qiskit/opflow/converters/converter_base.py b/qiskit/opflow/converters/converter_base.py deleted file mode 100644 index e1ed07f2e1c4..000000000000 --- a/qiskit/opflow/converters/converter_base.py +++ /dev/null @@ -1,52 +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. - -"""ConverterBase Class""" - -from abc import ABC, abstractmethod - -from qiskit.opflow.operator_base import OperatorBase -from qiskit.utils.deprecation import deprecate_func - - -class ConverterBase(ABC): - r""" - Deprecated: Converters take an Operator and return a new Operator, generally isomorphic - in some way with the first, but with certain desired properties. For example, - a converter may accept ``CircuitOp`` and return a ``SummedOp`` of - ``PauliOps`` representing the circuit unitary. Converters may not - have polynomial space or time scaling in their operations. On the contrary, many - converters, such as a ``MatrixExpectation`` or ``MatrixEvolution``, which convert - ``PauliOps`` to ``MatrixOps`` internally, will require time or space exponential - in the number of qubits unless a clever trick is known (such as the use of sparse - matrices).""" - - @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: - pass - - @abstractmethod - def convert(self, operator: OperatorBase) -> OperatorBase: - """Accept the Operator and return the converted Operator - - Args: - operator: The Operator to convert. - - Returns: - The converted Operator. - - """ - raise NotImplementedError diff --git a/qiskit/opflow/converters/dict_to_circuit_sum.py b/qiskit/opflow/converters/dict_to_circuit_sum.py deleted file mode 100644 index 04facc6fe506..000000000000 --- a/qiskit/opflow/converters/dict_to_circuit_sum.py +++ /dev/null @@ -1,69 +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. - -"""DictToCircuitSum Class""" - -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn -from qiskit.opflow.state_fns.dict_state_fn import DictStateFn -from qiskit.opflow.state_fns.vector_state_fn import VectorStateFn -from qiskit.utils.deprecation import deprecate_func - - -class DictToCircuitSum(ConverterBase): - r""" - Deprecated: Converts ``DictStateFns`` or ``VectorStateFns`` to equivalent ``CircuitStateFns`` - or sums thereof. The behavior of this class can be mostly replicated by calling ``to_circuit_op`` - on an Operator, but with the added control of choosing whether to convert only ``DictStateFns`` - or ``VectorStateFns``, rather than both. - """ - - @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, convert_dicts: bool = True, convert_vectors: bool = True - ) -> None: - """ - Args: - traverse: Whether to recurse down into Operators with internal sub-operators for - conversion. - convert_dicts: Whether to convert VectorStateFn. - convert_vectors: Whether to convert DictStateFns. - """ - super().__init__() - self._traverse = traverse - self._convert_dicts = convert_dicts - self._convert_vectors = convert_vectors - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Convert the Operator to ``CircuitStateFns``, recursively if ``traverse`` is True. - - Args: - operator: The Operator to convert - - Returns: - The converted Operator. - """ - - if isinstance(operator, DictStateFn) and self._convert_dicts: - return CircuitStateFn.from_dict(operator.primitive) - if isinstance(operator, VectorStateFn) and self._convert_vectors: - return CircuitStateFn.from_vector(operator.to_matrix(massive=True)) - elif isinstance(operator, ListOp) and "Dict" in operator.primitive_strings(): - return operator.traverse(self.convert) - else: - return operator diff --git a/qiskit/opflow/converters/pauli_basis_change.py b/qiskit/opflow/converters/pauli_basis_change.py deleted file mode 100644 index 0d2213a761dc..000000000000 --- a/qiskit/opflow/converters/pauli_basis_change.py +++ /dev/null @@ -1,555 +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. - -"""PauliBasisChange Class""" - -from functools import partial, reduce -from typing import Callable, List, Optional, Tuple, Union, cast - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.operator_globals import H, I, S -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.quantum_info import Pauli -from qiskit.utils.deprecation import deprecate_func - - -class PauliBasisChange(ConverterBase): - r""" - Deprecated: Converter for changing Paulis into other bases. By default, the diagonal basis - composed only of Pauli {Z, I}^n is used as the destination basis to which to convert. - Meaning, if a Pauli containing X or Y terms is passed in, which cannot be - sampled or evolved natively on some Quantum hardware, the Pauli can be replaced by a - composition of a change of basis circuit and a Pauli composed of only Z - and I terms (diagonal), which can be evolved or sampled natively on the Quantum - hardware. - - The replacement function determines how the ``PauliOps`` should be replaced by their computed - change-of-basis ``CircuitOps`` and destination ``PauliOps``. Several convenient out-of-the-box - replacement functions have been added as static methods, such as ``measurement_replacement_fn``. - - This class uses the typical basis change method found in most Quantum Computing textbooks - (such as on page 210 of Nielsen and Chuang's, "Quantum Computation and Quantum Information", - ISBN: 978-1-107-00217-3), which involves diagonalizing the single-qubit Paulis with H and S† - gates, mapping the eigenvectors of the diagonalized origin Pauli to the diagonalized - destination Pauli using CNOTS, and then de-diagonalizing any single qubit Paulis to their - non-diagonal destination values. Many other methods are possible, as well as variations on - this method, such as the placement of the CNOT chains. - """ - - @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, - destination_basis: Optional[Union[Pauli, PauliOp]] = None, - traverse: bool = True, - replacement_fn: Optional[Callable] = None, - ) -> None: - """ - Args: - destination_basis: The Pauli into the basis of which the operators - will be converted. If None is specified, the destination basis will be the - diagonal ({I, Z}^n) basis requiring only single qubit rotations. - traverse: If true and the operator passed into convert contains sub-Operators, - such as ListOp, traverse the Operator and apply the conversion to every - applicable sub-operator within it. - replacement_fn: A function specifying what to do with the basis-change - ``CircuitOp`` and destination ``PauliOp`` when converting an Operator and - replacing converted values. By default, this will be - - 1) For StateFns (or Measurements): replacing the StateFn with - ComposedOp(StateFn(d), c) where c is the conversion circuit and d is the - destination Pauli, so the overall beginning and ending operators are - equivalent. - - 2) For non-StateFn Operators: replacing the origin p with c·d·c†, where c - is the conversion circuit and d is the destination, so the overall - beginning and ending operators are equivalent. - - """ - super().__init__() - if destination_basis is not None: - self.destination = destination_basis # type: ignore - else: - self._destination = None # type: Optional[PauliOp] - self._traverse = traverse - self._replacement_fn = replacement_fn or PauliBasisChange.operator_replacement_fn - - @property - def destination(self) -> Optional[PauliOp]: - r""" - The destination ``PauliOp``, or ``None`` if using the default destination, the diagonal - basis. - """ - return self._destination - - @destination.setter - def destination(self, dest: Union[Pauli, PauliOp]) -> None: - r""" - The destination ``PauliOp``, or ``None`` if using the default destination, the diagonal - basis. - """ - if isinstance(dest, Pauli): - dest = PauliOp(dest) - - if not isinstance(dest, PauliOp): - raise TypeError( - f"PauliBasisChange can only convert into Pauli bases, not {type(dest)}." - ) - self._destination = dest - - # TODO see whether we should make this performant by handling ListOps of Paulis later. - # pylint: disable=too-many-return-statements - def convert(self, operator: OperatorBase) -> OperatorBase: - r""" - Given a ``PauliOp``, or an Operator containing ``PauliOps`` if ``_traverse`` is True, - converts each Pauli into the basis specified by self._destination and a - basis-change-circuit, calls ``replacement_fn`` with these two Operators, and replaces - the ``PauliOps`` with the output of ``replacement_fn``. For example, for the built-in - ``operator_replacement_fn`` below, each PauliOp p will be replaced by the composition - of the basis-change Clifford ``CircuitOp`` c with the destination PauliOp d and c†, - such that p = c·d·c†, up to global phase. - - Args: - operator: The Operator to convert. - - Returns: - The converted Operator. - - """ - if ( - isinstance(operator, OperatorStateFn) - and isinstance(operator.primitive, PauliSumOp) - and operator.primitive.grouping_type == "TPB" - ): - primitive = operator.primitive.primitive.copy() - origin_x = reduce(np.logical_or, primitive.paulis.x) - origin_z = reduce(np.logical_or, primitive.paulis.z) - origin_pauli = Pauli((origin_z, origin_x)) - cob_instr_op, _ = self.get_cob_circuit(origin_pauli) - primitive.paulis.z = np.logical_or(primitive.paulis.x, primitive.paulis.z) - primitive.paulis.x = False - # The following line is because the deprecated PauliTable did not have a phase - # and did not track it, so phase=0 was always guaranteed. - # But the new PauliList may change phase. - primitive.paulis.phase = 0 - dest_pauli_sum_op = PauliSumOp(primitive, coeff=operator.coeff, grouping_type="TPB") - return self._replacement_fn(cob_instr_op, dest_pauli_sum_op) - - if ( - isinstance(operator, OperatorStateFn) - and isinstance(operator.primitive, SummedOp) - and all( - isinstance(op, PauliSumOp) and op.grouping_type == "TPB" - for op in operator.primitive.oplist - ) - ): - sf_list: List[OperatorBase] = [ - StateFn(op, is_measurement=operator.is_measurement) - for op in operator.primitive.oplist - ] - listop_of_statefns = SummedOp(oplist=sf_list, coeff=operator.coeff) - return listop_of_statefns.traverse(self.convert) - - if isinstance(operator, OperatorStateFn) and isinstance(operator.primitive, PauliSumOp): - operator = OperatorStateFn( - operator.primitive.to_pauli_op(), - coeff=operator.coeff, - is_measurement=operator.is_measurement, - ) - - if isinstance(operator, PauliSumOp): - operator = operator.to_pauli_op() - - if isinstance(operator, (Pauli, PauliOp)): - cob_instr_op, dest_pauli_op = self.get_cob_circuit(operator) - return self._replacement_fn(cob_instr_op, dest_pauli_op) - if isinstance(operator, StateFn) and "Pauli" in operator.primitive_strings(): - # If the StateFn/Meas only contains a Pauli, use it directly. - if isinstance(operator.primitive, PauliOp): - cob_instr_op, dest_pauli_op = self.get_cob_circuit(operator.primitive) - return self._replacement_fn(cob_instr_op, dest_pauli_op * operator.coeff) - # TODO make a canonical "distribute" or graph swap as method in ListOp? - elif operator.primitive.distributive: - if operator.primitive.abelian: - origin_pauli = self.get_tpb_pauli(operator.primitive) - cob_instr_op, _ = self.get_cob_circuit(origin_pauli) - diag_ops: List[OperatorBase] = [ - self.get_diagonal_pauli_op(op) for op in operator.primitive.oplist - ] - dest_pauli_op = operator.primitive.__class__( - diag_ops, coeff=operator.coeff, abelian=True - ) - return self._replacement_fn(cob_instr_op, dest_pauli_op) - else: - sf_list = [ - StateFn(op, is_measurement=operator.is_measurement) - for op in operator.primitive.oplist - ] - listop_of_statefns = operator.primitive.__class__( - oplist=sf_list, coeff=operator.coeff - ) - return listop_of_statefns.traverse(self.convert) - - elif ( - isinstance(operator, ListOp) - and self._traverse - and "Pauli" in operator.primitive_strings() - ): - # If ListOp is abelian we can find a single post-rotation circuit - # for the whole set. For now, - # assume operator can only be abelian if all elements are - # Paulis (enforced in AbelianGrouper). - if operator.abelian: - origin_pauli = self.get_tpb_pauli(operator) - cob_instr_op, _ = self.get_cob_circuit(origin_pauli) - oplist = cast(List[PauliOp], operator.oplist) - diag_ops = [self.get_diagonal_pauli_op(op) for op in oplist] - dest_list_op = operator.__class__(diag_ops, coeff=operator.coeff, abelian=True) - return self._replacement_fn(cob_instr_op, dest_list_op) - else: - return operator.traverse(self.convert) - - return operator - - @staticmethod - def measurement_replacement_fn( - cob_instr_op: PrimitiveOp, dest_pauli_op: Union[PauliOp, PauliSumOp, ListOp] - ) -> OperatorBase: - r""" - A built-in convenience replacement function which produces measurements - isomorphic to an ``OperatorStateFn`` measurement holding the origin ``PauliOp``. - - Args: - cob_instr_op: The basis-change ``CircuitOp``. - dest_pauli_op: The destination Pauli type operator. - - Returns: - The ``~StateFn @ CircuitOp`` composition equivalent to a measurement by the original - ``PauliOp``. - """ - return ComposedOp([StateFn(dest_pauli_op, is_measurement=True), cob_instr_op]) - - @staticmethod - def statefn_replacement_fn( - cob_instr_op: PrimitiveOp, dest_pauli_op: Union[PauliOp, PauliSumOp, ListOp] - ) -> OperatorBase: - r""" - A built-in convenience replacement function which produces state functions - isomorphic to an ``OperatorStateFn`` state function holding the origin ``PauliOp``. - - Args: - cob_instr_op: The basis-change ``CircuitOp``. - dest_pauli_op: The destination Pauli type operator. - - Returns: - The ``~CircuitOp @ StateFn`` composition equivalent to a state function defined by the - original ``PauliOp``. - """ - return ComposedOp([cob_instr_op.adjoint(), StateFn(dest_pauli_op)]) - - @staticmethod - def operator_replacement_fn( - cob_instr_op: PrimitiveOp, dest_pauli_op: Union[PauliOp, PauliSumOp, ListOp] - ) -> OperatorBase: - r""" - A built-in convenience replacement function which produces Operators - isomorphic to the origin ``PauliOp``. - - Args: - cob_instr_op: The basis-change ``CircuitOp``. - dest_pauli_op: The destination ``PauliOp``. - - Returns: - The ``~CircuitOp @ PauliOp @ CircuitOp`` composition isomorphic to the - original ``PauliOp``. - """ - return ComposedOp([cob_instr_op.adjoint(), dest_pauli_op, cob_instr_op]) - - def get_tpb_pauli(self, list_op: ListOp) -> Pauli: - r""" - Gets the Pauli (not ``PauliOp``!) whose diagonalizing single-qubit rotations is a - superset of the diagonalizing single-qubit rotations for each of the Paulis in - ``list_op``. TPB stands for `Tensor Product Basis`. - - Args: - list_op: the :class:`ListOp` whose TPB Pauli to return. - - Returns: - The TBP Pauli. - - """ - oplist = cast(List[PauliOp], list_op.oplist) - origin_z = reduce(np.logical_or, [p_op.primitive.z for p_op in oplist]) - origin_x = reduce(np.logical_or, [p_op.primitive.x for p_op in oplist]) - return Pauli((origin_z, origin_x)) - - def get_diagonal_pauli_op(self, pauli_op: PauliOp) -> PauliOp: - """Get the diagonal ``PualiOp`` to which ``pauli_op`` could be rotated with only - single-qubit operations. - - Args: - pauli_op: The ``PauliOp`` whose diagonal to compute. - - Returns: - The diagonal ``PauliOp``. - """ - return PauliOp( - Pauli( - ( - np.logical_or(pauli_op.primitive.z, pauli_op.primitive.x), - [False] * pauli_op.num_qubits, - ) - ), - coeff=pauli_op.coeff, - ) - - def get_diagonalizing_clifford(self, pauli: Union[Pauli, PauliOp]) -> OperatorBase: - r""" - Construct a ``CircuitOp`` with only single-qubit gates which takes the eigenvectors - of ``pauli`` to eigenvectors composed only of \|0⟩ and \|1⟩ tensor products. Equivalently, - finds the basis-change circuit to take ``pauli`` to a diagonal ``PauliOp`` composed only - of Z and I tensor products. - - Note, underlying Pauli bits are in Qiskit endianness, so we need to reverse before we - begin composing with Operator flow. - - Args: - pauli: the ``Pauli`` or ``PauliOp`` to whose diagonalizing circuit to compute. - - Returns: - The diagonalizing ``CircuitOp``. - - """ - if isinstance(pauli, PauliOp): - pauli = pauli.primitive - - tensorall = cast( - Callable[[List[PrimitiveOp]], PrimitiveOp], partial(reduce, lambda x, y: x.tensor(y)) - ) - - y_to_x_origin = tensorall( - [S if has_y else I for has_y in reversed(np.logical_and(pauli.x, pauli.z))] - ).adjoint() - x_to_z_origin = tensorall( # pylint: disable=assignment-from-no-return - [H if has_x else I for has_x in reversed(pauli.x)] - ) - return x_to_z_origin.compose(y_to_x_origin) - - def pad_paulis_to_equal_length( - self, pauli_op1: PauliOp, pauli_op2: PauliOp - ) -> Tuple[PauliOp, PauliOp]: - r""" - If ``pauli_op1`` and ``pauli_op2`` do not act over the same number of qubits, pad - identities to the end of the shorter of the two so they are of equal length. Padding is - applied to the end of the Paulis. Note that the Terra represents Paulis in big-endian - order, so this will appear as padding to the beginning of the Pauli x and z bit arrays. - - Args: - pauli_op1: A pauli_op to possibly pad. - pauli_op2: A pauli_op to possibly pad. - - Returns: - A tuple containing the padded PauliOps. - - """ - num_qubits = max(pauli_op1.num_qubits, pauli_op2.num_qubits) - pauli_1, pauli_2 = pauli_op1.primitive, pauli_op2.primitive - - # Padding to the end of the Pauli, but remember that Paulis are in reverse endianness. - if not len(pauli_1.z) == num_qubits: - missing_qubits = num_qubits - len(pauli_1.z) - pauli_1 = Pauli( - ( - ([False] * missing_qubits) + pauli_1.z.tolist(), - ([False] * missing_qubits) + pauli_1.x.tolist(), - ) - ) - if not len(pauli_2.z) == num_qubits: - missing_qubits = num_qubits - len(pauli_2.z) - pauli_2 = Pauli( - ( - ([False] * missing_qubits) + pauli_2.z.tolist(), - ([False] * missing_qubits) + pauli_2.x.tolist(), - ) - ) - - return PauliOp(pauli_1, coeff=pauli_op1.coeff), PauliOp(pauli_2, coeff=pauli_op2.coeff) - - def construct_cnot_chain(self, diag_pauli_op1: PauliOp, diag_pauli_op2: PauliOp) -> PrimitiveOp: - r""" - Construct a ``CircuitOp`` (or ``PauliOp`` if equal to the identity) which takes the - eigenvectors of ``diag_pauli_op1`` to the eigenvectors of ``diag_pauli_op2``, - assuming both are diagonal (or performing this operation on their diagonalized Paulis - implicitly if not). This works by the insight that the eigenvalue of a diagonal Pauli's - eigenvector is equal to or -1 if the parity is 1 and 1 if the parity is 0, or - 1 - (2 * parity). Therefore, using CNOTs, we can write the parity of diag_pauli_op1's - significant bits onto some qubit, and then write out that parity onto diag_pauli_op2's - significant bits. - - Args: - diag_pauli_op1: The origin ``PauliOp``. - diag_pauli_op2: The destination ``PauliOp``. - - Return: - The ``PrimitiveOp`` performs the mapping. - """ - # TODO be smarter about connectivity and actual distance between pauli and destination - # TODO be smarter in general - - pauli_1 = ( - diag_pauli_op1.primitive if isinstance(diag_pauli_op1, PauliOp) else diag_pauli_op1 - ) - pauli_2 = ( - diag_pauli_op2.primitive if isinstance(diag_pauli_op2, PauliOp) else diag_pauli_op2 - ) - origin_sig_bits = np.logical_or(pauli_1.z, pauli_1.x) - destination_sig_bits = np.logical_or(pauli_2.z, pauli_2.x) - num_qubits = max(len(pauli_1.z), len(pauli_2.z)) - - sig_equal_sig_bits = np.logical_and(origin_sig_bits, destination_sig_bits) - non_equal_sig_bits = np.logical_not(origin_sig_bits == destination_sig_bits) - # Equivalent to np.logical_xor(origin_sig_bits, destination_sig_bits) - - if not any(non_equal_sig_bits): - return I ^ num_qubits - - # I am deeply sorry for this code, but I don't know another way to do it. - sig_in_origin_only_indices = np.extract( - np.logical_and(non_equal_sig_bits, origin_sig_bits), np.arange(num_qubits) - ) - sig_in_dest_only_indices = np.extract( - np.logical_and(non_equal_sig_bits, destination_sig_bits), np.arange(num_qubits) - ) - - if len(sig_in_origin_only_indices) > 0 and len(sig_in_dest_only_indices) > 0: - origin_anchor_bit = min(sig_in_origin_only_indices) - dest_anchor_bit = min(sig_in_dest_only_indices) - else: - # Set to lowest equal bit - origin_anchor_bit = min(np.extract(sig_equal_sig_bits, np.arange(num_qubits))) - dest_anchor_bit = origin_anchor_bit - - cnots = QuantumCircuit(num_qubits) - # Step 3) Take the indices of bits which are sig_bits in - # pauli but but not in dest, and cnot them to the pauli anchor. - for i in sig_in_origin_only_indices: - if not i == origin_anchor_bit: - cnots.cx(i, origin_anchor_bit) - - # Step 4) - if not origin_anchor_bit == dest_anchor_bit: - cnots.swap(origin_anchor_bit, dest_anchor_bit) - - # Need to do this or a Terra bug sometimes flips cnots. No time to investigate. - cnots.id(0) - - # Step 6) - for i in sig_in_dest_only_indices: - if not i == dest_anchor_bit: - cnots.cx(i, dest_anchor_bit) - - return PrimitiveOp(cnots) - - def get_cob_circuit(self, origin: Union[Pauli, PauliOp]) -> Tuple[PrimitiveOp, PauliOp]: - r""" - Construct an Operator which maps the +1 and -1 eigenvectors - of the origin Pauli to the +1 and -1 eigenvectors of the destination Pauli. It does so by - - 1) converting any \|i+⟩ or \|i+⟩ eigenvector bits in the origin to - \|+⟩ and \|-⟩ with S†s, then - - 2) converting any \|+⟩ or \|+⟩ eigenvector bits in the converted origin to - \|0⟩ and \|1⟩ with Hs, then - - 3) writing the parity of the significant (Z-measured, rather than I) - bits in the origin to a single - "origin anchor bit," using cnots, which will hold the parity of these bits, - - 4) swapping the parity of the pauli anchor bit into a destination anchor bit using - a swap gate (only if they are different, if there are any bits which are significant - in both origin and dest, we set both anchors to one of these bits to avoid a swap). - - 5) writing the parity of the destination anchor bit into the other significant bits - of the destination, - - 6) converting the \|0⟩ and \|1⟩ significant eigenvector bits to \|+⟩ and \|-⟩ eigenvector - bits in the destination where the destination demands it - (e.g. pauli.x == true for a bit), using Hs 8) converting the \|+⟩ and \|-⟩ - significant eigenvector bits to \|i+⟩ and \|i-⟩ eigenvector bits in the - destination where the destination demands it - (e.g. pauli.x == true and pauli.z == true for a bit), using Ss - - Args: - origin: The ``Pauli`` or ``PauliOp`` to map. - - Returns: - A tuple of a ``PrimitiveOp`` which equals the basis change mapping and a ``PauliOp`` - which equals the destination basis. - - Raises: - TypeError: Attempting to convert from non-Pauli origin. - ValueError: Attempting to change a non-identity Pauli to an identity Pauli, or vice - versa. - - """ - - # If pauli is an PrimitiveOp, extract the Pauli - if isinstance(origin, Pauli): - origin = PauliOp(origin) - - if not isinstance(origin, PauliOp): - raise TypeError( - f"PauliBasisChange can only convert Pauli-based OpPrimitives, not {type(origin)}" - ) - - # If no destination specified, assume nearest Pauli in {Z,I}^n basis, - # the standard basis change for expectations. - destination = self.destination or self.get_diagonal_pauli_op(origin) - - # Pad origin or destination if either are not as long as the other - origin, destination = self.pad_paulis_to_equal_length(origin, destination) - - origin_sig_bits = np.logical_or(origin.primitive.x, origin.primitive.z) - destination_sig_bits = np.logical_or(destination.primitive.x, destination.primitive.z) - if not any(origin_sig_bits) or not any(destination_sig_bits): - if not (any(origin_sig_bits) or any(destination_sig_bits)): - # Both all Identity, just return Identities - return I ^ origin.num_qubits, destination - else: - # One is Identity, one is not - raise ValueError("Cannot change to or from a fully Identity Pauli.") - - # Steps 1 and 2 - cob_instruction = self.get_diagonalizing_clifford(origin) - - # Construct CNOT chain, assuming full connectivity... - Steps 3)-5) - cob_instruction = self.construct_cnot_chain(origin, destination).compose(cob_instruction) - - # Step 6 and 7 - dest_diagonlizing_clifford = self.get_diagonalizing_clifford(destination).adjoint() - cob_instruction = dest_diagonlizing_clifford.compose(cob_instruction) - - return cast(PrimitiveOp, cob_instruction), destination diff --git a/qiskit/opflow/converters/two_qubit_reduction.py b/qiskit/opflow/converters/two_qubit_reduction.py deleted file mode 100644 index aa34d2cef4ca..000000000000 --- a/qiskit/opflow/converters/two_qubit_reduction.py +++ /dev/null @@ -1,101 +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. - -"""Z2 Symmetry Tapering Converter Class""" - -import logging -from typing import List, Tuple, Union, cast - -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.tapered_pauli_sum_op import Z2Symmetries -from qiskit.quantum_info import Pauli -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class TwoQubitReduction(ConverterBase): - """ - Deprecated: Two qubit reduction converter which eliminates the central and last - qubit in a list of Pauli that has diagonal operators (Z,I) at those positions. - - Chemistry specific method: - It can be used to taper two qubits in parity and binary-tree mapped - fermionic Hamiltonians when the spin orbitals are ordered in two spin - sectors, (block spin order) according to the number of particles in the system. - """ - - @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]]): - """ - Args: - num_particles: number of particles, if it is a list, - the first number is alpha and the second number if beta. - """ - super().__init__() - if isinstance(num_particles, (tuple, list)): - num_alpha = num_particles[0] - num_beta = num_particles[1] - else: - num_alpha = num_particles // 2 - num_beta = num_particles // 2 - - par_1 = 1 if (num_alpha + num_beta) % 2 == 0 else -1 - par_2 = 1 if num_alpha % 2 == 0 else -1 - self._tapering_values = [par_2, par_1] - - def convert(self, operator: OperatorBase) -> OperatorBase: - """ - Converts the Operator to tapered one by Z2 symmetries. - - Args: - operator: the operator - Returns: - A new operator whose qubit number is reduced by 2. - """ - if not isinstance(operator, PauliSumOp): - return operator - - operator = cast(PauliSumOp, operator) - - if operator.is_zero(): - logger.info( - "Operator is empty, can not do two qubit reduction. Return the empty operator back." - ) - return PauliSumOp.from_list([("I" * (operator.num_qubits - 2), 0)]) - - num_qubits = operator.num_qubits - last_idx = num_qubits - 1 - mid_idx = num_qubits // 2 - 1 - sq_list = [mid_idx, last_idx] - - # build symmetries, sq_paulis: - symmetries, sq_paulis = [], [] - for idx in sq_list: - pauli_str = ["I"] * num_qubits - - pauli_str[idx] = "Z" - z_sym = Pauli("".join(pauli_str)[::-1]) - symmetries.append(z_sym) - - pauli_str[idx] = "X" - sq_pauli = Pauli("".join(pauli_str)[::-1]) - sq_paulis.append(sq_pauli) - - z2_symmetries = Z2Symmetries(symmetries, sq_paulis, sq_list, self._tapering_values) - return z2_symmetries.taper(operator) diff --git a/qiskit/opflow/evolutions/__init__.py b/qiskit/opflow/evolutions/__init__.py deleted file mode 100644 index 6bfeb7d1490d..000000000000 --- a/qiskit/opflow/evolutions/__init__.py +++ /dev/null @@ -1,108 +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. - -""" -Operator Evolutions (:mod:`qiskit.opflow.evolutions`) -===================================================== - -.. currentmodule:: qiskit.opflow.evolutions - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Evolutions are converters which traverse an Operator tree, replacing -any :class:`EvolvedOp` `e` with a Schrodinger equation-style evolution -:class:`~qiskit.opflow.primitive_ops.CircuitOp` -equalling or approximating the matrix exponential of -i * the Operator contained inside -(`e.primitive`). The Evolutions are essentially implementations of Hamiltonian Simulation -algorithms, including various methods for Trotterization. - -The :class:`EvolvedOp` is simply a placeholder signifying that the Operator inside it should be -converted to its exponential by the Evolution converter. All Operators -(not :mod:`~qiskit.opflow.state_fns`) have -``.exp_i()`` methods which either return the exponential of the Operator directly, -or an :class:`EvolvedOp` containing the Operator. - - -Note: - Evolutions work with parameterized Operator coefficients, so - ``my_expectation.convert((t * H).exp_i())``, where t is a scalar or Terra Parameter and H - is an Operator, will produce a :class:`~qiskit.opflow.primitive_ops.CircuitOp` - equivalent to e^iHt. - -Evolution Base Class --------------------- - -The EvolutionBase class gives an interface for algorithms to ask for Evolutions as -execution settings. For example, if an algorithm contains an Operator evolution step within it, -such as :class:`~qiskit.algorithms.QAOA`, the algorithm can give the opportunity for the user -to pass an EvolutionBase of their choice to be used in that evolution step. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - EvolutionBase - -Evolutions ----------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - EvolutionFactory - EvolvedOp - MatrixEvolution - PauliTrotterEvolution - -Trotterizations ---------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - TrotterizationBase - TrotterizationFactory - Trotter - Suzuki - QDrift -""" - -from .evolution_base import EvolutionBase -from .evolution_factory import EvolutionFactory -from .evolved_op import EvolvedOp -from .pauli_trotter_evolution import PauliTrotterEvolution -from .matrix_evolution import MatrixEvolution -from .trotterizations import TrotterizationBase, TrotterizationFactory, Trotter, Suzuki, QDrift - -# TODO co-diagonalization of Abelian groups in PauliTrotterEvolution -# TODO quantum signal processing/qubitization -# TODO evolve by density matrix (need to add iexp to operator_state_fn) -# TODO linear combination evolution - -__all__ = [ - "EvolutionBase", - "EvolutionFactory", - "EvolvedOp", - "PauliTrotterEvolution", - "MatrixEvolution", - "TrotterizationBase", - "TrotterizationFactory", - "Trotter", - "Suzuki", - "QDrift", -] diff --git a/qiskit/opflow/evolutions/evolution_base.py b/qiskit/opflow/evolutions/evolution_base.py deleted file mode 100644 index 4c168078e0de..000000000000 --- a/qiskit/opflow/evolutions/evolution_base.py +++ /dev/null @@ -1,58 +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. - -"""EvolutionBase Class""" - -from abc import ABC, abstractmethod - -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.converters.converter_base import ConverterBase -from qiskit.utils.deprecation import deprecate_func - - -class EvolutionBase(ConverterBase, ABC): - r""" - Deprecated: A base for Evolution converters. - Evolutions are converters which traverse an Operator tree, replacing any ``EvolvedOp`` `e` - with a Schrodinger equation-style evolution ``CircuitOp`` equalling or approximating the - matrix exponential of -i * the Operator contained inside (`e.primitive`). The Evolutions are - essentially implementations of Hamiltonian Simulation algorithms, including various methods - for Trotterization. - - """ - - @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: - super().__init__() - - @abstractmethod - def convert(self, operator: OperatorBase) -> OperatorBase: - """Traverse the operator, replacing any ``EvolutionOps`` with their equivalent evolution - ``CircuitOps``. - - Args: - operator: The Operator to convert. - - Returns: - The converted Operator, with ``EvolutionOps`` replaced by ``CircuitOps``. - - """ - raise NotImplementedError - - # TODO @abstractmethod - # def error_bounds(self): - # """ error bounds """ - # raise NotImplementedError diff --git a/qiskit/opflow/evolutions/evolution_factory.py b/qiskit/opflow/evolutions/evolution_factory.py deleted file mode 100644 index 1f9a64e0ad7f..000000000000 --- a/qiskit/opflow/evolutions/evolution_factory.py +++ /dev/null @@ -1,58 +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. - -"""EvolutionFactory Class""" - -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.evolutions.evolution_base import EvolutionBase -from qiskit.opflow.evolutions.pauli_trotter_evolution import PauliTrotterEvolution -from qiskit.opflow.evolutions.matrix_evolution import MatrixEvolution -from qiskit.utils.deprecation import deprecate_func - - -class EvolutionFactory: - """Deprecated: A factory class for convenient automatic selection of an - Evolution algorithm based on the Operator to be converted. - """ - - @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: - r""" - A factory method for convenient automatic selection of an Evolution algorithm based on the - Operator to be converted. - - Args: - operator: the Operator being evolved - - Returns: - EvolutionBase: the ``EvolutionBase`` best suited to evolve operator. - - Raises: - ValueError: If operator is not of a composition for which we know the best Evolution - method. - - """ - primitive_strings = operator.primitive_strings() - if "Matrix" in primitive_strings: - return MatrixEvolution() - - elif "Pauli" in primitive_strings or "SparsePauliOp" in primitive_strings: - # TODO figure out what to do based on qubits and hamming weight. - return PauliTrotterEvolution() - - else: - raise ValueError("Evolutions of mixed Operators not yet supported.") diff --git a/qiskit/opflow/evolutions/evolved_op.py b/qiskit/opflow/evolutions/evolved_op.py deleted file mode 100644 index cf3a8ef53ae8..000000000000 --- a/qiskit/opflow/evolutions/evolved_op.py +++ /dev/null @@ -1,180 +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. - -"""EvolutionOp Class""" - -from typing import List, Optional, Set, Union, cast - -import numpy as np -import scipy - -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.matrix_op import MatrixOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class EvolvedOp(PrimitiveOp): - r""" - Deprecated: Class for wrapping Operator Evolutions for compilation (``convert``) by an EvolutionBase - method later, essentially acting as a placeholder. Note that EvolvedOp is a weird case of - PrimitiveOp. It happens to be that it fits into the PrimitiveOp interface nearly perfectly, - and it essentially represents a placeholder for a PrimitiveOp later, even though it doesn't - actually hold a primitive object. We could have chosen for it to be an OperatorBase, - but would have ended up copying and pasting a lot of code from PrimitiveOp.""" - primitive: 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: OperatorBase, coeff: Union[complex, ParameterExpression] = 1.0 - ) -> None: - """ - Args: - primitive: The operator being wrapped to signify evolution later. - coeff: A coefficient multiplying the operator - """ - super().__init__(primitive, coeff=coeff) - - def primitive_strings(self) -> Set[str]: - return self.primitive.primitive_strings() - - @property - def num_qubits(self) -> int: - return self.primitive.num_qubits - - def add(self, other: OperatorBase) -> Union["EvolvedOp", SummedOp]: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over operators with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - if isinstance(other, EvolvedOp) and self.primitive == other.primitive: - return EvolvedOp(self.primitive, coeff=self.coeff + other.coeff) - - if isinstance(other, SummedOp): - op_list = [cast(OperatorBase, self)] + other.oplist - return SummedOp(op_list) - - return SummedOp([self, other]) - - def adjoint(self) -> "EvolvedOp": - return EvolvedOp(self.primitive.adjoint() * -1, coeff=self.coeff.conjugate()) - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, EvolvedOp) or not self.coeff == other.coeff: - return False - - return self.primitive == other.primitive - - def tensor(self, other: OperatorBase) -> TensoredOp: - if isinstance(other, TensoredOp): - return TensoredOp([cast(OperatorBase, self)] + other.oplist) - - return TensoredOp([self, other]) - - def _expand_dim(self, num_qubits: int) -> TensoredOp: - # pylint: disable=cyclic-import - from ..operator_globals import I - - return self.tensor(I ^ num_qubits) - - def permute(self, permutation: List[int]) -> "EvolvedOp": - return EvolvedOp(self.primitive.permute(permutation), coeff=self.coeff) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - if front: - return other.compose(new_self) - if isinstance(other, ComposedOp): - return ComposedOp([new_self] + other.oplist) - - return ComposedOp([new_self, other]) - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return f"e^(-i*{prim_str})" - else: - return f"{self.coeff} * e^(-i*{prim_str})" - - def __repr__(self) -> str: - return f"EvolvedOp({repr(self.primitive)}, coeff={self.coeff})" - - def reduce(self) -> "EvolvedOp": - return EvolvedOp(self.primitive.reduce(), coeff=self.coeff) - - def assign_parameters(self, param_dict: dict) -> Union["EvolvedOp", ListOp]: - param_value = self.coeff - if isinstance(self.coeff, ParameterExpression): - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if self.coeff.parameters <= set(unrolled_dict.keys()): - binds = {param: unrolled_dict[param] for param in self.coeff.parameters} - param_value = float(self.coeff.bind(binds)) - return EvolvedOp(self.primitive.bind_parameters(param_dict), coeff=param_value) - - def eval( - self, front: Optional[Union[str, dict, np.ndarray, OperatorBase, Statevector]] = None - ) -> Union[OperatorBase, complex]: - return cast(Union[OperatorBase, complex], self.to_matrix_op().eval(front=front)) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - if ( - isinstance(self.primitive, ListOp) - and self.primitive.__class__.__name__ == ListOp.__name__ - ): - return np.array( - [ - op.exp_i().to_matrix(massive=massive) * self.primitive.coeff * self.coeff - for op in self.primitive.oplist - ], - dtype=complex, - ) - - prim_mat = -1.0j * self.primitive.to_matrix() - return scipy.linalg.expm(prim_mat) * self.coeff - - def to_matrix_op(self, massive: bool = False) -> Union[ListOp, MatrixOp]: - """Returns a ``MatrixOp`` equivalent to this Operator.""" - primitive = self.primitive - if isinstance(primitive, ListOp) and primitive.__class__.__name__ == ListOp.__name__: - return ListOp( - [op.exp_i().to_matrix_op() for op in primitive.oplist], - coeff=primitive.coeff * self.coeff, - ) - - prim_mat = EvolvedOp(primitive).to_matrix(massive=massive) - return MatrixOp(prim_mat, coeff=self.coeff) - - def log_i(self, massive: bool = False) -> OperatorBase: - return self.primitive * self.coeff - - def to_instruction(self, massive: bool = False) -> Instruction: - mat_op = self.to_matrix_op(massive=massive) - if not isinstance(mat_op, MatrixOp): - raise OpflowError("to_instruction is not allowed for ListOp.") - return mat_op.to_instruction() diff --git a/qiskit/opflow/evolutions/matrix_evolution.py b/qiskit/opflow/evolutions/matrix_evolution.py deleted file mode 100644 index 7ecdfa79ee98..000000000000 --- a/qiskit/opflow/evolutions/matrix_evolution.py +++ /dev/null @@ -1,74 +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. - -"""MatrixEvolution Class""" - -import logging - -from qiskit.opflow.evolutions.evolution_base import EvolutionBase -from qiskit.opflow.evolutions.evolved_op import EvolvedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.matrix_op import MatrixOp -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class MatrixEvolution(EvolutionBase): - r""" - Deprecated: Performs Evolution by classical matrix exponentiation, constructing a circuit with - ``UnitaryGates`` or ``HamiltonianGates`` containing the exponentiation of the Operator. - """ - - @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: - super().__init__() - - def convert(self, operator: OperatorBase) -> OperatorBase: - r""" - Traverse the operator, replacing ``EvolvedOps`` with ``CircuitOps`` containing - ``UnitaryGates`` or ``HamiltonianGates`` (if self.coeff is a ``ParameterExpression``) - equalling the exponentiation of -i * operator. This is done by converting the - ``EvolvedOp.primitive`` to a ``MatrixOp`` and simply calling ``.exp_i()`` on that. - - Args: - operator: The Operator to convert. - - Returns: - The converted operator. - """ - if isinstance(operator, EvolvedOp): - if not {"Matrix"} == operator.primitive_strings(): - logger.warning( - "Evolved Hamiltonian is not composed of only MatrixOps, converting " - "to Matrix representation, which can be expensive." - ) - # Setting massive=False because this conversion is implicit. User can perform this - # action on the Hamiltonian with massive=True explicitly if they so choose. - # TODO explore performance to see whether we should avoid doing this repeatedly - matrix_ham = operator.primitive.to_matrix_op(massive=False) - operator = EvolvedOp(matrix_ham, coeff=operator.coeff) - - if isinstance(operator.primitive, ListOp): - return operator.primitive.exp_i() * operator.coeff - elif isinstance(operator.primitive, (MatrixOp, PauliOp)): - return operator.primitive.exp_i() - elif isinstance(operator, ListOp): - return operator.traverse(self.convert).reduce() - - return operator diff --git a/qiskit/opflow/evolutions/pauli_trotter_evolution.py b/qiskit/opflow/evolutions/pauli_trotter_evolution.py deleted file mode 100644 index 7ed93d04770c..000000000000 --- a/qiskit/opflow/evolutions/pauli_trotter_evolution.py +++ /dev/null @@ -1,204 +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. - -"""PauliTrotterEvolution Class""" - -import logging -from typing import Optional, Union, cast - -import numpy as np - -from qiskit.circuit.library import PauliEvolutionGate -from qiskit.synthesis import LieTrotter, SuzukiTrotter -from qiskit.opflow.converters.pauli_basis_change import PauliBasisChange -from qiskit.opflow.evolutions.evolution_base import EvolutionBase -from qiskit.opflow.evolutions.evolved_op import EvolvedOp -from qiskit.opflow.evolutions.trotterizations.trotterization_base import TrotterizationBase -from qiskit.opflow.evolutions.trotterizations.trotterization_factory import TrotterizationFactory -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.operator_globals import I, Z -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.circuit_op import CircuitOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.utils.deprecation import deprecate_func - -# TODO uncomment when we implement Abelian grouped evolution. -# from qiskit.opflow.converters.abelian_grouper import AbelianGrouper - -logger = logging.getLogger(__name__) - - -class PauliTrotterEvolution(EvolutionBase): - r""" - Deprecated: An Evolution algorithm replacing exponentiated sums of Paulis by changing - them each to the Z basis, rotating with an rZ, changing back, and Trotterizing. - - More specifically, we compute basis change circuits for each Pauli into a single-qubit Z, - evolve the Z by the desired evolution time with an rZ gate, and change the basis back using - the adjoint of the original basis change circuit. For sums of Paulis, the individual Pauli - evolution circuits are composed together by Trotterization scheme. - """ - - @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, - trotter_mode: Optional[Union[str, TrotterizationBase]] = "trotter", - reps: Optional[int] = 1, - # TODO uncomment when we implement Abelian grouped evolution. - # group_paulis: Optional[bool] = False - ) -> None: - """ - Args: - trotter_mode: A string ('trotter', 'suzuki', or 'qdrift') to pass to the - TrotterizationFactory, or a TrotterizationBase, indicating how to combine - individual Pauli evolution circuits to equal the exponentiation of the Pauli sum. - reps: How many Trotterization repetitions to make, to improve the approximation - accuracy. - # TODO uncomment when we implement Abelian grouped evolution. - # group_paulis: Whether to group Pauli sums into Abelian - # sub-groups, so a single diagonalization circuit can be used for each group - # rather than each Pauli. - """ - super().__init__() - if isinstance(trotter_mode, TrotterizationBase): - self._trotter = trotter_mode - else: - self._trotter = TrotterizationFactory.build(mode=trotter_mode, reps=reps) - - # TODO uncomment when we implement Abelian grouped evolution. - # self._grouper = AbelianGrouper() if group_paulis else None - - @property - def trotter(self) -> TrotterizationBase: - """TrotterizationBase used to evolve SummedOps.""" - return self._trotter - - @trotter.setter - def trotter(self, trotter: TrotterizationBase) -> None: - """Set TrotterizationBase used to evolve SummedOps.""" - self._trotter = trotter - - def convert(self, operator: OperatorBase) -> OperatorBase: - r""" - Traverse the operator, replacing ``EvolvedOps`` with ``CircuitOps`` containing - Trotterized evolutions equalling the exponentiation of -i * operator. - - Args: - operator: The Operator to convert. - - Returns: - The converted operator. - """ - # TODO uncomment when we implement Abelian grouped evolution. - # if self._grouper: - # # Sort into commuting groups - # operator = self._grouper.convert(operator).reduce() - return self._recursive_convert(operator) - - def _get_evolution_synthesis(self): - """Return the ``EvolutionSynthesis`` corresponding to this Trotterization.""" - if self.trotter.order == 1: - return LieTrotter(reps=self.trotter.reps) - return SuzukiTrotter(reps=self.trotter.reps, order=self.trotter.order) - - def _recursive_convert(self, operator: OperatorBase) -> OperatorBase: - if isinstance(operator, EvolvedOp): - if isinstance(operator.primitive, (PauliOp, PauliSumOp)): - pauli = operator.primitive.primitive - time = operator.coeff * operator.primitive.coeff - evo = PauliEvolutionGate( - pauli, time=time, synthesis=self._get_evolution_synthesis() - ) - return CircuitOp(evo) - # operator = EvolvedOp(operator.primitive.to_pauli_op(), coeff=operator.coeff) - if not {"Pauli"} == operator.primitive_strings(): - logger.warning( - "Evolved Hamiltonian is not composed of only Paulis, converting to " - "Pauli representation, which can be expensive." - ) - # Setting massive=False because this conversion is implicit. User can perform this - # action on the Hamiltonian with massive=True explicitly if they so choose. - # TODO explore performance to see whether we should avoid doing this repeatedly - pauli_ham = operator.primitive.to_pauli_op(massive=False) - operator = EvolvedOp(pauli_ham, coeff=operator.coeff) - - if isinstance(operator.primitive, SummedOp): - # TODO uncomment when we implement Abelian grouped evolution. - # if operator.primitive.abelian: - # return self.evolution_for_abelian_paulisum(operator.primitive) - # else: - # Collect terms that are not the identity. - oplist = [ - x - for x in operator.primitive - if not isinstance(x, PauliOp) or sum(x.primitive.x + x.primitive.z) != 0 - ] - # Collect the coefficients of any identity terms, - # which become global phases when exponentiated. - identity_phases = [ - x.coeff - for x in operator.primitive - if isinstance(x, PauliOp) and sum(x.primitive.x + x.primitive.z) == 0 - ] - # Construct sum without the identity operators. - new_primitive = SummedOp(oplist, coeff=operator.primitive.coeff) - trotterized = self.trotter.convert(new_primitive) - circuit_no_identities = self._recursive_convert(trotterized) - # Set the global phase of the QuantumCircuit to account for removed identity terms. - global_phase = -sum(identity_phases) * operator.primitive.coeff - circuit_no_identities.primitive.global_phase = global_phase - return circuit_no_identities - # Covers ListOp, ComposedOp, TensoredOp - elif isinstance(operator.primitive, ListOp): - converted_ops = [self._recursive_convert(op) for op in operator.primitive.oplist] - return operator.primitive.__class__(converted_ops, coeff=operator.coeff) - elif isinstance(operator, ListOp): - return operator.traverse(self.convert).reduce() - - return operator - - def evolution_for_pauli(self, pauli_op: PauliOp) -> PrimitiveOp: - r""" - Compute evolution Operator for a single Pauli using a ``PauliBasisChange``. - - Args: - pauli_op: The ``PauliOp`` to evolve. - - Returns: - A ``PrimitiveOp``, either the evolution ``CircuitOp`` or a ``PauliOp`` equal to the - identity if pauli_op is the identity. - """ - - def replacement_fn(cob_instr_op, dest_pauli_op): - z_evolution = dest_pauli_op.exp_i() - # Remember, circuit composition order is mirrored operator composition order. - return cob_instr_op.adjoint().compose(z_evolution).compose(cob_instr_op) - - # Note: PauliBasisChange will pad destination with identities - # to produce correct CoB circuit - sig_bits = np.logical_or(pauli_op.primitive.z, pauli_op.primitive.x) - a_sig_bit = int(max(np.extract(sig_bits, np.arange(pauli_op.num_qubits)[::-1]))) - destination = (I.tensorpower(a_sig_bit)) ^ (Z * pauli_op.coeff) - cob = PauliBasisChange(destination_basis=destination, replacement_fn=replacement_fn) - return cast(PrimitiveOp, cob.convert(pauli_op)) - - # TODO implement Abelian grouped evolution. - def evolution_for_abelian_paulisum(self, op_sum: SummedOp) -> PrimitiveOp: - """Evolution for abelian pauli sum""" - raise NotImplementedError diff --git a/qiskit/opflow/evolutions/trotterizations/__init__.py b/qiskit/opflow/evolutions/trotterizations/__init__.py deleted file mode 100644 index a721bec2fee4..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/__init__.py +++ /dev/null @@ -1,24 +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. - -""" -Trotterization methods - Algorithms for -approximating Exponentials of Operator Sums. -""" - -from .trotterization_base import TrotterizationBase -from .trotterization_factory import TrotterizationFactory -from .trotter import Trotter -from .suzuki import Suzuki -from .qdrift import QDrift - -__all__ = ["TrotterizationBase", "TrotterizationFactory", "Trotter", "Suzuki", "QDrift"] diff --git a/qiskit/opflow/evolutions/trotterizations/qdrift.py b/qiskit/opflow/evolutions/trotterizations/qdrift.py deleted file mode 100644 index ca54d2592f4b..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/qdrift.py +++ /dev/null @@ -1,88 +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. - -""" -QDrift Class - -""" - -import warnings -from typing import List, Union, cast - -import numpy as np - -from qiskit.opflow.evolutions.trotterizations.trotterization_base import TrotterizationBase -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.utils import algorithm_globals -from qiskit.utils.deprecation import deprecate_func - -# pylint: disable=invalid-name - - -class QDrift(TrotterizationBase): - """Deprecated: The QDrift Trotterization method, which selects each each term in the - Trotterization randomly, with a probability proportional to its weight. Based on the work - of Earl Campbell in https://arxiv.org/abs/1811.08017. - """ - - @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: - r""" - Args: - reps: The number of times to repeat the Trotterization circuit. - """ - super().__init__(reps=reps) - - def convert(self, operator: OperatorBase) -> OperatorBase: - if not isinstance(operator, (SummedOp, PauliSumOp)): - raise TypeError("Trotterization converters can only convert SummedOp or PauliSumOp.") - - if not isinstance(operator.coeff, (float, int)): - raise TypeError( - "Trotterization converters can only convert operators with real coefficients." - ) - - operator_iter: Union[PauliSumOp, List[PrimitiveOp]] - - if isinstance(operator, PauliSumOp): - operator_iter = operator - coeffs = operator.primitive.coeffs - coeff = operator.coeff - else: - operator_iter = cast(List[PrimitiveOp], operator.oplist) - coeffs = [op.coeff for op in operator_iter] - coeff = operator.coeff - - # We artificially make the weights positive, TODO check approximation performance - weights = np.abs(coeffs) - lambd = np.sum(weights) - - N = 2 * (lambd**2) * (coeff**2) - factor = lambd * coeff / (N * self.reps) - # The protocol calls for the removal of the individual coefficients, - # and multiplication by a constant factor. - scaled_ops = [(op * (factor / op.coeff)).exp_i() for op in operator_iter] - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - sampled_ops = algorithm_globals.random.choice( - scaled_ops, size=(int(N * self.reps),), p=weights / lambd - ) - - return ComposedOp(sampled_ops).reduce() diff --git a/qiskit/opflow/evolutions/trotterizations/suzuki.py b/qiskit/opflow/evolutions/trotterizations/suzuki.py deleted file mode 100644 index 884034f12b35..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/suzuki.py +++ /dev/null @@ -1,122 +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. - -"""Suzuki Class""" - -from typing import List, Union, cast - -from numpy import isreal - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.evolutions.trotterizations.trotterization_base import TrotterizationBase -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.utils.deprecation import deprecate_func - - -class Suzuki(TrotterizationBase): - r""" - Deprecated: Suzuki Trotter expansion, composing the evolution circuits of each Operator in the sum - together by a recursive "bookends" strategy, repeating the whole composed circuit - ``reps`` times. - - Detailed in https://arxiv.org/pdf/quant-ph/0508139.pdf. - """ - - @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: - """ - Args: - reps: The number of times to repeat the expansion circuit. - order: The order of the expansion to perform. - - """ - super().__init__(reps=reps) - self._order = order - - @property - def order(self) -> int: - """returns order""" - return self._order - - @order.setter - def order(self, order: int) -> None: - """sets order""" - self._order = order - - def convert(self, operator: OperatorBase) -> OperatorBase: - if not isinstance(operator, (SummedOp, PauliSumOp)): - raise TypeError("Trotterization converters can only convert SummedOp or PauliSumOp.") - - if isinstance(operator.coeff, (float, ParameterExpression)): - coeff = operator.coeff - else: - if isreal(operator.coeff): - coeff = operator.coeff.real - else: - raise TypeError( - "Coefficient of the operator must be float or ParameterExpression, " - f"but {operator.coeff}:{type(operator.coeff)} is given." - ) - - if isinstance(operator, PauliSumOp): - comp_list = self._recursive_expansion(operator, coeff, self.order, self.reps) - if isinstance(operator, SummedOp): - comp_list = Suzuki._recursive_expansion(operator.oplist, coeff, self.order, self.reps) - - single_rep = ComposedOp(cast(List[OperatorBase], comp_list)) - full_evo = single_rep.power(self.reps) - return full_evo.reduce() - - @staticmethod - def _recursive_expansion( - op_list: Union[List[OperatorBase], PauliSumOp], - evo_time: Union[float, ParameterExpression], - expansion_order: int, - reps: int, - ) -> List[PrimitiveOp]: - """ - Compute the list of pauli terms for a single slice of the Suzuki expansion - following the paper https://arxiv.org/pdf/quant-ph/0508139.pdf. - - Args: - op_list: The slice's weighted Pauli list for the Suzuki expansion - evo_time: The parameter lambda as defined in said paper, - adjusted for the evolution time and the number of time slices - expansion_order: The order for the Suzuki expansion. - reps: The number of times to repeat the expansion circuit. - - Returns: - The evolution list after expansion. - """ - if expansion_order == 1: - # Base first-order Trotter case - return [(op * (evo_time / reps)).exp_i() for op in op_list] # type: ignore - if expansion_order == 2: - half = Suzuki._recursive_expansion(op_list, evo_time / 2, expansion_order - 1, reps) - return list(reversed(half)) + half - else: - p_k = (4 - 4 ** (1 / (2 * expansion_order - 1))) ** -1 - side = 2 * Suzuki._recursive_expansion( - op_list, evo_time * p_k, expansion_order - 2, reps - ) - middle = Suzuki._recursive_expansion( - op_list, evo_time * (1 - 4 * p_k), expansion_order - 2, reps - ) - return side + middle + side diff --git a/qiskit/opflow/evolutions/trotterizations/trotter.py b/qiskit/opflow/evolutions/trotterizations/trotter.py deleted file mode 100644 index 406598f845bb..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/trotter.py +++ /dev/null @@ -1,35 +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. - -"""Trotter Class""" - -from qiskit.opflow.evolutions.trotterizations.suzuki import Suzuki -from qiskit.utils.deprecation import deprecate_func - - -class Trotter(Suzuki): - r""" - Deprecated: Simple Trotter expansion, composing the evolution circuits of each Operator in the sum - together ``reps`` times and dividing the evolution time of each by ``reps``. - """ - - @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: - r""" - Args: - reps: The number of times to repeat the Trotterization circuit. - """ - super().__init__(order=1, reps=reps) diff --git a/qiskit/opflow/evolutions/trotterizations/trotterization_base.py b/qiskit/opflow/evolutions/trotterizations/trotterization_base.py deleted file mode 100644 index 9866209ecbab..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/trotterization_base.py +++ /dev/null @@ -1,68 +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. - -"""Trotterization Algorithm Base""" - -from abc import abstractmethod - -from qiskit.opflow.evolutions.evolution_base import EvolutionBase -from qiskit.opflow.operator_base import OperatorBase -from qiskit.utils.deprecation import deprecate_func - -# TODO centralize handling of commuting groups - - -class TrotterizationBase(EvolutionBase): - """Deprecated: A base for Trotterization methods, algorithms for approximating exponentiations of - operator sums by compositions of exponentiations. - """ - - @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: - super().__init__() - self._reps = reps - - @property - def reps(self) -> int: - """The number of repetitions to use in the Trotterization, improving the approximation - accuracy. - """ - return self._reps - - @reps.setter - def reps(self, reps: int) -> None: - r"""Set the number of repetitions to use in the Trotterization.""" - self._reps = reps - - @abstractmethod - def convert(self, operator: OperatorBase) -> OperatorBase: - r""" - Convert a ``SummedOp`` into a ``ComposedOp`` or ``CircuitOp`` representing an - approximation of e^-i*``op_sum``. - - Args: - operator: The ``SummedOp`` to evolve. - - Returns: - The Operator approximating op_sum's evolution. - - Raises: - TypeError: A non-SummedOps Operator is passed into ``convert``. - - """ - raise NotImplementedError - - # TODO @abstractmethod - trotter_error_bound diff --git a/qiskit/opflow/evolutions/trotterizations/trotterization_factory.py b/qiskit/opflow/evolutions/trotterizations/trotterization_factory.py deleted file mode 100644 index e82ccd3b94be..000000000000 --- a/qiskit/opflow/evolutions/trotterizations/trotterization_factory.py +++ /dev/null @@ -1,53 +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. - -"""TrotterizationFactory Class""" - -from qiskit.opflow.evolutions.trotterizations.qdrift import QDrift -from qiskit.opflow.evolutions.trotterizations.suzuki import Suzuki -from qiskit.opflow.evolutions.trotterizations.trotter import Trotter -from qiskit.opflow.evolutions.trotterizations.trotterization_base import TrotterizationBase -from qiskit.utils.deprecation import deprecate_func - - -class TrotterizationFactory: - """Deprecated: A factory for conveniently creating TrotterizationBase instances.""" - - @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: - """A factory for conveniently creating TrotterizationBase instances. - - Args: - mode: One of 'trotter', 'suzuki', 'qdrift' - reps: The number of times to repeat the Trotterization circuit. - - Returns: - The desired TrotterizationBase instance. - - Raises: - ValueError: A string not in ['trotter', 'suzuki', 'qdrift'] is given for mode. - """ - if mode == "trotter": - return Trotter(reps=reps) - - elif mode == "suzuki": - return Suzuki(reps=reps) - - elif mode == "qdrift": - return QDrift(reps=reps) - - raise ValueError(f"Trotter mode {mode} not supported") diff --git a/qiskit/opflow/exceptions.py b/qiskit/opflow/exceptions.py deleted file mode 100644 index de0b526cc79e..000000000000 --- a/qiskit/opflow/exceptions.py +++ /dev/null @@ -1,29 +0,0 @@ -# This code is part of Qiskit. -# -# (C) 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. - -"""Exception for errors raised by Opflow module.""" - -from qiskit.exceptions import QiskitError -from qiskit.utils.deprecation import deprecate_func - - -class OpflowError(QiskitError): - """Deprecated: For Opflow specific errors.""" - - @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): - """Set the error message.""" - super().__init__(*message) diff --git a/qiskit/opflow/expectations/__init__.py b/qiskit/opflow/expectations/__init__.py deleted file mode 100644 index 885d4b7e36f6..000000000000 --- a/qiskit/opflow/expectations/__init__.py +++ /dev/null @@ -1,80 +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. - -""" -Expectations (:mod:`qiskit.opflow.expectations`) -================================================ - -.. currentmodule:: qiskit.opflow.expectations - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Expectations are converters which enable the computation of the expectation -value of an Observable with respect to some state function. They traverse an Operator tree, -replacing :class:`~qiskit.opflow.state_fns.OperatorStateFn` measurements with equivalent -measurements which are more amenable to computation on quantum or classical hardware. -For example, if one would like to measure the -expectation value of an Operator ``o`` expressed as a sum of Paulis with respect to some state -function, but only has access to diagonal measurements on Quantum hardware, we can create a -measurement ~StateFn(o), use a :class:`PauliExpectation` to convert it to a diagonal measurement -and circuit pre-rotations to append to the state, and sample this circuit on Quantum hardware with -a :class:`~qiskit.opflow.converters.CircuitSampler`. All in all, this would be: -``my_sampler.convert(my_expect.convert(~StateFn(o)) @ my_state).eval()``. - - -Expectation Base Class ----------------------- - -The ExpectationBase class gives an interface for algorithms to ask for Expectations as -execution settings. For example, if an algorithm contains an expectation value step within it, -such as :class:`~qiskit.algorithms.VQE`, the algorithm can give the opportunity for the user -to pass an ExpectationBase of their choice to be used in that expectation value step. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - ExpectationBase - -Expectations ------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - ExpectationFactory - AerPauliExpectation - MatrixExpectation - PauliExpectation - CVaRExpectation -""" - -from .expectation_base import ExpectationBase -from .expectation_factory import ExpectationFactory -from .pauli_expectation import PauliExpectation -from .aer_pauli_expectation import AerPauliExpectation -from .matrix_expectation import MatrixExpectation -from .cvar_expectation import CVaRExpectation - -__all__ = [ - "ExpectationBase", - "ExpectationFactory", - "PauliExpectation", - "AerPauliExpectation", - "CVaRExpectation", - "MatrixExpectation", -] diff --git a/qiskit/opflow/expectations/aer_pauli_expectation.py b/qiskit/opflow/expectations/aer_pauli_expectation.py deleted file mode 100644 index 6eaa77d3d644..000000000000 --- a/qiskit/opflow/expectations/aer_pauli_expectation.py +++ /dev/null @@ -1,163 +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. - -"""AerPauliExpectation Class""" - -import logging -from functools import reduce -from operator import add -from typing import Union - -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.opflow.expectations.expectation_base import ExpectationBase -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.quantum_info import SparsePauliOp -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class AerPauliExpectation(ExpectationBase): - r"""An Expectation converter for using Aer's operator snapshot to - take expectations of quantum state circuits over Pauli observables. - - """ - - @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: - super().__init__() - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Accept an Operator and return a new Operator with the Pauli measurements replaced by - AerSnapshot-based expectation circuits. - - Args: - operator: The operator to convert. If it contains non-hermitian terms, the - operator is decomposed into hermitian and anti-hermitian parts. - - Returns: - The converted operator. - """ - - if isinstance(operator, OperatorStateFn) and operator.is_measurement: - if isinstance(operator.primitive, ListOp): - is_herm = all((op.is_hermitian() for op in operator.primitive.oplist)) - else: - is_herm = operator.primitive.is_hermitian() - - if not is_herm: - pauli_sum_re = ( - self._replace_pauli_sums( - 1 / 2 * (operator.primitive + operator.primitive.adjoint()).reduce() - ) - * operator.coeff - ) - pauli_sum_im = ( - self._replace_pauli_sums( - 1 / 2j * (operator.primitive - operator.primitive.adjoint()).reduce() - ) - * operator.coeff - ) - pauli_sum = (pauli_sum_re + 1j * pauli_sum_im).reduce() - else: - pauli_sum = self._replace_pauli_sums(operator.primitive) * operator.coeff - return pauli_sum - elif isinstance(operator, ListOp): - return operator.traverse(self.convert) - else: - return operator - - @classmethod - def _replace_pauli_sums(cls, operator): - try: - from qiskit.providers.aer.library import SaveExpectationValue - except ImportError as ex: - raise MissingOptionalLibraryError( - libname="qiskit-aer", - name="AerPauliExpectation", - pip_install="pip install qiskit-aer", - ) from ex - # The 'expval_measurement' label on the save instruction is special - the - # CircuitSampler will look for it to know that the circuit is a Expectation - # measurement, and not simply a - # circuit to replace with a DictStateFn - if operator.__class__ == ListOp: - return operator.traverse(cls._replace_pauli_sums) - - if isinstance(operator, PauliSumOp): - save_instruction = SaveExpectationValue(operator.primitive, "expval_measurement") - return CircuitStateFn( - save_instruction, coeff=operator.coeff, is_measurement=True, from_operator=True - ) - - # Change to Pauli representation if necessary - if {"Pauli"} != operator.primitive_strings(): - logger.warning( - "Measured Observable is not composed of only Paulis, converting to " - "Pauli representation, which can be expensive." - ) - # Setting massive=False because this conversion is implicit. User can perform this - # action on the Observable with massive=True explicitly if they so choose. - operator = operator.to_pauli_op(massive=False) - - if isinstance(operator, SummedOp): - sparse_pauli = reduce( - add, (meas.coeff * SparsePauliOp(meas.primitive) for meas in operator.oplist) - ) - save_instruction = SaveExpectationValue(sparse_pauli, "expval_measurement") - return CircuitStateFn( - save_instruction, coeff=operator.coeff, is_measurement=True, from_operator=True - ) - - if isinstance(operator, PauliOp): - sparse_pauli = operator.coeff * SparsePauliOp(operator.primitive) - save_instruction = SaveExpectationValue(sparse_pauli, "expval_measurement") - return CircuitStateFn(save_instruction, is_measurement=True, from_operator=True) - - raise TypeError( - f"Conversion of OperatorStateFn of {operator.__class__.__name__} is not defined." - ) - - def compute_variance(self, exp_op: OperatorBase) -> Union[list, float]: - r""" - Compute the variance of the expectation estimator. Because Aer takes this expectation - with matrix multiplication, the estimation is exact and the variance is always 0, - but we need to return those values in a way which matches the Operator's structure. - - Args: - exp_op: The full expectation value Operator after sampling. - - Returns: - The variances or lists thereof (if exp_op contains ListOps) of the expectation value - estimation, equal to 0. - """ - - # Need to do this to mimic Op structure - def sum_variance(operator): - if isinstance(operator, ComposedOp): - return 0.0 - elif isinstance(operator, ListOp): - return operator.combo_fn([sum_variance(op) for op in operator.oplist]) - raise TypeError(f"Variance cannot be computed for {operator.__class__.__name__}.") - - return sum_variance(exp_op) diff --git a/qiskit/opflow/expectations/cvar_expectation.py b/qiskit/opflow/expectations/cvar_expectation.py deleted file mode 100644 index 8de1a2897c87..000000000000 --- a/qiskit/opflow/expectations/cvar_expectation.py +++ /dev/null @@ -1,127 +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. - -"""The CVaR (Conditional Value at Risk) expectation class.""" - -from typing import Optional, Union - -from qiskit.opflow.expectations.aer_pauli_expectation import AerPauliExpectation -from qiskit.opflow.expectations.expectation_base import ExpectationBase -from qiskit.opflow.expectations.pauli_expectation import PauliExpectation -from qiskit.opflow.list_ops import ComposedOp, ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns import CVaRMeasurement, OperatorStateFn -from qiskit.utils.deprecation import deprecate_func - - -class CVaRExpectation(ExpectationBase): - r"""Deprecated: Compute the Conditional Value at Risk (CVaR) expectation value. - - The standard approach to calculating the expectation value of a Hamiltonian w.r.t. a - state is to take the sample mean of the measurement outcomes. This corresponds to an estimator - of the energy. However in several problem settings with a diagonal Hamiltonian, e.g. - in combinatorial optimization where the Hamiltonian encodes a cost function, we are not - interested in calculating the energy but in the lowest possible value we can find. - - To this end, we might consider using the best observed sample as a cost function during - variational optimization. The issue here, is that this can result in a non-smooth optimization - surface. To resolve this issue, we can smooth the optimization surface by using not just the - best observed sample, but instead average over some fraction of best observed samples. - This is exactly what the CVaR estimator accomplishes [1]. - - It is empirically shown, that this can lead to faster convergence for combinatorial - optimization problems. - - Let :math:`\alpha` be a real number in :math:`[0,1]` which specifies the fraction of best - observed samples which are used to compute the objective function. Observe that if - :math:`\alpha = 1`, CVaR is equivalent to a standard expectation value. Similarly, - if :math:`\alpha = 0`, then CVaR corresponds to using the best observed sample. - Intermediate values of :math:`\alpha` interpolate between these two objective functions. - - References: - - [1]: Barkoutsos, P. K., Nannicini, G., Robert, A., Tavernelli, I., and Woerner, S., - "Improving Variational Quantum Optimization using CVaR" - `arXiv:1907.04769 `_ - - """ - - @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: - """ - Args: - alpha: The alpha value describing the quantile considered in the expectation value. - expectation: An expectation object to compute the expectation value. Defaults - to the PauliExpectation calculation. - - Raises: - NotImplementedError: If the ``expectation`` is an AerPauliExpecation. - """ - super().__init__() - self.alpha = alpha - if isinstance(expectation, AerPauliExpectation): - raise NotImplementedError("AerPauliExpecation currently not supported.") - if expectation is None: - expectation = PauliExpectation() - self.expectation = expectation - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Return an expression that computes the CVaR expectation upon calling ``eval``. - Args: - operator: The operator to convert. - - Returns: - The converted operator. - """ - expectation = self.expectation.convert(operator) - - # replace OperatorMeasurements by CVaRMeasurement - def replace_with_cvar(operator): - if isinstance(operator, OperatorStateFn) and operator.is_measurement: - return CVaRMeasurement(operator.primitive, alpha=self.alpha) - elif isinstance(operator, ListOp): - return operator.traverse(replace_with_cvar) - return operator - - return replace_with_cvar(expectation) - - def compute_variance(self, exp_op: OperatorBase) -> Union[list, float]: - """Returns the variance of the CVaR calculation - - Args: - exp_op: The operator whose evaluation yields an expectation - of some StateFn against a diagonal observable. - - Returns: - The variance of the CVaR estimate corresponding to the converted - exp_op. - Raises: - ValueError: If the exp_op does not correspond to an expectation value. - """ - - def cvar_variance(operator): - if isinstance(operator, ComposedOp): - sfdict = operator.oplist[1] - measurement = operator.oplist[0] - return measurement.eval_variance(sfdict) - - elif isinstance(operator, ListOp): - return operator.combo_fn([cvar_variance(op) for op in operator.oplist]) - - raise ValueError("Input operator does not correspond to a value expectation value.") - - cvar_op = self.convert(exp_op) - return cvar_variance(cvar_op) diff --git a/qiskit/opflow/expectations/expectation_base.py b/qiskit/opflow/expectations/expectation_base.py deleted file mode 100644 index 330e739f1439..000000000000 --- a/qiskit/opflow/expectations/expectation_base.py +++ /dev/null @@ -1,73 +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. - -"""ExpectationBase Class""" - -from abc import abstractmethod -from typing import Union - -import numpy as np - -from qiskit.opflow.converters import ConverterBase -from qiskit.opflow.operator_base import OperatorBase -from qiskit.utils.deprecation import deprecate_func - - -class ExpectationBase(ConverterBase): - r""" - Deprecated: A base for Expectation value converters. Expectations are converters which enable the - computation of the expectation value of an Observable with respect to some state function. - They traverse an Operator tree, replacing OperatorStateFn measurements with equivalent - measurements which are more amenable to computation on quantum or classical hardware. For - example, if one would like to measure the expectation value of an Operator ``o`` expressed - as a sum of Paulis with respect to some state function, but only has access to diagonal - measurements on Quantum hardware, we can create a measurement ~StateFn(o), - use a ``PauliExpectation`` to convert it to a diagonal measurement and circuit - pre-rotations to a append to the state, and sample this circuit on Quantum hardware with - a CircuitSampler. All in all, this would be: - ``my_sampler.convert(my_expect.convert(~StateFn(o)) @ my_state).eval()``. - - """ - - @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: - super().__init__() - - @abstractmethod - def convert(self, operator: OperatorBase) -> OperatorBase: - """Accept an Operator and return a new Operator with the measurements replaced by - alternate methods to compute the expectation value. - - Args: - operator: The operator to convert. - - Returns: - The converted operator. - """ - raise NotImplementedError - - @abstractmethod - def compute_variance(self, exp_op: OperatorBase) -> Union[list, complex, np.ndarray]: - """Compute the variance of the expectation estimator. - - Args: - exp_op: The full expectation value Operator after sampling. - - Returns: - The variances or lists thereof (if exp_op contains ListOps) of the expectation value - estimation. - """ - raise NotImplementedError diff --git a/qiskit/opflow/expectations/expectation_factory.py b/qiskit/opflow/expectations/expectation_factory.py deleted file mode 100644 index 68bf55143b63..000000000000 --- a/qiskit/opflow/expectations/expectation_factory.py +++ /dev/null @@ -1,127 +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. - -"""ExpectationFactory Class""" - -import logging -from typing import Optional, Union - -from qiskit import BasicAer -from qiskit.opflow.expectations.aer_pauli_expectation import AerPauliExpectation -from qiskit.opflow.expectations.expectation_base import ExpectationBase -from qiskit.opflow.expectations.matrix_expectation import MatrixExpectation -from qiskit.opflow.expectations.pauli_expectation import PauliExpectation -from qiskit.opflow.operator_base import OperatorBase -from qiskit.providers import Backend -from qiskit.utils.backend_utils import is_aer_qasm, is_statevector_backend -from qiskit.utils import QuantumInstance, optionals -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class ExpectationFactory: - - """Deprecated: factory class for convenient automatic selection of an Expectation based on the - Operator to be converted and backend used to sample the expectation value. - """ - - @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, - backend: Optional[Union[Backend, QuantumInstance]] = None, - include_custom: bool = True, - ) -> ExpectationBase: - """ - A factory method for convenient automatic selection of an Expectation based on the - Operator to be converted and backend used to sample the expectation value. - - Args: - operator: The Operator whose expectation value will be taken. - backend: The backend which will be used to sample the expectation value. - include_custom: Whether the factory will include the (Aer) specific custom - expectations if their behavior against the backend might not be as expected. - For instance when using Aer qasm_simulator with paulis the Aer snapshot can - be used but the outcome lacks shot noise and hence does not intuitively behave - overall as people might expect when choosing a qasm_simulator. It is however - fast as long as the more state vector like behavior is acceptable. - - Returns: - The expectation algorithm which best fits the Operator and backend. - - Raises: - ValueError: If operator is not of a composition for which we know the best Expectation - method. - """ - backend_to_check = backend.backend if isinstance(backend, QuantumInstance) else backend - - # pylint: disable=cyclic-import - primitives = operator.primitive_strings() - if primitives in ({"Pauli"}, {"SparsePauliOp"}): - - if backend_to_check is None: - # If user has Aer but didn't specify a backend, use the Aer fast expectation - if optionals.HAS_AER: - from qiskit_aer import AerSimulator - - backend_to_check = AerSimulator() - # If user doesn't have Aer, use statevector_simulator - # for < 16 qubits, and qasm with warning for more. - else: - if operator.num_qubits <= 16: - backend_to_check = BasicAer.get_backend("statevector_simulator") - else: - logger.warning( - "%d qubits is a very large expectation value. " - "Consider installing Aer to use " - "Aer's fast expectation, which will perform better here. We'll use " - "the BasicAer qasm backend for this expectation to avoid having to " - "construct the %dx%d operator matrix.", - operator.num_qubits, - 2**operator.num_qubits, - 2**operator.num_qubits, - ) - backend_to_check = BasicAer.get_backend("qasm_simulator") - - # If the user specified Aer qasm backend and is using a - # Pauli operator, use the Aer fast expectation if we are including such - # custom behaviors. - if is_aer_qasm(backend_to_check) and include_custom: - return AerPauliExpectation() - - # If the user specified a statevector backend (either Aer or BasicAer), - # use a converter to produce a - # Matrix operator and compute using matmul - elif is_statevector_backend(backend_to_check): - if operator.num_qubits >= 16: - logger.warning( - "Note: Using a statevector_simulator with %d qubits can be very expensive. " - "Consider using the Aer qasm_simulator instead to take advantage of Aer's " - "built-in fast Pauli Expectation", - operator.num_qubits, - ) - return MatrixExpectation() - - # All other backends, including IBMQ, BasicAer QASM, go here. - else: - return PauliExpectation() - - elif primitives == {"Matrix"}: - return MatrixExpectation() - - else: - raise ValueError("Expectations of Mixed Operators not yet supported.") diff --git a/qiskit/opflow/expectations/matrix_expectation.py b/qiskit/opflow/expectations/matrix_expectation.py deleted file mode 100644 index 53c1c8912152..000000000000 --- a/qiskit/opflow/expectations/matrix_expectation.py +++ /dev/null @@ -1,77 +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. - -"""MatrixExpectation Class""" - -from typing import Union - -from qiskit.opflow.expectations.expectation_base import ExpectationBase -from qiskit.opflow.list_ops import ComposedOp, ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.utils.deprecation import deprecate_func - - -class MatrixExpectation(ExpectationBase): - """An Expectation converter which converts Operator measurements to - be matrix-based so they can be evaluated by matrix multiplication.""" - - @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: - super().__init__() - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Accept an Operator and return a new Operator with the Pauli measurements replaced by - Matrix based measurements. - - Args: - operator: The operator to convert. - - Returns: - The converted operator. - """ - if isinstance(operator, OperatorStateFn) and operator.is_measurement: - return operator.to_matrix_op() - elif isinstance(operator, ListOp): - return operator.traverse(self.convert) - else: - return operator - - def compute_variance(self, exp_op: OperatorBase) -> Union[list, float]: - r""" - Compute the variance of the expectation estimator. Because this expectation - works by matrix multiplication, the estimation is exact and the variance is - always 0, but we need to return those values in a way which matches the Operator's - structure. - - Args: - exp_op: The full expectation value Operator. - - Returns: - The variances or lists thereof (if exp_op contains ListOps) of the expectation value - estimation, equal to 0. - """ - - # Need to do this to mimic Op structure - def sum_variance(operator): - if isinstance(operator, ComposedOp): - return 0.0 - elif isinstance(operator, ListOp): - return operator.combo_fn([sum_variance(op) for op in operator.oplist]) - else: - return 0.0 - - return sum_variance(exp_op) diff --git a/qiskit/opflow/expectations/pauli_expectation.py b/qiskit/opflow/expectations/pauli_expectation.py deleted file mode 100644 index bc297afccce6..000000000000 --- a/qiskit/opflow/expectations/pauli_expectation.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. - -"""PauliExpectation Class""" - -import logging -from typing import Union - -import numpy as np - -from qiskit.opflow.converters.abelian_grouper import AbelianGrouper -from qiskit.opflow.converters.pauli_basis_change import PauliBasisChange -from qiskit.opflow.expectations.expectation_base import ExpectationBase -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class PauliExpectation(ExpectationBase): - r""" - An Expectation converter for Pauli-basis observables by changing Pauli measurements to a - diagonal ({Z, I}^n) basis and appending circuit post-rotations to the measured state function. - Optionally groups the Paulis with the same post-rotations (those that commute with one - another, or form Abelian groups) into single measurements to reduce circuit execution - overhead. - - """ - - @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: - """ - Args: - group_paulis: Whether to group the Pauli measurements into commuting sums, which all - have the same diagonalizing circuit. - - """ - super().__init__() - self._grouper = AbelianGrouper() if group_paulis else None - - def convert(self, operator: OperatorBase) -> OperatorBase: - """Accepts an Operator and returns a new Operator with the Pauli measurements replaced by - diagonal Pauli post-rotation based measurements so they can be evaluated by sampling and - averaging. - - Args: - operator: The operator to convert. - - Returns: - The converted operator. - """ - if isinstance(operator, ListOp): - return operator.traverse(self.convert).reduce() - - if isinstance(operator, OperatorStateFn) and operator.is_measurement: - # Change to Pauli representation if necessary - if ( - isinstance(operator.primitive, (ListOp, PrimitiveOp)) - and not isinstance(operator.primitive, PauliSumOp) - and {"Pauli", "SparsePauliOp"} < operator.primitive_strings() - ): - logger.warning( - "Measured Observable is not composed of only Paulis, converting to " - "Pauli representation, which can be expensive." - ) - # Setting massive=False because this conversion is implicit. User can perform this - # action on the Observable with massive=True explicitly if they so choose. - pauli_obsv = operator.primitive.to_pauli_op(massive=False) - operator = StateFn(pauli_obsv, is_measurement=True, coeff=operator.coeff) - - if self._grouper and isinstance(operator.primitive, (ListOp, PauliSumOp)): - grouped = self._grouper.convert(operator.primitive) - operator = StateFn(grouped, is_measurement=True, coeff=operator.coeff) - - # Convert the measurement into diagonal basis (PauliBasisChange chooses - # this basis by default). - cob = PauliBasisChange(replacement_fn=PauliBasisChange.measurement_replacement_fn) - return cob.convert(operator).reduce() - - return operator - - def compute_variance(self, exp_op: OperatorBase) -> Union[list, float, np.ndarray]: - def sum_variance(operator): - if isinstance(operator, ComposedOp): - sfdict = operator.oplist[1] - measurement = operator.oplist[0] - average = np.asarray(measurement.eval(sfdict)) - variance = sum( - (v * (np.asarray(measurement.eval(b)) - average)) ** 2 - for (b, v) in sfdict.primitive.items() - ) - return operator.coeff * variance - - elif isinstance(operator, ListOp): - return operator.combo_fn([sum_variance(op) for op in operator.oplist]) - - return 0.0 - - return sum_variance(exp_op) diff --git a/qiskit/opflow/gradients/__init__.py b/qiskit/opflow/gradients/__init__.py deleted file mode 100644 index 9c2625d078ca..000000000000 --- a/qiskit/opflow/gradients/__init__.py +++ /dev/null @@ -1,194 +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. - -r""" -Gradients (:mod:`qiskit.opflow.gradients`) -========================================== - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Given an operator that represents either a quantum state resp. an expectation value, -the gradient framework enables the evaluation of gradients, natural gradients, -Hessians, as well as the Quantum Fisher Information. - -Suppose a parameterized quantum state `|ψ(θ)〉 = V(θ)|ψ〉` with input state `|ψ〉` and parameterized -Ansatz `V(θ)`, and an Operator `O(ω)`. - - -**Gradients** - -We want to compute one of: -* :math:`d⟨ψ(θ)|O(ω)|ψ(θ)〉/ dω` -* :math:`d⟨ψ(θ)|O(ω)|ψ(θ)〉/ dθ` -* :math:`d⟨ψ(θ)|i〉⟨i|ψ(θ)〉/ dθ` - -The last case corresponds to the gradient w.r.t. the sampling probabilities of `|ψ(θ)`. -These gradients can be computed with different methods, i.e. a parameter shift, a linear combination -of unitaries and a finite difference method. - -**Examples** - -.. code-block:: - - x = Parameter('x') - ham = x * X - a = Parameter('a') - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.p(params[0], q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.) - - value_dict = {x: 0.1, a: np.pi / 4} - - ham_grad = Gradient(grad_method='param_shift').convert(operator=op, params=[x]) - ham_grad.assign_parameters(value_dict).eval() - - state_grad = Gradient(grad_method='lin_comb').convert(operator=op, params=[a]) - state_grad.assign_parameters(value_dict).eval() - - prob_grad = Gradient(grad_method='fin_diff').convert( - operator=CircuitStateFn(primitive=qc, coeff=1.), params=[a] - ) - prob_grad.assign_parameters(value_dict).eval() - -**Hessians** - -We want to compute one of: -* :math:`d^2⟨ψ(θ)|O(ω)|ψ(θ)〉/ dω^2` -* :math:`d^2⟨ψ(θ)|O(ω)|ψ(θ)〉/ dθ^2` -* :math:`d^2⟨ψ(θ)|O(ω)|ψ(θ)〉/ dθ dω` -* :math:`d^2⟨ψ(θ)|i〉⟨i|ψ(θ)〉/ dθ^2` - -The last case corresponds to the Hessian w.r.t. the sampling probabilities of `|ψ(θ)〉`. -Just as the first order gradients, the Hessians can be evaluated with different methods, i.e. a -parameter shift, a linear combination of unitaries and a finite difference method. -Given a tuple of parameters ``Hessian().convert(op, param_tuple)`` returns the value for the second -order derivative. -If a list of parameters is given ``Hessian().convert(op, param_list)`` returns the full Hessian for -all the given parameters according to the given parameter order. - -**QFI** - -The Quantum Fisher Information `QFI` is a metric tensor which is representative for the -representation capacity of a parameterized quantum state `|ψ(θ)〉 = V(θ)|ψ〉` generated by an -input state `|ψ〉` and a parameterized Ansatz `V(θ)`. -The entries of the `QFI` for a pure state read -:math:`\mathrm{QFI}_{kl} = 4 \mathrm{Re}[〈∂kψ|∂lψ〉−〈∂kψ|ψ〉〈ψ|∂lψ〉]`. - -Just as for the previous derivative types, the QFI can be computed using different methods: a full -representation based on a linear combination of unitaries implementation, a block-diagonal and a -diagonal representation based on an overlap method. - -**Examples** - -.. code-block:: - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.p(params[0], q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.) - - value_dict = {x: 0.1, a: np.pi / 4} - - qfi = QFI('lin_comb_full').convert( - operator=CircuitStateFn(primitive=qc, coeff=1.), params=[a] - ) - qfi.assign_parameters(value_dict).eval() - -**NaturalGradients** - -The natural gradient is a special gradient method which re-scales a gradient w.r.t. a state -parameter with the inverse of the corresponding Quantum Fisher Information (QFI) -:math:`\mathrm{QFI}^{-1} d⟨ψ(θ)|O(ω)|ψ(θ)〉/ dθ`. -Hereby, we can choose a gradient as well as a QFI method and a regularization method which is used -together with a least square solver instead of exact inversion of the QFI: - -**Examples** - -.. code-block:: - - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.) - nat_grad = NaturalGradient(grad_method='lin_comb, - qfi_method='lin_comb_full', - regularization='ridge').convert(operator=op, params=params) - -The derivative classes come with a `gradient_wrapper()` function which returns the corresponding -callable and are thus compatible with the optimizers. - -.. currentmodule:: qiskit.opflow.gradients - -Base Classes ------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - DerivativeBase - GradientBase - HessianBase - QFIBase - -Converters ----------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - CircuitGradient - CircuitQFI - -Derivatives ------------ - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - Gradient - Hessian - NaturalGradient - QFI - -""" - -from .circuit_gradients.circuit_gradient import CircuitGradient -from .circuit_qfis.circuit_qfi import CircuitQFI -from .derivative_base import DerivativeBase -from .gradient_base import GradientBase -from .gradient import Gradient -from .natural_gradient import NaturalGradient -from .hessian_base import HessianBase -from .hessian import Hessian -from .qfi_base import QFIBase -from .qfi import QFI - -__all__ = [ - "DerivativeBase", - "CircuitGradient", - "GradientBase", - "Gradient", - "NaturalGradient", - "HessianBase", - "Hessian", - "QFIBase", - "QFI", - "CircuitQFI", -] diff --git a/qiskit/opflow/gradients/circuit_gradients/__init__.py b/qiskit/opflow/gradients/circuit_gradients/__init__.py deleted file mode 100644 index 16953b57ff21..000000000000 --- a/qiskit/opflow/gradients/circuit_gradients/__init__.py +++ /dev/null @@ -1,20 +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 module for first order derivatives. -""" -from .circuit_gradient import CircuitGradient -from .lin_comb import LinComb -from .param_shift import ParamShift - -__all__ = ["CircuitGradient", "LinComb", "ParamShift"] diff --git a/qiskit/opflow/gradients/circuit_gradients/circuit_gradient.py b/qiskit/opflow/gradients/circuit_gradients/circuit_gradient.py deleted file mode 100644 index 52821d7ef1f9..000000000000 --- a/qiskit/opflow/gradients/circuit_gradients/circuit_gradient.py +++ /dev/null @@ -1,109 +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. - -"""CircuitGradient Class""" - -from abc import abstractmethod -from typing import List, Union, Optional, Tuple, Set - -from qiskit import QuantumCircuit, QiskitError, transpile -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.utils.deprecation import deprecate_func -from ...converters.converter_base import ConverterBase -from ...operator_base import OperatorBase - - -class CircuitGradient(ConverterBase): - r"""Deprecated: Circuit to gradient operator converter. - - Converter for changing parameterized circuits into operators - whose evaluation yields the gradient with respect to the circuit parameters. - - This is distinct from DerivativeBase converters which take gradients of composite - operators and handle things like differentiating combo_fn's and enforcing product rules - when operator coefficients are parameterized. - - CircuitGradient - uses quantum techniques to get derivatives of circuits - DerivativeBase - uses classical techniques to differentiate operator flow data structures - """ - - @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: - super().__init__() - - # pylint: disable=arguments-differ - @abstractmethod - def convert( - self, - operator: OperatorBase, - params: Optional[ - Union[ - ParameterExpression, - ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ] - ] = None, - ) -> OperatorBase: - r""" - Args: - operator: The operator we are taking the gradient of - params: The parameters we are taking the gradient wrt: ω - If a ParameterExpression, ParameterVector or List[ParameterExpression] is given, - then the 1st order derivative of the operator is calculated. - If a Tuple[ParameterExpression, ParameterExpression] or - List[Tuple[ParameterExpression, ParameterExpression]] - is given, then the 2nd order derivative of the operator is calculated. - - Returns: - An operator whose evaluation yields the Gradient. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - """ - raise NotImplementedError - - @staticmethod - def _transpile_to_supported_operations( - circuit: QuantumCircuit, supported_gates: Set[str] - ) -> QuantumCircuit: - """Transpile the given circuit into a gate set for which the gradients may be computed. - - Args: - circuit: Quantum circuit to be transpiled into supported operations. - supported_gates: Set of quantum operations supported by a gradient method intended to - be used on the quantum circuit. - - Returns: - Quantum circuit which is transpiled into supported operations. - - Raises: - QiskitError: when circuit transpiling fails. - - """ - unique_ops = set(circuit.count_ops()) - if not unique_ops.issubset(supported_gates): - try: - circuit = transpile( - circuit, basis_gates=list(supported_gates), optimization_level=0 - ) - except Exception as exc: - raise QiskitError( - f"Could not transpile the circuit provided {circuit} into supported gates " - f"{supported_gates}." - ) from exc - return circuit diff --git a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py b/qiskit/opflow/gradients/circuit_gradients/lin_comb.py deleted file mode 100644 index 2aee4c0bdb9c..000000000000 --- a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py +++ /dev/null @@ -1,911 +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. - -"""The module to compute the state gradient with the linear combination method.""" - -from collections.abc import Iterable -from copy import deepcopy -from functools import partial -from itertools import product -from typing import List, Optional, Tuple, Union, Callable - -import scipy -import numpy as np - -from qiskit.circuit import Gate, Instruction -from qiskit.circuit import ( - CircuitInstruction, - QuantumCircuit, - QuantumRegister, - ParameterVector, - ParameterExpression, - Parameter, -) -from qiskit.circuit.parametertable import ParameterReferences, ParameterTable -from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.library import SGate, SdgGate, XGate -from qiskit.circuit.library.standard_gates import ( - CXGate, - CYGate, - CZGate, - IGate, - RXGate, - RXXGate, - RYGate, - RYYGate, - RZGate, - RZXGate, - RZZGate, - PhaseGate, - UGate, - ZGate, -) -from qiskit.quantum_info import partial_trace -from qiskit.utils.deprecation import deprecate_func -from ...operator_base import OperatorBase -from ...list_ops.list_op import ListOp -from ...list_ops.composed_op import ComposedOp -from ...list_ops.summed_op import SummedOp -from ...operator_globals import Z, I, Y, One, Zero -from ...primitive_ops.primitive_op import PrimitiveOp -from ...state_fns.state_fn import StateFn -from ...state_fns.circuit_state_fn import CircuitStateFn -from ...state_fns.dict_state_fn import DictStateFn -from ...state_fns.vector_state_fn import VectorStateFn -from ...state_fns.sparse_vector_state_fn import SparseVectorStateFn -from ...exceptions import OpflowError -from .circuit_gradient import CircuitGradient -from ...converters import PauliBasisChange - - -class LinComb(CircuitGradient): - """Deprecated: Compute the state gradient d⟨ψ(ω)|O(θ)|ψ(ω)〉/ dω respectively the gradients of the - sampling probabilities of the basis states of - a state |ψ(ω)〉w.r.t. ω. - This method employs a linear combination of unitaries, - see e.g. https://arxiv.org/pdf/1811.11184.pdf - """ - - SUPPORTED_GATES = { - "rx", - "ry", - "rz", - "rzx", - "rzz", - "ryy", - "rxx", - "p", - "u", - "controlledgate", - "cx", - "cy", - "cz", - "ccx", - "swap", - "iswap", - "t", - "s", - "sdg", - "x", - "y", - "z", - } - - # 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): - """ - Args: - aux_meas_op: The operator that the auxiliary qubit is measured with respect to. - For ``aux_meas_op = Z`` we compute 2Re[(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉], - for ``aux_meas_op = -Y`` we compute 2Im[(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉], and - for ``aux_meas_op = Z - 1j * Y`` we compute 2(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉. - Raises: - ValueError: If the provided auxiliary measurement operator is not supported. - """ - super().__init__() - if aux_meas_op not in [Z, -Y, (Z - 1j * Y)]: - raise ValueError( - "This auxiliary measurement operator is currently not supported. Please choose " - "either Z, -Y, or Z - 1j * Y. " - ) - self._aux_meas_op = aux_meas_op - - def convert( - self, - operator: OperatorBase, - params: Union[ - ParameterExpression, - ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ], - ) -> OperatorBase: - """Convert ``operator`` into an operator that represents the gradient w.r.t. ``params``. - - Args: - operator: The operator we are taking the gradient of: ⟨ψ(ω)|O(θ)|ψ(ω)〉 - params: The parameters we are taking the gradient wrt: ω - If a ParameterExpression, ParameterVector or List[ParameterExpression] is given, - then the 1st order derivative of the operator is calculated. - If a Tuple[ParameterExpression, ParameterExpression] or - List[Tuple[ParameterExpression, ParameterExpression]] - is given, then the 2nd order derivative of the operator is calculated. - Returns: - An operator corresponding to the gradient resp. Hessian. The order is in accordance with - the order of the given parameters. - """ - return self._prepare_operator(operator, params) - - # pylint: disable=too-many-return-statements - def _prepare_operator( - self, - operator: OperatorBase, - params: Union[ - ParameterExpression, - ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ], - ) -> OperatorBase: - """Traverse ``operator`` to get back the adapted operator representing the gradient. - - Args: - operator: The operator we are taking the gradient of: ⟨ψ(ω)|O(θ)|ψ(ω)〉. - params: The parameters we are taking the gradient wrt: ω. - If a ``ParameterExpression```, ``ParameterVector`` or ``List[ParameterExpression]`` - is given, then the 1st order derivative of the operator is calculated. - If a ``Tuple[ParameterExpression, ParameterExpression]`` or - ``List[Tuple[ParameterExpression, ParameterExpression]]`` - is given, then the 2nd order derivative of the operator is calculated. - Returns: - The adapted operator. - Measurement operators are attached with an additional Z term acting - on an additional working qubit. - Quantum states - which must be given as circuits - are adapted. An additional - working qubit controls intercepting gates. - See e.g. [1]. - - Raises: - ValueError: If ``operator`` does not correspond to an expectation value. - TypeError: If the ``StateFn`` corresponding to the quantum state could not be extracted - from ``operator``. - OpflowError: If third or higher order gradients are requested. - - References: - [1]: Evaluating analytic gradients on quantum hardware - Maria Schuld, Ville Bergholm, Christian Gogolin, Josh Izaac, and Nathan Killoran - Phys. Rev. A 99, 032331 – Published 21 March 2019 - - """ - - if isinstance(operator, ComposedOp): - # Get the measurement and the state operator - if not isinstance(operator[0], StateFn) or not operator[0].is_measurement: - raise ValueError("The given operator does not correspond to an expectation value") - if not isinstance(operator[-1], StateFn) or operator[-1].is_measurement: - raise ValueError("The given operator does not correspond to an expectation value") - if operator[0].is_measurement: - meas = deepcopy(operator.oplist[0]) - meas = meas.primitive * meas.coeff - if len(operator.oplist) == 2: - state_op = operator[1] - if not isinstance(state_op, StateFn): - raise TypeError( - "The StateFn representing the quantum state could not be extracted." - ) - if isinstance(params, (ParameterExpression, ParameterVector)) or ( - isinstance(params, list) - and all(isinstance(param, ParameterExpression) for param in params) - ): - - return self._gradient_states( - state_op, - meas_op=(2 * meas), - target_params=params, - ) - elif isinstance(params, tuple) or ( - isinstance(params, list) - and all(isinstance(param, tuple) for param in params) - ): - return self._hessian_states( - state_op, - meas_op=(4 * (I ^ meas)), - target_params=params, - ) # type: ignore - else: - raise OpflowError( - "The linear combination gradient does only support the " - "computation of 1st gradients and 2nd order gradients." - ) - else: - state_op = deepcopy(operator) - state_op.oplist.pop(0) - if not isinstance(state_op, StateFn): - raise TypeError( - "The StateFn representing the quantum state could not be extracted." - ) - - if isinstance(params, (ParameterExpression, ParameterVector)) or ( - isinstance(params, list) - and all(isinstance(param, ParameterExpression) for param in params) - ): - return state_op.traverse( - partial( - self._gradient_states, - meas_op=(2 * meas), - target_params=params, - ) - ) - elif isinstance(params, tuple) or ( - isinstance(params, list) - and all(isinstance(param, tuple) for param in params) - ): - return state_op.traverse( - partial( - self._hessian_states, - meas_op=(4 * I ^ meas), - target_params=params, - ) - ) - - raise OpflowError( - "The linear combination gradient only supports the " - "computation of 1st and 2nd order gradients." - ) - else: - return operator.traverse(partial(self._prepare_operator, params=params)) - elif isinstance(operator, ListOp): - return operator.traverse(partial(self._prepare_operator, params=params)) - elif isinstance(operator, StateFn): - if operator.is_measurement: - return operator.traverse(partial(self._prepare_operator, params=params)) - else: - if isinstance(params, (ParameterExpression, ParameterVector)) or ( - isinstance(params, list) - and all(isinstance(param, ParameterExpression) for param in params) - ): - return self._gradient_states(operator, target_params=params) - elif isinstance(params, tuple) or ( - isinstance(params, list) and all(isinstance(param, tuple) for param in params) - ): - return self._hessian_states(operator, target_params=params) # type: ignore - else: - raise OpflowError( - "The linear combination gradient does only support the computation " - "of 1st gradients and 2nd order gradients." - ) - elif isinstance(operator, PrimitiveOp): - return operator - return operator - - @staticmethod - def _grad_combo_fn(x, state_op): - def get_result(item): - if isinstance(item, (DictStateFn, SparseVectorStateFn)): - item = item.primitive - if isinstance(item, VectorStateFn): - item = item.primitive.data - - if isinstance(item, dict): - prob_dict = {} - for key, val in item.items(): - prob_counts = val * np.conj(val) - if int(key[0]) == 1: - prob_counts *= -1 - suffix = key[1:] - prob_dict[suffix] = prob_dict.get(suffix, 0) + prob_counts - for key in prob_dict: - prob_dict[key] *= 2 - return prob_dict - elif isinstance(item, scipy.sparse.spmatrix): - # Generate the operator which computes the linear combination - trace = _z_exp(item) - return trace - elif isinstance(item, Iterable): - # Generate the operator which computes the linear combination - lin_comb_op = 2 * Z ^ (I ^ state_op.num_qubits) - lin_comb_op = lin_comb_op.to_matrix() - outer = np.outer(item, item.conj()) - return list( - np.diag(partial_trace(lin_comb_op.dot(outer), [state_op.num_qubits]).data) - ) - else: - raise TypeError( - "The state result should be either a DictStateFn or a VectorStateFn." - ) - - if not isinstance(x, Iterable): - return get_result(x) - elif len(x) == 1: - return get_result(x[0]) - else: - result = [] - for item in x: - result.append(get_result(item)) - return result - - @staticmethod - def _hess_combo_fn(x, state_op): - def get_result(item): - if isinstance(item, DictStateFn): - item = item.primitive - if isinstance(item, VectorStateFn): - item = item.primitive.data - if isinstance(item, Iterable): - # Generate the operator which computes the linear combination - lin_comb_op = 4 * (I ^ (state_op.num_qubits + 1)) ^ Z - lin_comb_op = lin_comb_op.to_matrix() - return list( - np.diag( - partial_trace(lin_comb_op.dot(np.outer(item, np.conj(item))), [0, 1]).data - ) - ) - elif isinstance(item, scipy.sparse.spmatrix): - # Generate the operator which computes the linear combination - trace = _z_exp(item) - return trace - elif isinstance(item, dict): - prob_dict = {} - for key, val in item.values(): - prob_counts = val * np.conj(val) - if int(key[-1]) == 1: - prob_counts *= -1 - prefix = key[:-2] - prob_dict[prefix] = prob_dict.get(prefix, 0) + prob_counts - for key in prob_dict: - prob_dict[key] *= 4 - return prob_dict - else: - raise TypeError( - "The state result should be either a DictStateFn or a VectorStateFn." - ) - - if not isinstance(x, Iterable): - return get_result(x) - elif len(x) == 1: - return get_result(x[0]) - else: - result = [] - for item in x: - result.append(get_result(item)) - return result - - @staticmethod - def _gate_gradient_dict(gate: Gate) -> List[Tuple[List[complex], List[Instruction]]]: - r"""Given a parameterized gate U(theta) with derivative - dU(theta)/dtheta = sum_ia_iU(theta)V_i. - This function returns a:=[a_0, ...] and V=[V_0, ...] - Suppose U takes multiple parameters, i.e., U(theta^0, ... theta^k). - The returned coefficients and gates are ordered accordingly. - Only parameterized Qiskit gates are supported. - - Args: - gate: The gate for which the derivative is being computed. - - Returns: - The coefficients and the gates used for the metric computation for each parameter of - the respective gates ``[([a^0], [V^0]) ..., ([a^k], [V^k])]``. - - Raises: - OpflowError: If the input gate is controlled by another state but '|1>^{\otimes k}' - TypeError: If the input gate is not a supported parameterized gate. - """ - - # pylint: disable=too-many-return-statements - if isinstance(gate, PhaseGate): - # theta - return [([0.5j, -0.5j], [IGate(), CZGate()])] - if isinstance(gate, UGate): - # theta, lambda, phi - return [([-0.5j], [CZGate()]), ([-0.5j], [CZGate()]), ([-0.5j], [CZGate()])] - if isinstance(gate, RXGate): - # theta - return [([-0.5j], [CXGate()])] - if isinstance(gate, RYGate): - # theta - return [([-0.5j], [CYGate()])] - if isinstance(gate, RZGate): - # theta - return [([-0.5j], [CZGate()])] - if isinstance(gate, RXXGate): - # theta - cxx_circ = QuantumCircuit(3) - cxx_circ.cx(0, 1) - cxx_circ.cx(0, 2) - cxx = cxx_circ.to_instruction() - return [([-0.5j], [cxx])] - if isinstance(gate, RYYGate): - # theta - cyy_circ = QuantumCircuit(3) - cyy_circ.cy(0, 1) - cyy_circ.cy(0, 2) - cyy = cyy_circ.to_instruction() - return [([-0.5j], [cyy])] - if isinstance(gate, RZZGate): - # theta - czz_circ = QuantumCircuit(3) - czz_circ.cz(0, 1) - czz_circ.cz(0, 2) - czz = czz_circ.to_instruction() - return [([-0.5j], [czz])] - if isinstance(gate, RZXGate): - # theta - czx_circ = QuantumCircuit(3) - czx_circ.cx(0, 2) - czx_circ.cz(0, 1) - czx = czx_circ.to_instruction() - return [([-0.5j], [czx])] - if isinstance(gate, ControlledGate): - # TODO support arbitrary control states - if gate.ctrl_state != 2**gate.num_ctrl_qubits - 1: - raise OpflowError( - "Function only support controlled gates with control state `1` on all control " - "qubits." - ) - - base_coeffs_gates = LinComb._gate_gradient_dict(gate.base_gate) - coeffs_gates = [] - # The projectors needed for the gradient of a controlled gate are integrated by a sum - # of gates. - # The following line generates the decomposition gates. - - proj_gates_controlled = [ - [(-1) ** p.count(ZGate()), p] - for p in product([IGate(), ZGate()], repeat=gate.num_ctrl_qubits) - ] - for base_coeffs, base_gates in base_coeffs_gates: # loop over parameters - coeffs = [] - gates = [] - for phase, proj_gates in proj_gates_controlled: - coeffs.extend([phase * c / (2**gate.num_ctrl_qubits) for c in base_coeffs]) - for base_gate in base_gates: - controlled_circ = QuantumCircuit(gate.num_ctrl_qubits + gate.num_qubits) - for i, proj_gate in enumerate(proj_gates): - if isinstance(proj_gate, ZGate): - controlled_circ.cz(0, i + 1) - if not isinstance(base_gate, IGate): - controlled_circ.append( - base_gate, - [ - 0, - range( - gate.num_ctrl_qubits + 1, - gate.num_ctrl_qubits + gate.num_qubits, - ), - ], - ) - gates.append(controlled_circ.to_instruction()) - c_g = (coeffs, gates) - coeffs_gates.append(c_g) - return coeffs_gates - raise TypeError(f"Unrecognized parameterized gate, {gate}") - - @staticmethod - def apply_grad_gate( - circuit, - gate, - param_index, - grad_gate, - grad_coeff, - qr_superpos, - open_ctrl=False, - trim_after_grad_gate=False, - ): - """Util function to apply a gradient gate for the linear combination of unitaries method. - Replaces the ``gate`` instance in ``circuit`` with ``grad_gate`` using ``qr_superpos`` as - superposition qubit. Also adds the appropriate sign-fix gates on the superposition qubit. - - Args: - circuit (QuantumCircuit): The circuit in which to do the replacements. - gate (Gate): The gate instance to replace. - param_index (int): The index of the parameter in ``gate``. - grad_gate (Gate): A controlled gate encoding the gradient of ``gate``. - grad_coeff (float): A coefficient to the gradient component. Might not be one if the - gradient contains multiple summed terms. - qr_superpos (QuantumRegister): A ``QuantumRegister`` of size 1 contained in ``circuit`` - that is used as control for ``grad_gate``. - open_ctrl (bool): If True use an open control for ``grad_gate`` instead of closed. - trim_after_grad_gate (bool): If True remove all gates after the ``grad_gate``. Can - be used to reduce the circuit depth in e.g. computing an overlap of gradients. - - Returns: - QuantumCircuit: A copy of the original circuit with the gradient gate added. - - Raises: - RuntimeError: If ``gate`` is not in ``circuit``. - """ - qr_superpos_qubits = tuple(qr_superpos) - # copy the input circuit taking the gates by reference - out = QuantumCircuit(*circuit.qregs) - out._data.reserve(len(circuit._data)) - out._data.extend(circuit._data) - out._parameter_table = ParameterTable( - {param: values.copy() for param, values in circuit._parameter_table.items()} - ) - - # get the data index and qubits of the target gate TODO use built-in - gate_idx, gate_qubits = None, None - for i, instruction in enumerate(out._data): - if instruction.operation is gate: - gate_idx, gate_qubits = i, instruction.qubits - break - if gate_idx is None: - raise RuntimeError("The specified gate could not be found in the circuit data.") - - # initialize replacement instructions - replacement = [] - - # insert the phase fix before the target gate better documentation - sign = np.sign(grad_coeff) - is_complex = np.iscomplex(grad_coeff) - - if sign < 0 and is_complex: - replacement.append(CircuitInstruction(SdgGate(), qr_superpos_qubits, ())) - elif sign < 0: - replacement.append(CircuitInstruction(ZGate(), qr_superpos_qubits, ())) - elif is_complex: - replacement.append(CircuitInstruction(SGate(), qr_superpos_qubits, ())) - # else no additional gate required - - # open control if specified - if open_ctrl: - replacement += [CircuitInstruction(XGate(), qr_superpos_qubits, [])] - - # compute the replacement - if isinstance(gate, UGate) and param_index == 0: - theta = gate.params[2] - rz_plus, rz_minus = RZGate(theta), RZGate(-theta) - replacement += [CircuitInstruction(rz_plus, (qubit,), ()) for qubit in gate_qubits] - replacement += [ - CircuitInstruction(RXGate(np.pi / 2), (qubit,), ()) for qubit in gate_qubits - ] - replacement.append(CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits, [])) - replacement += [ - CircuitInstruction(RXGate(-np.pi / 2), (qubit,), ()) for qubit in gate_qubits - ] - replacement += [CircuitInstruction(rz_minus, (qubit,), ()) for qubit in gate_qubits] - - # update parametertable if necessary - if isinstance(theta, ParameterExpression): - # This dangerously subverts ParameterTable by abusing the fact that binding will - # mutate the exact instruction instance, and relies on all instances of `rz_plus` - # that were added before being the same in memory, which QuantumCircuit usually - # ensures is not the case. I'm leaving this as close to its previous form as - # possible, to avoid introducing further complications, but this whole method - # accesses internal attributes of `QuantumCircuit` and needs rewriting. - # - Jake Lishman, 2022-03-02. - out._update_parameter_table(rz_plus) - out._update_parameter_table(rz_minus) - - if open_ctrl: - replacement.append(CircuitInstruction(XGate(), qr_superpos_qubits, ())) - - if not trim_after_grad_gate: - replacement.append(CircuitInstruction(gate, gate_qubits, ())) - - elif isinstance(gate, UGate) and param_index == 1: - # gradient gate is applied after the original gate in this case - replacement.append(CircuitInstruction(gate, gate_qubits, ())) - replacement.append(CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits, ())) - if open_ctrl: - replacement.append(CircuitInstruction(XGate(), qr_superpos_qubits, ())) - - else: - replacement.append(CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits, ())) - if open_ctrl: - replacement.append(CircuitInstruction(XGate(), qr_superpos_qubits, ())) - if not trim_after_grad_gate: - replacement.append(CircuitInstruction(gate, gate_qubits, ())) - - # replace the parameter we compute the derivative of with the replacement - # TODO can this be done more efficiently? - if trim_after_grad_gate: # remove everything after the gradient gate - out._data[gate_idx:] = replacement - # reset parameter table - table = ParameterTable() - for instruction in out._data: - for idx, param_expression in enumerate(instruction.operation.params): - if isinstance(param_expression, ParameterExpression): - for param in param_expression.parameters: - if param not in table.keys(): - table[param] = ParameterReferences(((instruction.operation, idx),)) - else: - table[param].add((instruction.operation, idx)) - - out._parameter_table = table - - else: - out._data[gate_idx : gate_idx + 1] = replacement - - return out - - def _aux_meas_basis_trafo( - self, aux_meas_op: OperatorBase, state: StateFn, state_op: StateFn, combo_fn: Callable - ) -> ListOp: - """ - This function applies the necessary basis transformation to measure the quantum state in - a different basis -- given by the auxiliary measurement operator ``aux_meas_op``. - - Args: - aux_meas_op: The auxiliary measurement operator defines the necessary measurement basis. - state: This operator represents the gradient or Hessian before the basis transformation. - state_op: The operator representing the quantum state for which we compute the gradient - or Hessian. - combo_fn: This ``combo_fn`` defines whether the target is a gradient or Hessian. - - - Returns: - Operator representing the gradient or Hessian. - - Raises: - ValueError: If ``aux_meas_op`` is neither ``Z`` nor ``-Y`` nor ``Z - 1j * Y``. - - """ - if aux_meas_op == Z - 1j * Y: - state_z = ListOp( - [state], - combo_fn=partial(combo_fn, state_op=state_op), - ) - pbc = PauliBasisChange(replacement_fn=PauliBasisChange.measurement_replacement_fn) - pbc = pbc.convert(-Y ^ (I ^ (state.num_qubits - 1))) - state_y = pbc[-1] @ state - state_y = ListOp( - [state_y], - combo_fn=partial(combo_fn, state_op=state_op), - ) - return state_z - 1j * state_y - - elif aux_meas_op == -Y: - pbc = PauliBasisChange(replacement_fn=PauliBasisChange.measurement_replacement_fn) - pbc = pbc.convert(aux_meas_op ^ (I ^ (state.num_qubits - 1))) - state = pbc[-1] @ state - return -1 * ListOp( - [state], - combo_fn=partial(combo_fn, state_op=state_op), - ) - elif aux_meas_op == Z: - return ListOp( - [state], - combo_fn=partial(combo_fn, state_op=state_op), - ) - else: - raise ValueError( - f"The auxiliary measurement operator passed {aux_meas_op} is not supported. " - "Only Y, Z, or Z - 1j * Y are valid." - ) - - def _gradient_states( - self, - state_op: StateFn, - meas_op: Optional[OperatorBase] = None, - target_params: Optional[Union[Parameter, List[Parameter]]] = None, - open_ctrl: bool = False, - trim_after_grad_gate: bool = False, - ) -> ListOp: - """Generate the gradient states. - - Args: - state_op: The operator representing the quantum state for which we compute the gradient. - meas_op: The operator representing the observable for which we compute the gradient. - target_params: The parameters we are taking the gradient wrt: ω - open_ctrl: If True use an open control for ``grad_gate`` instead of closed. - trim_after_grad_gate: If True remove all gates after the ``grad_gate``. Can - be used to reduce the circuit depth in e.g. computing an overlap of gradients. - - Returns: - ListOp of StateFns as quantum circuits which are the states w.r.t. which we compute the - gradient. If a parameter appears multiple times, one circuit is created per - parameterized gates to compute the product rule. - - Raises: - QiskitError: If one of the circuits could not be constructed. - TypeError: If the operators is of unsupported type. - ValueError: If the auxiliary operator preparation fails. - """ - # unroll separately from the H gate since we need the H gate to be the first - # operation in the data attributes of the circuit - unrolled = self._transpile_to_supported_operations(state_op.primitive, self.SUPPORTED_GATES) - qr_superpos = QuantumRegister(1) - state_qc = QuantumCircuit(*state_op.primitive.qregs, qr_superpos) - state_qc.h(qr_superpos) - - state_qc.compose(unrolled, inplace=True) - - # Define the working qubit to realize the linear combination of unitaries - if not isinstance(target_params, (list, np.ndarray)): - target_params = [target_params] - - oplist = [] - for param in target_params: - if param not in state_qc.parameters: - oplist += [~Zero @ One] - else: - param_gates = state_qc._parameter_table[param] - sub_oplist = [] - for gate, idx in param_gates: - grad_coeffs, grad_gates = self._gate_gradient_dict(gate)[idx] - - # construct the states - for grad_coeff, grad_gate in zip(grad_coeffs, grad_gates): - grad_circuit = self.apply_grad_gate( - state_qc, - gate, - idx, - grad_gate, - grad_coeff, - qr_superpos, - open_ctrl, - trim_after_grad_gate, - ) - # apply final Hadamard on superposition qubit - grad_circuit.h(qr_superpos) - - # compute the correct coefficient and append to list of circuits - coeff = np.sqrt(np.abs(grad_coeff)) * state_op.coeff - state = CircuitStateFn(grad_circuit, coeff=coeff) - - # apply the chain rule if the parameter expression if required - param_expression = gate.params[idx] - - if isinstance(meas_op, OperatorBase): - state = ( - StateFn(self._aux_meas_op ^ meas_op, is_measurement=True) @ state - ) - - else: - state = self._aux_meas_basis_trafo( - self._aux_meas_op, state, state_op, self._grad_combo_fn - ) - - if param_expression != param: # parameter is not identity, apply chain rule - param_grad = param_expression.gradient(param) - state *= param_grad - - sub_oplist += [state] - - oplist += [SummedOp(sub_oplist) if len(sub_oplist) > 1 else sub_oplist[0]] - - return ListOp(oplist) if len(oplist) > 1 else oplist[0] - - def _hessian_states( - self, - state_op: StateFn, - meas_op: Optional[OperatorBase] = None, - target_params: Optional[ - Union[ - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ] - ] = None, - ) -> OperatorBase: - """Generate the operator states whose evaluation returns the Hessian (items). - - Args: - state_op: The operator representing the quantum state for which we compute the Hessian. - meas_op: The operator representing the observable for which we compute the gradient. - target_params: The parameters we are computing the Hessian wrt: ω - - Returns: - Operators which give the Hessian. If a parameter appears multiple times, one circuit is - created per parameterized gates to compute the product rule. - - Raises: - QiskitError: If one of the circuits could not be constructed. - TypeError: If ``operator`` is of unsupported type. - ValueError: If the auxiliary operator preparation fails. - """ - if not isinstance(target_params, list): - target_params = [target_params] - - if not all(isinstance(params, tuple) for params in target_params): - raise TypeError( - "Please define in the parameters for which the Hessian is evaluated " - "either as parameter tuple or a list of parameter tuples" - ) - - # create circuit with two additional qubits - qr_add0 = QuantumRegister(1, "s0") - qr_add1 = QuantumRegister(1, "s1") - state_qc = QuantumCircuit(*state_op.primitive.qregs, qr_add0, qr_add1) - - # add Hadamards - state_qc.h(qr_add0) - state_qc.h(qr_add1) - - # compose with the original circuit - state_qc.compose(state_op.primitive, inplace=True) - - # create a copy of the original circuit with an additional working qubit register - oplist = [] - for param_a, param_b in target_params: - if param_a not in state_qc.parameters or param_b not in state_qc.parameters: - oplist += [~Zero @ One] - else: - sub_oplist = [] - param_gates_a = state_qc._parameter_table[param_a] - param_gates_b = state_qc._parameter_table[param_b] - for gate_a, idx_a in param_gates_a: - grad_coeffs_a, grad_gates_a = self._gate_gradient_dict(gate_a)[idx_a] - - for grad_coeff_a, grad_gate_a in zip(grad_coeffs_a, grad_gates_a): - grad_circuit = self.apply_grad_gate( - state_qc, gate_a, idx_a, grad_gate_a, grad_coeff_a, qr_add0 - ) - - for gate_b, idx_b in param_gates_b: - grad_coeffs_b, grad_gates_b = self._gate_gradient_dict(gate_b)[idx_b] - - for grad_coeff_b, grad_gate_b in zip(grad_coeffs_b, grad_gates_b): - hessian_circuit = self.apply_grad_gate( - grad_circuit, gate_b, idx_b, grad_gate_b, grad_coeff_b, qr_add1 - ) - - # final Hadamards and CZ - hessian_circuit.h(qr_add0) - hessian_circuit.cz(qr_add1[0], qr_add0[0]) - hessian_circuit.h(qr_add1) - - coeff = state_op.coeff - coeff *= np.sqrt(np.abs(grad_coeff_a) * np.abs(grad_coeff_b)) - state = CircuitStateFn(hessian_circuit, coeff=coeff) - - if meas_op is not None: - state = ( - StateFn(self._aux_meas_op ^ meas_op, is_measurement=True) - @ state - ) - else: - state = self._aux_meas_basis_trafo( - self._aux_meas_op, state, state_op, self._hess_combo_fn - ) - - # Chain Rule Parameter Expression - param_grad = 1 - for gate, idx, param in zip( - [gate_a, gate_b], [idx_a, idx_b], [param_a, param_b] - ): - param_expression = gate.params[idx] - if param_expression != param: # need to apply chain rule - param_grad *= param_expression.gradient(param) - - if param_grad != 1: - state *= param_grad - - sub_oplist += [state] - - oplist += [SummedOp(sub_oplist) if len(sub_oplist) > 1 else sub_oplist[0]] - - return ListOp(oplist) if len(oplist) > 1 else oplist[0] - - -def _z_exp(spmatrix): - """Compute the sampling probabilities of the qubits after applying measurement on the - auxiliary qubit.""" - - dok = spmatrix.todok() - num_qubits = int(np.log2(dok.shape[1])) - exp = scipy.sparse.dok_matrix((1, 2 ** (num_qubits - 1))) - - for index, amplitude in dok.items(): - binary = bin(index[1])[2:].zfill(num_qubits) - sign = -1 if binary[0] == "1" else 1 - new_index = int(binary[1:], 2) - exp[(0, new_index)] = exp[(0, new_index)] + 2 * sign * np.abs(amplitude) ** 2 - - return exp diff --git a/qiskit/opflow/gradients/circuit_gradients/param_shift.py b/qiskit/opflow/gradients/circuit_gradients/param_shift.py deleted file mode 100644 index dcbb58052aa0..000000000000 --- a/qiskit/opflow/gradients/circuit_gradients/param_shift.py +++ /dev/null @@ -1,430 +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. - -"""The module to compute the state gradient with the parameter shift rule.""" - -from collections.abc import Iterable -from copy import deepcopy -from functools import partial -from typing import List, Union, Tuple, Dict - -import scipy -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter, ParameterExpression, ParameterVector -from qiskit.utils.deprecation import deprecate_func -from .circuit_gradient import CircuitGradient -from ...operator_base import OperatorBase -from ...state_fns.state_fn import StateFn -from ...operator_globals import Zero, One -from ...state_fns.circuit_state_fn import CircuitStateFn -from ...primitive_ops.circuit_op import CircuitOp -from ...list_ops.summed_op import SummedOp -from ...list_ops.list_op import ListOp -from ...list_ops.composed_op import ComposedOp -from ...state_fns.dict_state_fn import DictStateFn -from ...state_fns.vector_state_fn import VectorStateFn -from ...state_fns.sparse_vector_state_fn import SparseVectorStateFn -from ...exceptions import OpflowError -from ..derivative_base import _coeff_derivative - - -class ParamShift(CircuitGradient): - """Deprecated: Compute the gradient d⟨ψ(ω)|O(θ)|ψ(ω)〉/ dω respectively the gradients of the sampling - probabilities of the basis states of a state |ψ(ω)〉w.r.t. ω with the parameter shift - method. - """ - - SUPPORTED_GATES = {"x", "y", "z", "h", "rx", "ry", "rz", "p", "u", "cx", "cy", "cz"} - - @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): - r""" - Args: - analytic: If True use the parameter shift rule to compute analytic gradients, - else use a finite difference approach - epsilon: The offset size to use when computing finite difference gradients. - Ignored if analytic == True - - Raises: - ValueError: If method != ``fin_diff`` and ``epsilon`` is not None. - """ - super().__init__() - self._analytic = analytic - self._epsilon = epsilon - - @property - def analytic(self) -> bool: - """Returns ``analytic`` flag. - - Returns: - ``analytic`` flag. - - """ - return self._analytic - - @property - def epsilon(self) -> float: - """Returns ``epsilon``. - - Returns: - ``epsilon``. - - """ - return self._epsilon - - # pylint: disable=signature-differs - def convert( - self, - operator: OperatorBase, - params: Union[ - ParameterExpression, - ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ], - ) -> OperatorBase: - """ - Args: - operator: The operator corresponding to our quantum state we are taking the - gradient of: |ψ(ω)〉 - params: The parameters we are taking the gradient wrt: ω - If a ParameterExpression, ParameterVector or List[ParameterExpression] is given, - then the 1st order derivative of the operator is calculated. - If a Tuple[ParameterExpression, ParameterExpression] or - List[Tuple[ParameterExpression, ParameterExpression]] - is given, then the 2nd order derivative of the operator is calculated. - - Returns: - An operator corresponding to the gradient resp. Hessian. The order is in accordance with - the order of the given parameters. - - Raises: - OpflowError: If the parameters are given in an invalid format. - - """ - if isinstance(params, (ParameterExpression, ParameterVector)): - return self._parameter_shift(operator, params) - elif isinstance(params, tuple): - return self._parameter_shift(self._parameter_shift(operator, params[0]), params[1]) - elif isinstance(params, Iterable): - if all(isinstance(param, ParameterExpression) for param in params): - return self._parameter_shift(operator, params) - elif all(isinstance(param, tuple) for param in params): - return ListOp( - [ - self._parameter_shift(self._parameter_shift(operator, pair[0]), pair[1]) - for pair in params - ] - ) - else: - raise OpflowError( - "The linear combination gradient does only support " - "the computation " - "of 1st gradients and 2nd order gradients." - ) - else: - raise OpflowError( - "The linear combination gradient does only support the computation " - "of 1st gradients and 2nd order gradients." - ) - - # pylint: disable=too-many-return-statements - def _parameter_shift( - self, operator: OperatorBase, params: Union[ParameterExpression, ParameterVector, List] - ) -> OperatorBase: - r""" - Args: - operator: The operator containing circuits we are taking the derivative of. - params: The parameters (ω) we are taking the derivative with respect to. If - a ParameterVector is provided, each parameter will be shifted. - - Returns: - param_shifted_op: An operator object which evaluates to the respective gradients. - - Raises: - ValueError: If the given parameters do not occur in the provided operator - TypeError: If the operator has more than one circuit representing the quantum state - """ - if isinstance(params, (ParameterVector, list)): - param_grads = [self._parameter_shift(operator, param) for param in params] - absent_params = [ - params[i] for i, grad_ops in enumerate(param_grads) if grad_ops is None - ] - if len(absent_params) > 0: - raise ValueError( - "The following parameters do not appear in the provided operator: ", - absent_params, - ) - return ListOp(absent_params) - - # By this point, it's only one parameter - param = params - - if isinstance(operator, ListOp) and not isinstance(operator, ComposedOp): - return_op = operator.traverse(partial(self._parameter_shift, params=param)) - - # Remove any branch of the tree where the relevant parameter does not occur - trimmed_oplist = [op for op in return_op.oplist if op is not None] - # If all branches are None, remove the parent too - if len(trimmed_oplist) == 0: - return None - # Rebuild the operator with the trimmed down oplist - properties = {"coeff": return_op._coeff, "abelian": return_op._abelian} - if return_op.__class__ == ListOp: - properties["combo_fn"] = return_op.combo_fn - return return_op.__class__(oplist=trimmed_oplist, **properties) - - else: - circs = self.get_unique_circuits(operator) - - if len(circs) > 1: - raise TypeError( - "Please define an operator with a single circuit representing " - "the quantum state." - ) - if len(circs) == 0: - return operator - circ = circs[0] - - if self.analytic: - # Unroll the circuit into a gate set for which the gradient may be computed - # using pi/2 shifts. - circ = ParamShift._transpile_to_supported_operations(circ, self.SUPPORTED_GATES) - operator = ParamShift._replace_operator_circuit(operator, circ) - - if param not in circ._parameter_table: - return ~Zero @ One - - shifted_ops = [] - summed_shifted_op = None - - iref_to_data_index = {id(inst.operation): idx for idx, inst in enumerate(circ.data)} - - for param_reference in circ._parameter_table[param]: - original_gate, param_index = param_reference - m = iref_to_data_index[id(original_gate)] - - pshift_op = deepcopy(operator) - mshift_op = deepcopy(operator) - - # We need the circuit objects of the newly instantiated operators - pshift_circ = self.get_unique_circuits(pshift_op)[0] - mshift_circ = self.get_unique_circuits(mshift_op)[0] - - pshift_gate = pshift_circ.data[m].operation - mshift_gate = mshift_circ.data[m].operation - - p_param = pshift_gate.params[param_index] - m_param = mshift_gate.params[param_index] - # For analytic gradients the circuit parameters are shifted once by +pi/2 and - # once by -pi/2. - if self.analytic: - shift_constant = 0.5 - pshift_gate.params[param_index] = p_param + (np.pi / (4 * shift_constant)) - mshift_gate.params[param_index] = m_param - (np.pi / (4 * shift_constant)) - # For finite difference gradients the circuit parameters are shifted once by - # +epsilon and once by -epsilon. - else: - shift_constant = 1.0 / (2 * self._epsilon) - pshift_gate.params[param_index] = p_param + self._epsilon - mshift_gate.params[param_index] = m_param - self._epsilon - # The results of the shifted operators are now evaluated according the parameter - # shift / finite difference formula. - if isinstance(operator, ComposedOp): - shifted_op = shift_constant * (pshift_op - mshift_op) - # If the operator represents a quantum state then we apply a special combo - # function to evaluate probability gradients. - elif isinstance(operator, StateFn): - shifted_op = ListOp( - [pshift_op, mshift_op], - combo_fn=partial(self._prob_combo_fn, shift_constant=shift_constant), - ) - else: - raise TypeError( - "Probability gradients are not supported for the given operator type" - ) - - if isinstance(p_param, ParameterExpression) and not isinstance(p_param, Parameter): - expr_grad = _coeff_derivative(p_param, param) - shifted_op *= expr_grad - if not summed_shifted_op: - summed_shifted_op = shifted_op - else: - summed_shifted_op += shifted_op - - shifted_ops.append(summed_shifted_op) - - if not SummedOp(shifted_ops).reduce(): - return ~StateFn(Zero) @ One - else: - return SummedOp(shifted_ops).reduce() - - @staticmethod - def _prob_combo_fn( - x: Union[ - DictStateFn, - VectorStateFn, - SparseVectorStateFn, - List[Union[DictStateFn, VectorStateFn, SparseVectorStateFn]], - ], - shift_constant: float, - ) -> Union[Dict, np.ndarray]: - """Implement the combo_fn used to evaluate probability gradients - - Args: - x: Output of an operator evaluation - shift_constant: Shifting constant factor needed for proper rescaling - - Returns: - Array representing the probability gradients w.r.t. the given operator and parameters - - Raises: - TypeError: if ``x`` is not DictStateFn, VectorStateFn or their list. - - """ - # Note: In the probability gradient case, the amplitudes still need to be converted - # into sampling probabilities. - - def get_primitives(item): - if isinstance(item, (DictStateFn, SparseVectorStateFn)): - item = item.primitive - if isinstance(item, VectorStateFn): - item = item.primitive.data - return item - - is_statefn = False - if isinstance(x, list): - # Check if all items in x are a StateFn items - if all(isinstance(item, StateFn) for item in x): - is_statefn = True - items = [get_primitives(item) for item in x] - else: - # Check if x is a StateFn item - if isinstance(x, StateFn): - is_statefn = True - items = [get_primitives(x)] - if isinstance(items[0], dict): - prob_dict: Dict[str, float] = {} - for i, item in enumerate(items): - for key, prob_counts in item.items(): - prob_dict[key] = ( - prob_dict.get(key, 0) + shift_constant * ((-1) ** i) * prob_counts - ) - return prob_dict - elif isinstance(items[0], scipy.sparse.spmatrix): - # If x was given as StateFn the state amplitudes need to be multiplied in order to - # evaluate the sampling probabilities which are then subtracted according to the - # parameter shift rule. - if is_statefn: - return shift_constant * np.subtract( - items[0].multiply(np.conj(items[0])), items[1].multiply(np.conj(items[1])) - ) - # If x was not given as a StateFn the state amplitudes were already converted into - # sampling probabilities which are then only subtracted according to the - # parameter shift rule. - else: - return shift_constant * np.subtract(items[0], items[1]) - elif isinstance(items[0], Iterable): - # If x was given as StateFn the state amplitudes need to be multiplied in order to - # evaluate the sampling probabilities which are then subtracted according to the - # parameter shift rule. - if is_statefn: - return shift_constant * np.subtract( - np.multiply(items[0], np.conj(items[0])), - np.multiply(items[1], np.conj(items[1])), - ) - # If x was not given as a StateFn the state amplitudes were already converted into - # sampling probabilities which are then only subtracted according to the - # parameter shift rule. - else: - return shift_constant * np.subtract(items[0], items[1]) - raise TypeError( - "Probability gradients can only be evaluated from VectorStateFs or DictStateFns." - ) - - @staticmethod - def _replace_operator_circuit(operator: OperatorBase, circuit: QuantumCircuit) -> OperatorBase: - """Replace a circuit element in an operator with a single element given as circuit - - Args: - operator: Operator for which the circuit representing the quantum state shall be - replaced - circuit: Circuit which shall replace the circuit in the given operator - - Returns: - Operator with replaced circuit quantum state function - - """ - if isinstance(operator, CircuitStateFn): - return CircuitStateFn(circuit, coeff=operator.coeff) - elif isinstance(operator, CircuitOp): - return CircuitOp(circuit, coeff=operator.coeff) - elif isinstance(operator, (ComposedOp, ListOp)): - return operator.traverse(partial(ParamShift._replace_operator_circuit, circuit=circuit)) - else: - return operator - - @classmethod - def get_unique_circuits(cls, operator: OperatorBase) -> List[QuantumCircuit]: - """Traverse the operator and return all unique circuits - - Args: - operator: An operator that potentially includes QuantumCircuits - - Returns: - A list of all unique quantum circuits that appear in the operator - - """ - if isinstance(operator, CircuitStateFn): - return [operator.primitive] - - def get_circuit(op): - return op.primitive if isinstance(op, (CircuitStateFn, CircuitOp)) else None - - unrolled_op = cls.unroll_operator(operator) - circuits = [] - for ops in unrolled_op: - if not isinstance(ops, list): - ops = [ops] - for op in ops: - if isinstance(op, (CircuitStateFn, CircuitOp, QuantumCircuit)): - c = get_circuit(op) - if c and c not in circuits: - circuits.append(c) - return circuits - - @classmethod - def unroll_operator(cls, operator: OperatorBase) -> Union[OperatorBase, List[OperatorBase]]: - """Traverse the operator and return all OperatorBase objects flattened - into a single list. This is used as a subroutine to extract all - circuits within a large composite operator. - - Args: - operator: An OperatorBase type object - - Returns: - A single flattened list of all OperatorBase objects within the - input operator - - """ - if isinstance(operator, ListOp): - return [cls.unroll_operator(op) for op in operator] - if hasattr(operator, "primitive") and isinstance(operator.primitive, ListOp): - return [operator.__class__(op) for op in operator.primitive] - return operator diff --git a/qiskit/opflow/gradients/circuit_qfis/__init__.py b/qiskit/opflow/gradients/circuit_qfis/__init__.py deleted file mode 100644 index d32126acd523..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/__init__.py +++ /dev/null @@ -1,20 +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 module for first order derivatives.""" - -from .circuit_qfi import CircuitQFI -from .lin_comb_full import LinCombFull -from .overlap_diag import OverlapDiag -from .overlap_block_diag import OverlapBlockDiag - -__all__ = ["CircuitQFI", "LinCombFull", "OverlapDiag", "OverlapBlockDiag"] diff --git a/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py b/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py deleted file mode 100644 index 034492aca523..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py +++ /dev/null @@ -1,66 +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. - -"""CircuitQFI Class""" - -from abc import abstractmethod -from typing import List, Union - -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.utils.deprecation import deprecate_func -from ...converters.converter_base import ConverterBase -from ...operator_base import OperatorBase - - -class CircuitQFI(ConverterBase): - r"""Deprecated: Circuit to Quantum Fisher Information operator converter. - - Converter for changing parameterized circuits into operators - whose evaluation yields Quantum Fisher Information metric tensor - with respect to the given circuit parameters - - This is distinct from DerivativeBase converters which take gradients of composite - operators and handle things like differentiating combo_fn's and enforcing product rules - when operator coefficients are parameterized. - - CircuitQFI - uses quantum techniques to get the QFI of circuits - DerivativeBase - uses classical techniques to differentiate opflow data structures - """ - - @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: - super().__init__() - - # pylint: disable=arguments-differ - @abstractmethod - def convert( - self, - operator: OperatorBase, - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> OperatorBase: - r""" - Args: - operator: The operator corresponding to the quantum state :math:`|\psi(\omega)\rangle` - for which we compute the QFI. - params: The parameters :math:`\omega` with respect to which we are computing the QFI. - - Returns: - An operator whose evaluation yields the QFI metric tensor. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - """ - raise NotImplementedError diff --git a/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py b/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py deleted file mode 100644 index 91793e03ac1b..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py +++ /dev/null @@ -1,228 +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. - -"""The module for Quantum the Fisher Information.""" - -from typing import List, Union - -import numpy as np -from qiskit.circuit import QuantumCircuit, QuantumRegister, ParameterVector, ParameterExpression -from qiskit.utils.arithmetic import triu_to_dense -from qiskit.utils.deprecation import deprecate_func -from ...operator_base import OperatorBase -from ...list_ops.list_op import ListOp -from ...list_ops.summed_op import SummedOp -from ...operator_globals import I, Z, Y -from ...state_fns.state_fn import StateFn -from ...state_fns.circuit_state_fn import CircuitStateFn -from ..circuit_gradients.lin_comb import LinComb -from .circuit_qfi import CircuitQFI - - -class LinCombFull(CircuitQFI): - r"""Deprecated: Compute the full Quantum Fisher Information (QFI). - - Given a pure, parameterized quantum state this class uses the linear combination of unitaries - See also :class:`~qiskit.opflow.QFI`. - """ - - # 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, - phase_fix: bool = True, - ): - """ - Args: - aux_meas_op: The operator that the auxiliary qubit is measured with respect to. - For ``aux_meas_op = Z`` we compute 4Re[(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉], - for ``aux_meas_op = -Y`` we compute 4Im[(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉], and - for ``aux_meas_op = Z - 1j * Y`` we compute 4(dω⟨ψ(ω)|)O(θ)|ψ(ω)〉. - phase_fix: Whether or not to compute and add the additional phase fix term - Re[(dω⟨<ψ(ω)|)|ψ(ω)><ψ(ω)|(dω|ψ(ω))>]. - Raises: - ValueError: If the provided auxiliary measurement operator is not supported. - """ - super().__init__() - if aux_meas_op not in [Z, -Y, (Z - 1j * Y)]: - raise ValueError( - "This auxiliary measurement operator is currently not supported. Please choose " - "either Z, -Y, or Z - 1j * Y. " - ) - self._aux_meas_op = aux_meas_op - self._phase_fix = phase_fix - - def convert( - self, - operator: CircuitStateFn, - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> ListOp: - r""" - Args: - operator: The operator corresponding to the quantum state :math:`|\psi(\omega)\rangle` - for which we compute the QFI. - params: The parameters :math:`\omega` with respect to which we are computing the QFI. - Returns: - A ``ListOp[ListOp]`` where the operator at position ``[k][l]`` corresponds to the matrix - element :math:`k, l` of the QFI. - - Raises: - TypeError: If ``operator`` is an unsupported type. - """ - # QFI & phase fix observable - qfi_observable = StateFn( - 4 * self._aux_meas_op ^ (I ^ operator.num_qubits), is_measurement=True - ) - - # Check if the given operator corresponds to a quantum state given as a circuit. - if not isinstance(operator, CircuitStateFn): - raise TypeError( - "LinCombFull is only compatible with states that are given as " - f"CircuitStateFn, not {type(operator)}" - ) - - # If a single parameter is given wrap it into a list. - if isinstance(params, ParameterExpression): - params = [params] - elif isinstance(params, ParameterVector): - params = params[:] # unroll to list - - if self._phase_fix: - # First, the operators are computed which can compensate for a potential phase-mismatch - # between target and trained state, i.e.〈ψ|∂lψ〉 - phase_fix_observable = I ^ operator.num_qubits - gradient_states = LinComb(aux_meas_op=(Z - 1j * Y))._gradient_states( - operator, - meas_op=phase_fix_observable, - target_params=params, - open_ctrl=False, - trim_after_grad_gate=True, - ) - - # pylint: disable=unidiomatic-typecheck - if type(gradient_states) == ListOp: - phase_fix_states = gradient_states.oplist - else: - phase_fix_states = [gradient_states] - - # Get 4 * Re[〈∂kψ|∂lψ] - qfi_operators = [] - # Add a working qubit - qr_work = QuantumRegister(1, "work_qubit") - state_qc = QuantumCircuit(*operator.primitive.qregs, qr_work) - state_qc.h(qr_work) - # unroll separately from the H gate since we need the H gate to be the first - # operation in the data attributes of the circuit - unrolled = LinComb._transpile_to_supported_operations( - operator.primitive, LinComb.SUPPORTED_GATES - ) - state_qc.compose(unrolled, inplace=True) - - # Get the circuits needed to compute〈∂iψ|∂jψ〉 - for i, param_i in enumerate(params): # loop over parameters - qfi_ops = [] - for j, param_j in enumerate(params[i:], i): - # Get the gates of the quantum state which are parameterized by param_i - qfi_op = [] - param_gates_i = state_qc._parameter_table[param_i] - for gate_i, idx_i in param_gates_i: - grad_coeffs_i, grad_gates_i = LinComb._gate_gradient_dict(gate_i)[idx_i] - - # get the location of gate_i, used for trimming - location_i = None - for idx, instruction in enumerate(state_qc._data): - if instruction.operation is gate_i: - location_i = idx - break - - for grad_coeff_i, grad_gate_i in zip(grad_coeffs_i, grad_gates_i): - - # Get the gates of the quantum state which are parameterized by param_j - param_gates_j = state_qc._parameter_table[param_j] - for gate_j, idx_j in param_gates_j: - grad_coeffs_j, grad_gates_j = LinComb._gate_gradient_dict(gate_j)[idx_j] - - # get the location of gate_j, used for trimming - location_j = None - for idx, instruction in enumerate(state_qc._data): - if instruction.operation is gate_j: - location_j = idx - break - - for grad_coeff_j, grad_gate_j in zip(grad_coeffs_j, grad_gates_j): - - grad_coeff_ij = np.conj(grad_coeff_i) * grad_coeff_j - qfi_circuit = LinComb.apply_grad_gate( - state_qc, - gate_i, - idx_i, - grad_gate_i, - grad_coeff_ij, - qr_work, - open_ctrl=True, - trim_after_grad_gate=(location_j < location_i), - ) - - # create a copy of the original circuit with the same registers - qfi_circuit = LinComb.apply_grad_gate( - qfi_circuit, - gate_j, - idx_j, - grad_gate_j, - 1, - qr_work, - open_ctrl=False, - trim_after_grad_gate=(location_j >= location_i), - ) - - qfi_circuit.h(qr_work) - # Convert the quantum circuit into a CircuitStateFn and add the - # coefficients i, j and the original operator coefficient - coeff = operator.coeff - coeff *= np.sqrt(np.abs(grad_coeff_i) * np.abs(grad_coeff_j)) - state = CircuitStateFn(qfi_circuit, coeff=coeff) - - param_grad = 1 - for gate, idx, param in zip( - [gate_i, gate_j], [idx_i, idx_j], [param_i, param_j] - ): - param_expression = gate.params[idx] - param_grad *= param_expression.gradient(param) - meas = param_grad * qfi_observable - - term = meas @ state - - qfi_op.append(term) - - # Compute −4 * Re(〈∂kψ|ψ〉〈ψ|∂lψ〉) - def phase_fix_combo_fn(x): - return -4 * np.real(x[0] * np.conjugate(x[1])) - - if self._phase_fix: - phase_fix_op = ListOp( - [phase_fix_states[i], phase_fix_states[j]], combo_fn=phase_fix_combo_fn - ) - # Add the phase fix quantities to the entries of the QFI - # Get 4 * Re[〈∂kψ|∂lψ〉−〈∂kψ|ψ〉〈ψ|∂lψ〉] - qfi_ops += [SummedOp(qfi_op) + phase_fix_op] - else: - qfi_ops += [SummedOp(qfi_op)] - - qfi_operators.append(ListOp(qfi_ops)) - - # Return estimate of the full QFI -- A QFI is by definition positive semi-definite. - return ListOp(qfi_operators, combo_fn=triu_to_dense) diff --git a/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py b/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py deleted file mode 100644 index 8e6e41dbd2f0..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py +++ /dev/null @@ -1,195 +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. - -"""The module for the Quantum Fisher Information.""" - -from typing import List, Union - -import numpy as np -from scipy.linalg import block_diag -from qiskit.circuit import Parameter, ParameterVector, ParameterExpression -from qiskit.utils.arithmetic import triu_to_dense -from qiskit.utils.deprecation import deprecate_func -from ...list_ops.list_op import ListOp -from ...primitive_ops.circuit_op import CircuitOp -from ...expectations.pauli_expectation import PauliExpectation -from ...operator_globals import Zero -from ...state_fns.state_fn import StateFn -from ...state_fns.circuit_state_fn import CircuitStateFn -from ...exceptions import OpflowError - -from .circuit_qfi import CircuitQFI -from ..derivative_base import _coeff_derivative -from .overlap_diag import _get_generators, _partition_circuit - - -class OverlapBlockDiag(CircuitQFI): - r"""Deprecated: Compute the block-diagonal of the QFI given a pure, parameterized quantum state. - - The blocks are given by all parameterized gates in quantum circuit layer. - See also :class:`~qiskit.opflow.QFI`. - """ - - @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: - super().__init__() - - def convert( - self, - operator: Union[CircuitOp, CircuitStateFn], - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> ListOp: - r""" - Args: - operator: The operator corresponding to the quantum state :math:`|\psi(\omega)\rangle` - for which we compute the QFI. - params: The parameters :math:`\omega` with respect to which we are computing the QFI. - - Returns: - A ``ListOp[ListOp]`` where the operator at position ``[k][l]`` corresponds to the matrix - element :math:`k, l` of the QFI. - - Raises: - NotImplementedError: If ``operator`` is neither ``CircuitOp`` nor ``CircuitStateFn``. - """ - if not isinstance(operator, (CircuitOp, CircuitStateFn)): - raise NotImplementedError("operator must be a CircuitOp or CircuitStateFn") - return self._block_diag_approx(operator=operator, params=params) - - def _block_diag_approx( - self, - operator: Union[CircuitOp, CircuitStateFn], - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> ListOp: - r""" - Args: - operator: The operator corresponding to the quantum state :math:`|\psi(\omega)\rangle` - for which we compute the QFI. - params: The parameters :math:`\omega` with respect to which we are computing the QFI. - - Returns: - A ``ListOp[ListOp]`` where the operator at position ``[k][l]`` corresponds to the matrix - element :math:`k, l` of the QFI. - - Raises: - NotImplementedError: If a circuit is found such that one parameter controls multiple - gates, or one gate contains multiple parameters. - OpflowError: If there are more than one parameter. - - """ - - # If a single parameter is given wrap it into a list. - if isinstance(params, ParameterExpression): - params = [params] - - circuit = operator.primitive - # Partition the circuit into layers, and build the circuits to prepare $\psi_i$ - layers = _partition_circuit(circuit) - if layers[-1].num_parameters == 0: - layers.pop(-1) - - block_params = [list(layer.parameters) for layer in layers] - # Remove any parameters found which are not in params - block_params = [[param for param in block if param in params] for block in block_params] - - # Determine the permutation needed to ensure that the final - # operator is consistent with the ordering of the input parameters - perm = [params.index(param) for block in block_params for param in block] - - psis = [CircuitOp(layer) for layer in layers] - for i, psi in enumerate(psis): - if i == 0: - continue - psis[i] = psi @ psis[i - 1] - - # Get generators - # TODO: make this work for other types of rotations - # NOTE: This assumes that each parameter only affects one rotation. - # we need to think more about what happens if multiple rotations - # are controlled with a single parameter. - - generators = _get_generators(params, circuit) - - blocks = [] - - # Psi_i = layer_i @ layer_i-1 @ ... @ layer_0 @ Zero - for k, psi_i in enumerate(psis): - params = block_params[k] - block = np.zeros((len(params), len(params))).tolist() - - # calculate all single-operator terms - single_terms = np.zeros(len(params)).tolist() - for i, p_i in enumerate(params): - generator = generators[p_i] - psi_gen_i = ~StateFn(generator) @ psi_i @ Zero - psi_gen_i = PauliExpectation().convert(psi_gen_i) - single_terms[i] = psi_gen_i - - def get_parameter_expression(circuit, param): - if len(circuit._parameter_table[param]) > 1: - raise NotImplementedError( - "OverlapDiag does not yet support multiple " - "gates parameterized by a single parameter. For such " - "circuits use LinCombFull" - ) - gate = next(iter(circuit._parameter_table[param]))[0] - if len(gate.params) > 1: - raise OpflowError( - "OverlapDiag cannot yet support gates with more than one parameter." - ) - - param_value = gate.params[0] - return param_value - - # Calculate all double-operator terms - # and build composite operators for each matrix entry - for i, p_i in enumerate(params): - generator_i = generators[p_i] - param_expr_i = get_parameter_expression(circuit, p_i) - for j, p_j in enumerate(params[i:], i): - if i == j: - block[i][i] = ListOp([single_terms[i]], combo_fn=lambda x: 1 - x[0] ** 2) - if isinstance(param_expr_i, ParameterExpression) and not isinstance( - param_expr_i, Parameter - ): - expr_grad_i = _coeff_derivative(param_expr_i, p_i) - block[i][i] *= expr_grad_i * expr_grad_i - continue - - generator_j = generators[p_j] - generator = ~generator_j @ generator_i - param_expr_j = get_parameter_expression(circuit, p_j) - - psi_gen_ij = ~StateFn(generator) @ psi_i @ Zero - psi_gen_ij = PauliExpectation().convert(psi_gen_ij) - cross_term = ListOp([single_terms[i], single_terms[j]], combo_fn=np.prod) - block[i][j] = psi_gen_ij - cross_term - - # pylint: disable=unidiomatic-typecheck - if type(param_expr_i) == ParameterExpression: - expr_grad_i = _coeff_derivative(param_expr_i, p_i) - block[i][j] *= expr_grad_i - if type(param_expr_j) == ParameterExpression: - expr_grad_j = _coeff_derivative(param_expr_j, p_j) - block[i][j] *= expr_grad_j - - wrapped_block = ListOp( - [ListOp([block[i][j] for j in range(i, len(params))]) for i in range(len(params))], - combo_fn=triu_to_dense, - ) - blocks.append(wrapped_block) - - return ListOp(oplist=blocks, combo_fn=lambda x: np.real(block_diag(*x))[:, perm][perm, :]) diff --git a/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py b/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py deleted file mode 100644 index b579c1930713..000000000000 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py +++ /dev/null @@ -1,275 +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. - -"""The module for Quantum the Fisher Information.""" - -import copy -from typing import List, Union - -import numpy as np -from qiskit.circuit import ParameterVector, ParameterExpression -from qiskit.circuit.library import RZGate, RXGate, RYGate -from qiskit.converters import dag_to_circuit, circuit_to_dag -from qiskit.utils.deprecation import deprecate_func -from ...list_ops.list_op import ListOp -from ...primitive_ops.circuit_op import CircuitOp -from ...expectations.pauli_expectation import PauliExpectation -from ...operator_globals import I, Z, Y, X, Zero -from ...state_fns.state_fn import StateFn -from ...state_fns.circuit_state_fn import CircuitStateFn - - -from .circuit_qfi import CircuitQFI -from ..derivative_base import _coeff_derivative - - -class OverlapDiag(CircuitQFI): - r"""Deprecated: Compute the diagonal of the QFI given a pure, parameterized quantum state. - - See also :class:`~qiskit.opflow.QFI`. - """ - - @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: - super().__init__() - - def convert( - self, - operator: Union[CircuitOp, CircuitStateFn], - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> ListOp: - r""" - Args: - operator: The operator corresponding to the quantum state :math:`|\psi(\omega)\rangle` - for which we compute the QFI. - params: The parameters :math:`\omega` with respect to which we are computing the QFI. - - Returns: - A ``ListOp[ListOp]`` where the operator at position ``[k][l]`` corresponds to the matrix - element :math:`k, l` of the QFI. - - Raises: - NotImplementedError: If ``operator`` is neither ``CircuitOp`` nor ``CircuitStateFn``. - - """ - - if not isinstance(operator, CircuitStateFn): - raise NotImplementedError("operator must be a CircuitStateFn") - - return self._diagonal_approx(operator=operator, params=params) - - # TODO, for some reason diagonal_approx doesn't use the same get_parameter_expression method. - # This should be fixed. - def _diagonal_approx( - self, - operator: Union[CircuitOp, CircuitStateFn], - params: Union[ParameterExpression, ParameterVector, List], - ) -> ListOp: - """ - Args: - operator: The operator corresponding to the quantum state |ψ(ω)〉for which we compute - the QFI - params: The parameters we are computing the QFI wrt: ω - - Returns: - ListOp where the operator at position k corresponds to QFI_k,k - - Raises: - NotImplementedError: If a circuit is found such that one parameter controls multiple - gates, or one gate contains multiple parameters. - TypeError: If a circuit is found that includes more than one parameter as they are - currently not supported for the overlap diagonal QFI method. - - """ - - if not isinstance(operator, (CircuitOp, CircuitStateFn)): - raise NotImplementedError("operator must be a CircuitOp or CircuitStateFn") - - # If a single parameter is given wrap it into a list. - if isinstance(params, ParameterExpression): - params = [params] - - circuit = operator.primitive - - # Partition the circuit into layers, and build the circuits to prepare $\psi_i$ - layers = _partition_circuit(circuit) - if layers[-1].num_parameters == 0: - layers.pop(-1) - - psis = [CircuitOp(layer) for layer in layers] - for i, psi in enumerate(psis): - if i == 0: - continue - psis[i] = psi @ psis[i - 1] - - # TODO: make this work for other types of rotations - # NOTE: This assumes that each parameter only affects one rotation. - # we need to think more about what happens if multiple rotations - # are controlled with a single parameter. - generators = _get_generators(params, circuit) - - diag = [] - for param in params: - if len(circuit._parameter_table[param]) > 1: - raise NotImplementedError( - "OverlapDiag does not yet support multiple " - "gates parameterized by a single parameter. For such " - "circuits use LinCombFull" - ) - - gate = next(iter(circuit._parameter_table[param]))[0] - - if len(gate.params) != 1: - raise TypeError( - "OverlapDiag cannot yet support gates with more than one parameter." - ) - - param_value = gate.params[0] - generator = generators[param] - meas_op = ~StateFn(generator) - - # get appropriate psi_i - psi = [(psi) for psi in psis if param in psi.primitive.parameters][0] - - op = meas_op @ psi @ Zero - if type(param_value) == ParameterExpression: # pylint: disable=unidiomatic-typecheck - expr_grad = _coeff_derivative(param_value, param) - op *= expr_grad - rotated_op = PauliExpectation().convert(op) - diag.append(rotated_op) - - grad_op = ListOp(diag, combo_fn=lambda x: np.diag(np.real([1 - y**2 for y in x]))) - return grad_op - - -def _partition_circuit(circuit): - dag = circuit_to_dag(circuit) - dag_layers = [i["graph"] for i in dag.serial_layers()] - num_qubits = circuit.num_qubits - layers = list( - zip(dag_layers, [{x: False for x in range(0, num_qubits)} for layer in dag_layers]) - ) - - # initialize the ledger - # The ledger tracks which qubits in each layer are available to have - # gates from subsequent layers shifted backward. - # The idea being that all parameterized gates should have - # no descendants within their layer - bit_indices = {bit: index for index, bit in enumerate(circuit.qubits)} - for i, (layer, ledger) in enumerate(layers): - op_node = layer.op_nodes()[0] - is_param = op_node.op.is_parameterized() - qargs = op_node.qargs - indices = [bit_indices[qarg] for qarg in qargs] - if is_param: - for index in indices: - ledger[index] = True - - def apply_node_op(node, dag, back=True): - op = copy.copy(node.op) - qargs = copy.copy(node.qargs) - cargs = copy.copy(node.cargs) - if back: - dag.apply_operation_back(op, qargs, cargs) - else: - dag.apply_operation_front(op, qargs, cargs) - - converged = False - - for _ in range(dag.depth() + 1): - if converged: - break - - converged = True - - for i, (layer, ledger) in enumerate(layers): - if i == len(layers) - 1: - continue - - (next_layer, next_ledger) = layers[i + 1] - for next_node in next_layer.op_nodes(): - is_param = next_node.op.is_parameterized() - qargs = next_node.qargs - indices = [bit_indices[qarg] for qarg in qargs] - - # If the next_node can be moved back a layer without - # without becoming the descendant of a parameterized gate, - # then do it. - if not any(ledger[x] for x in indices): - - apply_node_op(next_node, layer) - next_layer.remove_op_node(next_node) - - if is_param: - for index in indices: - ledger[index] = True - next_ledger[index] = False - - converged = False - - # clean up empty layers left behind. - if len(next_layer.op_nodes()) == 0: - layers.pop(i + 1) - - partitioned_circs = [dag_to_circuit(layer[0]) for layer in layers] - return partitioned_circs - - -def _get_generators(params, circuit): - dag = circuit_to_dag(circuit) - layers = list(dag.serial_layers()) - - generators = {} - num_qubits = dag.num_qubits() - bit_indices = {bit: index for index, bit in enumerate(circuit.qubits)} - - for layer in layers: - instr = layer["graph"].op_nodes()[0].op - # if no gate is parameterized, skip - if not any(isinstance(param, ParameterExpression) for param in instr.params): - continue - - if len(instr.params) != 1: - raise NotImplementedError( - "The QFI diagonal approximation currently only supports " - "gates with a single free parameter." - ) - param_value = instr.params[0] - - for param in params: - if param in param_value.parameters: - - if isinstance(instr, RYGate): - generator = Y - elif isinstance(instr, RZGate): - generator = Z - elif isinstance(instr, RXGate): - generator = X - else: - raise NotImplementedError(f"Generator for gate {instr.name} not implemented.") - - # get all qubit indices in this layer where the param parameterizes - # an operation. - indices = [[bit_indices[q] for q in qreg] for qreg in layer["partition"]] - indices = [item for sublist in indices for item in sublist] - - if len(indices) > 1: - raise NotImplementedError - index = indices[0] - generator = (I ^ (index)) ^ generator ^ (I ^ (num_qubits - index - 1)) - generators[param] = generator - - return generators diff --git a/qiskit/opflow/gradients/derivative_base.py b/qiskit/opflow/gradients/derivative_base.py deleted file mode 100644 index 4ba0ffed2418..000000000000 --- a/qiskit/opflow/gradients/derivative_base.py +++ /dev/null @@ -1,247 +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. - -"""DerivativeBase Class""" - -from abc import abstractmethod -from typing import Callable, Iterable, List, Optional, Tuple, Union - -import numpy as np -from qiskit.utils.deprecation import deprecate_func -from qiskit.utils.quantum_instance import QuantumInstance -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.providers import Backend -from ..converters.converter_base import ConverterBase -from ..expectations import ExpectationBase, PauliExpectation -from ..list_ops.composed_op import ComposedOp -from ..list_ops.list_op import ListOp -from ..list_ops.tensored_op import TensoredOp -from ..operator_base import OperatorBase -from ..primitive_ops.primitive_op import PrimitiveOp -from ..state_fns import StateFn, OperatorStateFn - -OperatorType = Union[StateFn, PrimitiveOp, ListOp] - - -class DerivativeBase(ConverterBase): - r"""Deprecated: Base class for differentiating opflow objects. - - Converter for differentiating opflow objects and handling - things like properly differentiating combo_fn's and enforcing product rules - when operator coefficients are parameterized. - - This is distinct from CircuitGradient converters which use quantum - techniques such as parameter shifts and linear combination of unitaries - to compute derivatives of circuits. - - CircuitGradient - uses quantum techniques to get derivatives of circuits - DerivativeBase - uses classical techniques to differentiate opflow data structures - """ - - @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: - super().__init__() - - # pylint: disable=arguments-differ - @abstractmethod - def convert( - self, - operator: OperatorBase, - params: Optional[ - Union[ParameterVector, ParameterExpression, List[ParameterExpression]] - ] = None, - ) -> OperatorBase: - r""" - Args: - operator: The operator we are taking the gradient, Hessian or QFI of - params: The parameters we are taking the gradient, Hessian or QFI with respect to. - - Returns: - An operator whose evaluation yields the gradient, Hessian or QFI. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - """ - raise NotImplementedError - - def gradient_wrapper( - self, - operator: OperatorBase, - bind_params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - grad_params: Optional[ - Union[ - ParameterExpression, - ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - ] - ] = None, - backend: Optional[Union[Backend, QuantumInstance]] = None, - expectation: Optional[ExpectationBase] = None, - ) -> Callable[[Iterable], np.ndarray]: - """Get a callable function which provides the respective gradient, Hessian or QFI for given - parameter values. This callable can be used as gradient function for optimizers. - - Args: - operator: The operator for which we want to get the gradient, Hessian or QFI. - bind_params: The operator parameters to which the parameter values are assigned. - grad_params: The parameters with respect to which we are taking the gradient, Hessian - or QFI. If grad_params = None, then grad_params = bind_params - backend: The quantum backend or QuantumInstance to use to evaluate the gradient, - Hessian or QFI. - expectation: The expectation converter to be used. If none is set then - `PauliExpectation()` is used. - - Returns: - Function to compute a gradient, Hessian or QFI. The function - takes an iterable as argument which holds the parameter values. - """ - from ..converters import CircuitSampler - - if grad_params is None: - grad_params = bind_params - - grad = self.convert(operator, grad_params) - if expectation is None: - expectation = PauliExpectation() - grad = expectation.convert(grad) - - sampler = CircuitSampler(backend=backend) if backend is not None else None - - def gradient_fn(p_values): - p_values_dict = dict(zip(bind_params, p_values)) - if not backend: - converter = grad.assign_parameters(p_values_dict) - return np.real(converter.eval()) - else: - p_values_list = {k: [v] for k, v in p_values_dict.items()} - sampled = sampler.convert(grad, p_values_list) - fully_bound = sampled.bind_parameters(p_values_dict) - return np.real(fully_bound.eval()[0]) - - return gradient_fn - - @staticmethod - @deprecate_func( - 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 - ) -> Union[ParameterExpression, float]: - """Get the derivative of a parameter expression w.r.t. the given parameter. - - Args: - param_expr: The Parameter Expression for which we compute the derivative - param: Parameter w.r.t. which we want to take the derivative - - Returns: - ParameterExpression representing the gradient of param_expr w.r.t. param - """ - return _coeff_derivative(param_expr, param) - - @classmethod - def _erase_operator_coeffs(cls, operator: OperatorBase) -> OperatorBase: - """This method traverses an input operator and deletes all of the coefficients - - Args: - operator: An operator type object. - - Returns: - An operator which is equal to the input operator but whose coefficients - have all been set to 1.0 - - Raises: - TypeError: If unknown operator type is reached. - """ - if isinstance(operator, PrimitiveOp): - return operator / operator.coeff - op_coeff = operator.coeff # type: ignore - return (operator / op_coeff).traverse(cls._erase_operator_coeffs) - - @classmethod - def _factor_coeffs_out_of_composed_op(cls, operator: OperatorBase) -> OperatorBase: - """Factor all coefficients of ComposedOp out into a single global coefficient. - - Part of the automatic differentiation logic inside of Gradient and Hessian - counts on the fact that no product or chain rules need to be computed between - operators or coefficients within a ComposedOp. To ensure this condition is met, - this function traverses an operator and replaces each ComposedOp with an equivalent - ComposedOp, but where all coefficients have been factored out and placed onto the - ComposedOp. Note that this cannot be done properly if an OperatorMeasurement contains - a SummedOp as it's primitive. - - Args: - operator: The operator whose coefficients are being re-organized - - Returns: - An operator equivalent to the input operator, but whose coefficients have been - reorganized - - Raises: - ValueError: If an element within a ComposedOp has a primitive of type ListOp, - then it is not possible to factor all coefficients out of the ComposedOp. - """ - if isinstance(operator, ListOp) and not isinstance(operator, ComposedOp): - return operator.traverse(cls._factor_coeffs_out_of_composed_op) - if isinstance(operator, ComposedOp): - total_coeff = operator.coeff - take_norm_of_coeffs = False - for k, op in enumerate(operator.oplist): - if take_norm_of_coeffs: - total_coeff *= op.coeff * np.conj(op.coeff) # type: ignore - else: - total_coeff *= op.coeff # type: ignore - if hasattr(op, "primitive"): - prim = op.primitive # type: ignore - if isinstance(op, StateFn) and isinstance(prim, TensoredOp): - # Check if any of the coefficients in the TensoredOp is a - # ParameterExpression - for prim_op in prim.oplist: - # If a coefficient is a ParameterExpression make sure that the - # coefficients are pulled together correctly - if isinstance(prim_op.coeff, ParameterExpression): - prim_tensored = StateFn( - prim.reduce(), is_measurement=op.is_measurement, coeff=op.coeff - ) - operator.oplist[k] = prim_tensored - return operator.traverse(cls._factor_coeffs_out_of_composed_op) - elif isinstance(prim, ListOp): - raise ValueError( - "This operator was not properly decomposed. " - "By this point, all operator measurements should " - "contain single operators, otherwise the coefficient " - "gradients will not be handled properly." - ) - if hasattr(prim, "coeff"): - if take_norm_of_coeffs: - total_coeff *= prim._coeff * np.conj(prim._coeff) - else: - total_coeff *= prim._coeff - if isinstance(op, OperatorStateFn) and op.is_measurement: - take_norm_of_coeffs = True - return cls._erase_operator_coeffs(operator).mul(total_coeff) - - else: - return operator - - -def _coeff_derivative(coeff, param): - if isinstance(coeff, ParameterExpression) and len(coeff.parameters) > 0: - return coeff.gradient(param) - return 0 diff --git a/qiskit/opflow/gradients/gradient.py b/qiskit/opflow/gradients/gradient.py deleted file mode 100644 index 045d4695621e..000000000000 --- a/qiskit/opflow/gradients/gradient.py +++ /dev/null @@ -1,231 +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. - -"""The base interface for Opflow's gradient.""" - -from typing import Union, List, Optional -import numpy as np - -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.circuit._utils import sort_parameters -from qiskit.utils import optionals as _optionals -from qiskit.utils.deprecation import deprecate_func -from .circuit_gradients.circuit_gradient import CircuitGradient -from ..expectations.pauli_expectation import PauliExpectation -from .gradient_base import GradientBase -from .derivative_base import _coeff_derivative -from ..list_ops.composed_op import ComposedOp -from ..list_ops.list_op import ListOp -from ..list_ops.summed_op import SummedOp -from ..list_ops.tensored_op import TensoredOp -from ..operator_base import OperatorBase -from ..operator_globals import Zero, One -from ..state_fns.circuit_state_fn import CircuitStateFn -from ..exceptions import OpflowError - - -class Gradient(GradientBase): - """Deprecated: Convert an operator expression to the first-order gradient.""" - - @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): - super().__init__(grad_method=grad_method, **kwargs) - - def convert( - self, - operator: OperatorBase, - params: Optional[ - Union[ParameterVector, ParameterExpression, List[ParameterExpression]] - ] = None, - ) -> OperatorBase: - r""" - Args: - operator: The operator we are taking the gradient of. - params: The parameters we are taking the gradient with respect to. If not - explicitly passed, they are inferred from the operator and sorted by name. - - Returns: - An operator whose evaluation yields the Gradient. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - ValueError: If ``operator`` is not parameterized. - """ - if len(operator.parameters) == 0: - raise ValueError("The operator we are taking the gradient of is not parameterized!") - if params is None: - params = sort_parameters(operator.parameters) - if isinstance(params, (ParameterVector, list)): - param_grads = [self.convert(operator, param) for param in params] - absent_params = [ - params[i] for i, grad_ops in enumerate(param_grads) if grad_ops is None - ] - if len(absent_params) > 0: - raise ValueError( - "The following parameters do not appear in the provided operator: ", - absent_params, - ) - return ListOp(param_grads) - - param = params - # Preprocessing - expec_op = PauliExpectation(group_paulis=False).convert(operator).reduce() - cleaned_op = self._factor_coeffs_out_of_composed_op(expec_op) - return self.get_gradient(cleaned_op, param) - - # pylint: disable=too-many-return-statements - def get_gradient( - self, - operator: OperatorBase, - params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]], - ) -> OperatorBase: - """Get the gradient for the given operator w.r.t. the given parameters - - Args: - operator: Operator w.r.t. which we take the gradient. - params: Parameters w.r.t. which we compute the gradient. - - Returns: - Operator which represents the gradient w.r.t. the given params. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - OpflowError: If the coefficient of the operator could not be reduced to 1. - OpflowError: If the differentiation of a combo_fn requires JAX but the package is not - installed. - TypeError: If the operator does not include a StateFn given by a quantum circuit - Exception: Unintended code is reached - MissingOptionalLibraryError: jax not installed - """ - - def is_coeff_c(coeff, c): - if isinstance(coeff, ParameterExpression): - expr = coeff._symbol_expr - return expr == c - return coeff == c - - def is_coeff_c_abs(coeff, c): - if isinstance(coeff, ParameterExpression): - expr = coeff._symbol_expr - return np.abs(expr) == c - return np.abs(coeff) == c - - if isinstance(params, (ParameterVector, list)): - param_grads = [self.get_gradient(operator, param) for param in params] - # If get_gradient returns None, then the corresponding parameter was probably not - # present in the operator. This needs to be looked at more carefully as other things can - # probably trigger a return of None. - absent_params = [ - params[i] for i, grad_ops in enumerate(param_grads) if grad_ops is None - ] - if len(absent_params) > 0: - raise ValueError( - "The following parameters do not appear in the provided operator: ", - absent_params, - ) - return ListOp(param_grads) - - # By now params is a single parameter - param = params - # Handle Product Rules - if not is_coeff_c(operator._coeff, 1.0) and not is_coeff_c(operator._coeff, 1.0j): - # Separate the operator from the coefficient - coeff = operator._coeff - op = operator / coeff - if np.iscomplex(coeff): - from .circuit_gradients.lin_comb import LinComb - - if isinstance(self.grad_method, LinComb): - op *= 1j - coeff /= 1j - - # Get derivative of the operator (recursively) - d_op = self.get_gradient(op, param) - # ..get derivative of the coeff - d_coeff = _coeff_derivative(coeff, param) - - grad_op = 0 - if d_op != ~Zero @ One and not is_coeff_c(coeff, 0.0): - grad_op += coeff * d_op - if op != ~Zero @ One and not is_coeff_c(d_coeff, 0.0): - grad_op += d_coeff * op - if grad_op == 0: - grad_op = ~Zero @ One - return grad_op - - # Base Case, you've hit a ComposedOp! - # Prior to execution, the composite operator was standardized and coefficients were - # collected. Any operator measurements were converted to Pauli-Z measurements and rotation - # circuits were applied. Additionally, all coefficients within ComposedOps were collected - # and moved out front. - if isinstance(operator, ComposedOp): - - # Gradient of an expectation value - if not is_coeff_c_abs(operator._coeff, 1.0): - raise OpflowError( - "Operator pre-processing failed. Coefficients were not properly " - "collected inside the ComposedOp." - ) - - # Do some checks to make sure operator is sensible - # TODO add compatibility with sum of circuit state fns - if not isinstance(operator[-1], CircuitStateFn): - raise TypeError( - "The gradient framework is compatible with states that are given as " - "CircuitStateFn" - ) - - return self.grad_method.convert(operator, param) - - elif isinstance(operator, CircuitStateFn): - # Gradient of an a state's sampling probabilities - if not is_coeff_c(operator._coeff, 1.0): - raise OpflowError( - "Operator pre-processing failed. Coefficients were not properly " - "collected inside the ComposedOp." - ) - return self.grad_method.convert(operator, param) - - # Handle the chain rule - elif isinstance(operator, ListOp): - grad_ops = [self.get_gradient(op, param) for op in operator.oplist] - - # pylint: disable=comparison-with-callable - if operator.combo_fn == ListOp.default_combo_fn: # If using default - return ListOp(oplist=grad_ops) - elif isinstance(operator, SummedOp): - return SummedOp(oplist=[grad for grad in grad_ops if grad != ~Zero @ One]).reduce() - elif isinstance(operator, TensoredOp): - return TensoredOp(oplist=grad_ops) - - if operator.grad_combo_fn: - grad_combo_fn = operator.grad_combo_fn - else: - _optionals.HAS_JAX.require_now("automatic differentiation") - from jax import jit, grad - - grad_combo_fn = jit(grad(operator.combo_fn, holomorphic=True)) - - def chain_rule_combo_fn(x): - result = np.dot(x[1], x[0]) - if isinstance(result, np.ndarray): - result = list(result) - return result - - return ListOp( - [ListOp(operator.oplist, combo_fn=grad_combo_fn), ListOp(grad_ops)], - combo_fn=chain_rule_combo_fn, - ) diff --git a/qiskit/opflow/gradients/gradient_base.py b/qiskit/opflow/gradients/gradient_base.py deleted file mode 100644 index 03b813dd5c09..000000000000 --- a/qiskit/opflow/gradients/gradient_base.py +++ /dev/null @@ -1,77 +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. - -"""The base interface for Aqua's gradient.""" - -from typing import Union - -from qiskit.utils.deprecation import deprecate_func -from .circuit_gradients.circuit_gradient import CircuitGradient -from .derivative_base import DerivativeBase - - -class GradientBase(DerivativeBase): - """Deprecated: Base class for first-order operator gradient. - - Convert an operator expression to the first-order gradient. - """ - - @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): - r""" - Args: - grad_method: The method used to compute the state/probability gradient. Can be either - ``'param_shift'`` or ``'lin_comb'`` or ``'fin_diff'``. - Ignored for gradients w.r.t observable parameters. - kwargs (dict): Optional parameters for a CircuitGradient - - Raises: - ValueError: If method != ``fin_diff`` and ``epsilon`` is not None. - """ - super().__init__() - if isinstance(grad_method, CircuitGradient): - self._grad_method = grad_method - elif grad_method == "param_shift": - from .circuit_gradients.param_shift import ParamShift - - self._grad_method = ParamShift(analytic=True) - - elif grad_method == "fin_diff": - from .circuit_gradients.param_shift import ParamShift - - epsilon = kwargs.get("epsilon", 1e-6) - self._grad_method = ParamShift(analytic=False, epsilon=epsilon) - - elif grad_method == "lin_comb": - from .circuit_gradients.lin_comb import LinComb - - self._grad_method = LinComb() - else: - raise ValueError( - "Unrecognized input provided for `grad_method`. Please provide" - " a CircuitGradient object or one of the pre-defined string" - " arguments: {'param_shift', 'fin_diff', 'lin_comb'}. " - ) - - @property - def grad_method(self) -> CircuitGradient: - """Returns ``CircuitGradient``. - - Returns: - ``CircuitGradient``. - - """ - return self._grad_method diff --git a/qiskit/opflow/gradients/hessian.py b/qiskit/opflow/gradients/hessian.py deleted file mode 100644 index 68dc44863a44..000000000000 --- a/qiskit/opflow/gradients/hessian.py +++ /dev/null @@ -1,293 +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. - -"""The module to compute Hessians.""" - -from typing import Union, List, Tuple, Optional -import numpy as np - -from qiskit.circuit import ParameterVector, ParameterExpression -from qiskit.circuit._utils import sort_parameters -from qiskit.utils import optionals as _optionals -from qiskit.utils.deprecation import deprecate_func -from ..operator_globals import Zero, One -from ..state_fns.circuit_state_fn import CircuitStateFn -from ..state_fns.state_fn import StateFn -from ..expectations.pauli_expectation import PauliExpectation -from ..list_ops.list_op import ListOp -from ..list_ops.composed_op import ComposedOp -from ..list_ops.summed_op import SummedOp -from ..list_ops.tensored_op import TensoredOp -from ..operator_base import OperatorBase -from .gradient import Gradient -from .derivative_base import _coeff_derivative -from .hessian_base import HessianBase -from ..exceptions import OpflowError -from ...utils.arithmetic import triu_to_dense -from .circuit_gradients.circuit_gradient import CircuitGradient - - -class Hessian(HessianBase): - """Deprecated: Compute the Hessian of an expected value.""" - - @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): - super().__init__(hess_method=hess_method, **kwargs) - - def convert( - self, - operator: OperatorBase, - params: Optional[ - Union[ - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - List[ParameterExpression], - ParameterVector, - ] - ] = None, - ) -> OperatorBase: - """ - Args: - operator: The operator for which we compute the Hessian - params: The parameters we are computing the Hessian with respect to - Either give directly the tuples/list of tuples for which the second order - derivative is to be computed or give a list of parameters to build the - full Hessian for those parameters. If not explicitly passed, the full Hessian is - constructed. The parameters are then inferred from the operator and sorted by - name. - - Returns: - OperatorBase: An operator whose evaluation yields the Hessian - """ - - expec_op = PauliExpectation(group_paulis=False).convert(operator).reduce() - cleaned_op = self._factor_coeffs_out_of_composed_op(expec_op) - return self.get_hessian(cleaned_op, params) - - # pylint: disable=too-many-return-statements - def get_hessian( - self, - operator: OperatorBase, - params: Optional[ - Union[ - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - List[ParameterExpression], - ParameterVector, - ] - ] = None, - ) -> OperatorBase: - """Get the Hessian for the given operator w.r.t. the given parameters - - Args: - operator: Operator w.r.t. which we take the Hessian. - params: Parameters w.r.t. which we compute the Hessian. If not explicitly passed, - the full Hessian is constructed. The parameters are then inferred from the operator - and sorted by name. - - Returns: - Operator which represents the gradient w.r.t. the given params. - - Raises: - ValueError: If ``params`` contains a parameter not present in ``operator``. - ValueError: If ``operator`` is not parameterized. - OpflowError: If the coefficient of the operator could not be reduced to 1. - OpflowError: If the differentiation of a combo_fn - requires JAX but the package is not installed. - TypeError: If the operator does not include a StateFn given by a quantum circuit - TypeError: If the parameters were given in an unsupported format. - Exception: Unintended code is reached - MissingOptionalLibraryError: jax not installed - """ - if len(operator.parameters) == 0: - raise ValueError("The operator we are taking the gradient of is not parameterized!") - if params is None: - params = sort_parameters(operator.parameters) - # if input is a tuple instead of a list, wrap it into a list - if isinstance(params, (ParameterVector, list)): - # Case: a list of parameters were given, compute the Hessian for all param pairs - if all(isinstance(param, ParameterExpression) for param in params): - return ListOp( - [ - ListOp( - [ - self.get_hessian(operator, (p_i, p_j)) - for i, p_i in enumerate(params[j:], j) - ] - ) - for j, p_j in enumerate(params) - ], - combo_fn=triu_to_dense, - ) - # Case: a list was given containing tuples of parameter pairs. - elif all(isinstance(param, tuple) for param in params): - # Compute the Hessian entries corresponding to these pairs of parameters. - return ListOp([self.get_hessian(operator, param_pair) for param_pair in params]) - - def is_coeff_c(coeff, c): - if isinstance(coeff, ParameterExpression): - expr = coeff._symbol_expr - return expr == c - return coeff == c - - # If a gradient is requested w.r.t a single parameter, then call the - # Gradient().get_gradient method. - if isinstance(params, ParameterExpression): - return Gradient(grad_method=self._hess_method).get_gradient(operator, params) - - if (not isinstance(params, tuple)) or (not len(params) == 2): - raise TypeError("Parameters supplied in unsupported format.") - - # By this point, it's only one parameter tuple - p_0 = params[0] - p_1 = params[1] - - # Handle Product Rules - if not is_coeff_c(operator._coeff, 1.0): - # Separate the operator from the coefficient - coeff = operator._coeff - op = operator / coeff - # Get derivative of the operator (recursively) - d0_op = self.get_hessian(op, p_0) - d1_op = self.get_hessian(op, p_1) - # ..get derivative of the coeff - d0_coeff = _coeff_derivative(coeff, p_0) - d1_coeff = _coeff_derivative(coeff, p_1) - - dd_op = self.get_hessian(op, params) - dd_coeff = _coeff_derivative(d0_coeff, p_1) - - grad_op = 0 - # Avoid creating operators that will evaluate to zero - if dd_op != ~Zero @ One and not is_coeff_c(coeff, 0): - grad_op += coeff * dd_op - if d0_op != ~Zero @ One and not is_coeff_c(d1_coeff, 0): - grad_op += d1_coeff * d0_op - if d1_op != ~Zero @ One and not is_coeff_c(d0_coeff, 0): - grad_op += d0_coeff * d1_op - if not is_coeff_c(dd_coeff, 0): - grad_op += dd_coeff * op - - if grad_op == 0: - return ~Zero @ One - - return grad_op - - # Base Case, you've hit a ComposedOp! - # Prior to execution, the composite operator was standardized and coefficients were - # collected. Any operator measurements were converted to Pauli-Z measurements and rotation - # circuits were applied. Additionally, all coefficients within ComposedOps were collected - # and moved out front. - if isinstance(operator, ComposedOp): - - if not is_coeff_c(operator.coeff, 1.0): - raise OpflowError( - "Operator pre-processing failed. Coefficients were not properly " - "collected inside the ComposedOp." - ) - - # Do some checks to make sure operator is sensible - # TODO enable compatibility with sum of CircuitStateFn operators - if not isinstance(operator[-1], CircuitStateFn): - raise TypeError( - "The gradient framework is compatible with states that are given as " - "CircuitStateFn" - ) - - return self.hess_method.convert(operator, params) - - # This is the recursive case where the chain rule is handled - elif isinstance(operator, ListOp): - # These operators correspond to (d_op/d θ0,θ1) for op in operator.oplist - # and params = (θ0,θ1) - dd_ops = [self.get_hessian(op, params) for op in operator.oplist] - - # TODO Note that this check to see if the ListOp has a default combo_fn - # will fail if the user manually specifies the default combo_fn. - # I.e operator = ListOp([...], combo_fn=lambda x:x) will not pass this check and - # later on jax will try to differentiate it and fail. - # An alternative is to check the byte code of the operator's combo_fn against the - # default one. - # This will work but look very ugly and may have other downsides I'm not aware of - if operator.combo_fn == ListOp([]).combo_fn: - return ListOp(oplist=dd_ops) - elif isinstance(operator, SummedOp): - return SummedOp(oplist=dd_ops) - elif isinstance(operator, TensoredOp): - return TensoredOp(oplist=dd_ops) - - # These operators correspond to (d g_i/d θ0)•(d g_i/d θ1) for op in operator.oplist - # and params = (θ0,θ1) - d1d0_ops = ListOp( - [ - ListOp( - [ - Gradient(grad_method=self._hess_method).convert(op, param) - for param in params - ], - combo_fn=np.prod, - ) - for op in operator.oplist - ] - ) - - _optionals.HAS_JAX.require_now("automatic differentiation") - from jax import grad, jit - - if operator.grad_combo_fn: - first_partial_combo_fn = operator.grad_combo_fn - - second_partial_combo_fn = jit( - grad(lambda x: first_partial_combo_fn(x)[0], holomorphic=True) - ) - else: - first_partial_combo_fn = jit(grad(operator.combo_fn, holomorphic=True)) - second_partial_combo_fn = jit( - grad(lambda x: first_partial_combo_fn(x)[0], holomorphic=True) - ) - - # For a general combo_fn F(g_0, g_1, ..., g_k) - # dF/d θ0,θ1 = sum_i: (∂F/∂g_i)•(d g_i/ d θ0,θ1) + (∂F/∂^2 g_i)•(d g_i/d θ0)•(d g_i/d - # θ1) - - # term1 = (∂F/∂g_i)•(d g_i/ d θ0,θ1) - term1 = ListOp( - [ListOp(operator.oplist, combo_fn=first_partial_combo_fn), ListOp(dd_ops)], - combo_fn=lambda x: np.dot(x[1], x[0]), - ) - # term2 = (∂F/∂^2 g_i)•(d g_i/d θ0)•(d g_i/d θ1) - term2 = ListOp( - [ListOp(operator.oplist, combo_fn=second_partial_combo_fn), d1d0_ops], - combo_fn=lambda x: np.dot(x[1], x[0]), - ) - - return SummedOp([term1, term2]) - - elif isinstance(operator, StateFn): - if not operator.is_measurement: - return self.hess_method.convert(operator, params) - - else: - raise TypeError( - "The computation of Hessians is only supported for Operators which " - "represent expectation values or quantum states." - ) - - else: - raise TypeError( - "The computation of Hessians is only supported for Operators which " - "represent expectation values." - ) diff --git a/qiskit/opflow/gradients/hessian_base.py b/qiskit/opflow/gradients/hessian_base.py deleted file mode 100644 index 77f81ca1d6e0..000000000000 --- a/qiskit/opflow/gradients/hessian_base.py +++ /dev/null @@ -1,75 +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. - -"""The module to compute Hessians.""" - -from typing import Union - -from qiskit.utils.deprecation import deprecate_func -from .circuit_gradients.circuit_gradient import CircuitGradient -from .derivative_base import DerivativeBase - - -class HessianBase(DerivativeBase): - """Deprecated: Base class for the Hessian of an expected value.""" - - @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): - r""" - Args: - hess_method: The method used to compute the state/probability gradient. Can be either - ``'param_shift'`` or ``'lin_comb'`` or ``'fin_diff'``. - Ignored for gradients w.r.t observable parameters. - kwargs (dict): Optional parameters for a CircuitGradient - - Raises: - ValueError: If method != ``fin_diff`` and ``epsilon`` is not None. - """ - super().__init__() - if isinstance(hess_method, CircuitGradient): - self._hess_method = hess_method - elif hess_method == "param_shift": - from .circuit_gradients import ParamShift - - self._hess_method = ParamShift() - - elif hess_method == "fin_diff": - from .circuit_gradients import ParamShift - - epsilon = kwargs.get("epsilon", 1e-6) - self._hess_method = ParamShift(analytic=False, epsilon=epsilon) - - elif hess_method == "lin_comb": - from .circuit_gradients import LinComb - - self._hess_method = LinComb() - - else: - raise ValueError( - "Unrecognized input provided for `hess_method`. Please provide" - " a CircuitGradient object or one of the pre-defined string" - " arguments: {'param_shift', 'fin_diff', 'lin_comb'}. " - ) - - @property - def hess_method(self) -> CircuitGradient: - """Returns ``CircuitGradient``. - - Returns: - ``CircuitGradient``. - - """ - return self._hess_method diff --git a/qiskit/opflow/gradients/natural_gradient.py b/qiskit/opflow/gradients/natural_gradient.py deleted file mode 100644 index 6a2edb5a108b..000000000000 --- a/qiskit/opflow/gradients/natural_gradient.py +++ /dev/null @@ -1,561 +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. - -"""Natural Gradient.""" - -from collections.abc import Iterable -from typing import List, Tuple, Callable, Optional, Union -import numpy as np - -from qiskit.circuit import ParameterVector, ParameterExpression -from qiskit.circuit._utils import sort_parameters -from qiskit.utils import optionals as _optionals -from qiskit.utils.deprecation import deprecate_func -from ..operator_base import OperatorBase -from ..list_ops.list_op import ListOp -from ..list_ops.composed_op import ComposedOp -from ..state_fns.circuit_state_fn import CircuitStateFn -from .circuit_gradients import CircuitGradient -from .circuit_qfis import CircuitQFI -from .gradient import Gradient -from .gradient_base import GradientBase -from .qfi import QFI - -# Error tolerance variable -ETOL = 1e-8 -# Cut-off ratio for small singular values for least square solver -RCOND = 1e-2 - - -class NaturalGradient(GradientBase): - r"""Deprecated: Convert an operator expression to the first-order gradient. - - Given an ill-posed inverse problem - - x = arg min{||Ax-C||^2} (1) - - one can use regularization schemes can be used to stabilize the system and find a numerical - solution - - x_lambda = arg min{||Ax-C||^2 + lambda*R(x)} (2) - - where R(x) represents the penalization term. - """ - - @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] = "lin_comb", - qfi_method: Union[str, CircuitQFI] = "lin_comb_full", - regularization: Optional[str] = None, - **kwargs, - ): - r""" - Args: - grad_method: The method used to compute the state gradient. Can be either - ``'param_shift'`` or ``'lin_comb'`` or ``'fin_diff'``. - qfi_method: The method used to compute the QFI. Can be either - ``'lin_comb_full'`` or ``'overlap_block_diag'`` or ``'overlap_diag'``. - regularization: Use the following regularization with a least square method to solve the - underlying system of linear equations - Can be either None or ``'ridge'`` or ``'lasso'`` or ``'perturb_diag'`` - ``'ridge'`` and ``'lasso'`` use an automatic optimal parameter search - If regularization is None but the metric is ill-conditioned or singular then - a least square solver is used without regularization - kwargs (dict): Optional parameters for a CircuitGradient - """ - super().__init__(grad_method) - - self._qfi_method = QFI(qfi_method) - self._regularization = regularization - self._epsilon = kwargs.get("epsilon", 1e-6) - - def convert( - self, - operator: OperatorBase, - params: Optional[ - Union[ParameterVector, ParameterExpression, List[ParameterExpression]] - ] = None, - ) -> OperatorBase: - r""" - Args: - operator: The operator we are taking the gradient of. - params: The parameters we are taking the gradient with respect to. If not explicitly - passed, they are inferred from the operator and sorted by name. - - Returns: - An operator whose evaluation yields the NaturalGradient. - - Raises: - TypeError: If ``operator`` does not represent an expectation value or the quantum - state is not ``CircuitStateFn``. - ValueError: If ``params`` contains a parameter not present in ``operator``. - ValueError: If ``operator`` is not parameterized. - """ - if not isinstance(operator, ComposedOp): - if not (isinstance(operator, ListOp) and len(operator.oplist) == 1): - raise TypeError( - "Please provide the operator either as ComposedOp or as ListOp of " - "a CircuitStateFn potentially with a combo function." - ) - - if not isinstance(operator[-1], CircuitStateFn): - raise TypeError( - "Please make sure that the operator for which you want to compute " - "Quantum Fisher Information represents an expectation value or a " - "loss function and that the quantum state is given as " - "CircuitStateFn." - ) - if len(operator.parameters) == 0: - raise ValueError("The operator we are taking the gradient of is not parameterized!") - if params is None: - params = sort_parameters(operator.parameters) - if not isinstance(params, Iterable): - params = [params] - # Instantiate the gradient - grad = Gradient(self._grad_method, epsilon=self._epsilon).convert(operator, params) - # Instantiate the QFI metric which is used to re-scale the gradient - metric = self._qfi_method.convert(operator[-1], params) * 0.25 - - def combo_fn(x): - return self.nat_grad_combo_fn(x, self.regularization) - - # Define the ListOp which combines the gradient and the QFI according to the combination - # function defined above. - return ListOp([grad, metric], combo_fn=combo_fn) - - @staticmethod - def nat_grad_combo_fn(x: tuple, regularization: Optional[str] = None) -> np.ndarray: - r""" - Natural Gradient Function Implementation. - - Args: - x: Iterable consisting of Gradient, Quantum Fisher Information. - regularization: Regularization method. - - Returns: - Natural Gradient. - - Raises: - ValueError: If the gradient has imaginary components that are non-negligible. - - """ - gradient = x[0] - metric = x[1] - if np.amax(np.abs(np.imag(gradient))) > ETOL: - raise ValueError( - "The imaginary part of the gradient are non-negligible. The largest absolute " - f"imaginary value in the gradient is {np.amax(np.abs(np.imag(gradient)))}. " - "Please increase the number of shots." - ) - gradient = np.real(gradient) - - if np.amax(np.abs(np.imag(metric))) > ETOL: - raise ValueError( - "The imaginary part of the metric are non-negligible. The largest " - "absolute imaginary value in the gradient is " - f"{np.amax(np.abs(np.imag(metric)))}. Please " - "increase the number of shots." - ) - metric = np.real(metric) - - if regularization is not None: - # If a regularization method is chosen then use a regularized solver to - # construct the natural gradient. - nat_grad = NaturalGradient._regularized_sle_solver( - metric, gradient, regularization=regularization - ) - else: - # Check if numerical instabilities lead to a metric which is not positive semidefinite - w, v = np.linalg.eigh(metric) - - if not all(ew >= (-1) * ETOL for ew in w): - raise ValueError( - f"The underlying metric has at least one Eigenvalue < -{ETOL}. " - f"The smallest Eigenvalue is {np.amin(w)} " - "Please use a regularized least-square solver for this problem or " - "increase the number of backend shots.", - ) - if not all(ew >= 0 for ew in w): - # If not all eigenvalues are non-negative, set them to a small positive - # value - w = [max(ETOL, ew) for ew in w] - # Recompose the adapted eigenvalues with the eigenvectors to get a new metric - metric = np.real(v @ np.diag(w) @ np.linalg.inv(v)) - nat_grad = np.linalg.lstsq(metric, gradient, rcond=RCOND)[0] - return nat_grad - - @property - def qfi_method(self) -> CircuitQFI: - """Returns ``CircuitQFI``. - - Returns: ``CircuitQFI``. - - """ - return self._qfi_method.qfi_method - - @property - def regularization(self) -> Optional[str]: - """Returns the regularization option. - - Returns: the regularization option. - - """ - return self._regularization - - @staticmethod - def _reg_term_search( - metric: np.ndarray, - gradient: np.ndarray, - reg_method: Callable[[np.ndarray, np.ndarray, float], float], - lambda1: float = 1e-3, - lambda4: float = 1.0, - tol: float = 1e-8, - ) -> Tuple[float, np.ndarray]: - """ - This method implements a search for a regularization parameter lambda by finding for the - corner of the L-curve. - More explicitly, one has to evaluate a suitable lambda by finding a compromise between - the error in the solution and the norm of the regularization. - This function implements a method presented in - `A simple algorithm to find the L-curve corner in the regularization of inverse problems - ` - - Args: - metric: See (1) and (2). - gradient: See (1) and (2). - reg_method: Given the metric, gradient and lambda the regularization method must return - ``x_lambda`` - see (2). - lambda1: Left starting point for L-curve corner search. - lambda4: Right starting point for L-curve corner search. - tol: Termination threshold. - - Returns: - Regularization coefficient which is the solution to the regularization inverse problem. - """ - - def _get_curvature(x_lambda: List) -> float: - """Calculate Menger curvature - - Menger, K. (1930). Untersuchungen ̈uber Allgemeine Metrik. Math. Ann.,103(1), 466–501 - - Args: - ``x_lambda: [[x_lambdaj], [x_lambdak], [x_lambdal]]`` - ``lambdaj < lambdak < lambdal`` - - Returns: - Menger Curvature - - """ - eps = [] - eta = [] - for x in x_lambda: - try: - eps.append(np.log(np.linalg.norm(np.matmul(metric, x) - gradient) ** 2)) - except ValueError: - eps.append( - np.log(np.linalg.norm(np.matmul(metric, np.transpose(x)) - gradient) ** 2) - ) - eta.append(np.log(max(np.linalg.norm(x) ** 2, ETOL))) - p_temp = 1 - c_k = 0 - for i in range(3): - p_temp *= (eps[np.mod(i + 1, 3)] - eps[i]) ** 2 + ( - eta[np.mod(i + 1, 3)] - eta[i] - ) ** 2 - c_k += eps[i] * eta[np.mod(i + 1, 3)] - eps[np.mod(i + 1, 3)] * eta[i] - c_k = 2 * c_k / max(1e-4, np.sqrt(p_temp)) - return c_k - - def get_lambda2_lambda3(lambda1, lambda4): - gold_sec = (1 + np.sqrt(5)) / 2.0 - lambda2 = 10 ** ((np.log10(lambda4) + np.log10(lambda1) * gold_sec) / (1 + gold_sec)) - lambda3 = 10 ** (np.log10(lambda1) + np.log10(lambda4) - np.log10(lambda2)) - return lambda2, lambda3 - - lambda2, lambda3 = get_lambda2_lambda3(lambda1, lambda4) - lambda_ = [lambda1, lambda2, lambda3, lambda4] - x_lambda = [] - for lam in lambda_: - x_lambda.append(reg_method(metric, gradient, lam)) - counter = 0 - while (lambda_[3] - lambda_[0]) / lambda_[3] >= tol: - counter += 1 - c_2 = _get_curvature(x_lambda[:-1]) - c_3 = _get_curvature(x_lambda[1:]) - while c_3 < 0: - lambda_[3] = lambda_[2] - x_lambda[3] = x_lambda[2] - lambda_[2] = lambda_[1] - x_lambda[2] = x_lambda[1] - lambda2, _ = get_lambda2_lambda3(lambda_[0], lambda_[3]) - lambda_[1] = lambda2 - x_lambda[1] = reg_method(metric, gradient, lambda_[1]) - c_3 = _get_curvature(x_lambda[1:]) - - if c_2 > c_3: - lambda_mc = lambda_[1] - x_mc = x_lambda[1] - lambda_[3] = lambda_[2] - x_lambda[3] = x_lambda[2] - lambda_[2] = lambda_[1] - x_lambda[2] = x_lambda[1] - lambda2, _ = get_lambda2_lambda3(lambda_[0], lambda_[3]) - lambda_[1] = lambda2 - x_lambda[1] = reg_method(metric, gradient, lambda_[1]) - else: - lambda_mc = lambda_[2] - x_mc = x_lambda[2] - lambda_[0] = lambda_[1] - x_lambda[0] = x_lambda[1] - lambda_[1] = lambda_[2] - x_lambda[1] = x_lambda[2] - _, lambda3 = get_lambda2_lambda3(lambda_[0], lambda_[3]) - lambda_[2] = lambda3 - x_lambda[2] = reg_method(metric, gradient, lambda_[2]) - return lambda_mc, x_mc - - @staticmethod - @_optionals.HAS_SKLEARN.require_in_call - def _ridge( - metric: np.ndarray, - gradient: np.ndarray, - lambda_: float = 1.0, - lambda1: float = 1e-4, - lambda4: float = 1e-1, - tol_search: float = 1e-8, - fit_intercept: bool = True, - normalize: bool = False, - copy_a: bool = True, - max_iter: int = 1000, - tol: float = 0.0001, - solver: str = "auto", - random_state: Optional[int] = None, - ) -> Tuple[float, np.ndarray]: - """ - Ridge Regression with automatic search for a good regularization term lambda - x_lambda = arg min{||Ax-C||^2 + lambda*||x||_2^2} (3) - `Scikit Learn Ridge Regression - ` - - Args: - metric: See (1) and (2). - gradient: See (1) and (2). - lambda_ : regularization parameter used if auto_search = False - lambda1: left starting point for L-curve corner search - lambda4: right starting point for L-curve corner search - tol_search: termination threshold for regularization parameter search - fit_intercept: if True calculate intercept - normalize: ignored if fit_intercept=False, if True normalize A for regression - copy_a: if True A is copied, else overwritten - max_iter: max. number of iterations if solver is CG - tol: precision of the regression solution - solver: solver {‘auto’, ‘svd’, ‘cholesky’, ‘lsqr’, ‘sparse_cg’, ‘sag’, ‘saga’} - random_state: seed for the pseudo random number generator used when data is shuffled - - Returns: - regularization coefficient, solution to the regularization inverse problem - - Raises: - MissingOptionalLibraryError: scikit-learn not installed - - """ - from sklearn.linear_model import Ridge - from sklearn.preprocessing import StandardScaler - - reg = Ridge( - alpha=lambda_, - fit_intercept=fit_intercept, - copy_X=copy_a, - max_iter=max_iter, - tol=tol, - solver=solver, - random_state=random_state, - ) - - def reg_method(a, c, alpha): - reg.set_params(alpha=alpha) - if normalize: - reg.fit(StandardScaler().fit_transform(a), c) - else: - reg.fit(a, c) - return reg.coef_ - - lambda_mc, x_mc = NaturalGradient._reg_term_search( - metric, gradient, reg_method, lambda1=lambda1, lambda4=lambda4, tol=tol_search - ) - return lambda_mc, np.transpose(x_mc) - - @staticmethod - @_optionals.HAS_SKLEARN.require_in_call - def _lasso( - metric: np.ndarray, - gradient: np.ndarray, - lambda_: float = 1.0, - lambda1: float = 1e-4, - lambda4: float = 1e-1, - tol_search: float = 1e-8, - fit_intercept: bool = True, - normalize: bool = False, - precompute: Union[bool, Iterable] = False, - copy_a: bool = True, - max_iter: int = 1000, - tol: float = 0.0001, - warm_start: bool = False, - positive: bool = False, - random_state: Optional[int] = None, - selection: str = "random", - ) -> Tuple[float, np.ndarray]: - """ - Lasso Regression with automatic search for a good regularization term lambda - x_lambda = arg min{||Ax-C||^2/(2*n_samples) + lambda*||x||_1} (4) - `Scikit Learn Lasso Regression - ` - - Args: - metric: Matrix of size mxn. - gradient: Vector of size m. - lambda_ : regularization parameter used if auto_search = False - lambda1: left starting point for L-curve corner search - lambda4: right starting point for L-curve corner search - tol_search: termination threshold for regularization parameter search - fit_intercept: if True calculate intercept - normalize: ignored if fit_intercept=False, if True normalize A for regression - precompute: If True compute and use Gram matrix to speed up calculations. - Gram matrix can also be given explicitly - copy_a: if True A is copied, else overwritten - max_iter: max. number of iterations if solver is CG - tol: precision of the regression solution - warm_start: if True reuse solution from previous fit as initialization - positive: if True force positive coefficients - random_state: seed for the pseudo random number generator used when data is shuffled - selection: {'cyclic', 'random'} - - Returns: - regularization coefficient, solution to the regularization inverse problem - - Raises: - MissingOptionalLibraryError: scikit-learn not installed - - """ - from sklearn.linear_model import Lasso - from sklearn.preprocessing import StandardScaler - - reg = Lasso( - alpha=lambda_, - fit_intercept=fit_intercept, - precompute=precompute, - copy_X=copy_a, - max_iter=max_iter, - tol=tol, - warm_start=warm_start, - positive=positive, - random_state=random_state, - selection=selection, - ) - - def reg_method(a, c, alpha): - reg.set_params(alpha=alpha) - if normalize: - reg.fit(StandardScaler().fit_transform(a), c) - else: - reg.fit(a, c) - return reg.coef_ - - lambda_mc, x_mc = NaturalGradient._reg_term_search( - metric, gradient, reg_method, lambda1=lambda1, lambda4=lambda4, tol=tol_search - ) - - return lambda_mc, x_mc - - @staticmethod - def _regularized_sle_solver( - metric: np.ndarray, - gradient: np.ndarray, - regularization: str = "perturb_diag", - lambda1: float = 1e-3, - lambda4: float = 1.0, - alpha: float = 0.0, - tol_norm_x: Tuple[float, float] = (1e-8, 5.0), - tol_cond_a: float = 1000.0, - ) -> np.ndarray: - """ - Solve a linear system of equations with a regularization method and automatic lambda fitting - - Args: - metric: Matrix of size mxn. - gradient: Vector of size m. - regularization: Regularization scheme to be used: 'ridge', 'lasso', - 'perturb_diag_elements' or 'perturb_diag' - lambda1: left starting point for L-curve corner search (for 'ridge' and 'lasso') - lambda4: right starting point for L-curve corner search (for 'ridge' and 'lasso') - alpha: perturbation coefficient for 'perturb_diag_elements' and 'perturb_diag' - tol_norm_x: tolerance for the norm of x - tol_cond_a: tolerance for the condition number of A - - Returns: - solution to the regularized system of linear equations - - """ - if regularization == "ridge": - _, x = NaturalGradient._ridge(metric, gradient, lambda1=lambda1) - elif regularization == "lasso": - _, x = NaturalGradient._lasso(metric, gradient, lambda1=lambda1) - elif regularization == "perturb_diag_elements": - alpha = 1e-7 - while np.linalg.cond(metric + alpha * np.diag(metric)) > tol_cond_a: - alpha *= 10 - # include perturbation in A to avoid singularity - x, _, _, _ = np.linalg.lstsq(metric + alpha * np.diag(metric), gradient, rcond=None) - elif regularization == "perturb_diag": - alpha = 1e-7 - while np.linalg.cond(metric + alpha * np.eye(len(gradient))) > tol_cond_a: - alpha *= 10 - # include perturbation in A to avoid singularity - x, _, _, _ = np.linalg.lstsq( - metric + alpha * np.eye(len(gradient)), gradient, rcond=None - ) - else: - # include perturbation in A to avoid singularity - x, _, _, _ = np.linalg.lstsq(metric, gradient, rcond=None) - - if np.linalg.norm(x) > tol_norm_x[1] or np.linalg.norm(x) < tol_norm_x[0]: - if regularization == "ridge": - lambda1 = lambda1 / 10.0 - _, x = NaturalGradient._ridge(metric, gradient, lambda1=lambda1, lambda4=lambda4) - elif regularization == "lasso": - lambda1 = lambda1 / 10.0 - _, x = NaturalGradient._lasso(metric, gradient, lambda1=lambda1) - elif regularization == "perturb_diag_elements": - while np.linalg.cond(metric + alpha * np.diag(metric)) > tol_cond_a: - if alpha == 0: - alpha = 1e-7 - else: - alpha *= 10 - # include perturbation in A to avoid singularity - x, _, _, _ = np.linalg.lstsq(metric + alpha * np.diag(metric), gradient, rcond=None) - else: - if alpha == 0: - alpha = 1e-7 - else: - alpha *= 10 - while np.linalg.cond(metric + alpha * np.eye(len(gradient))) > tol_cond_a: - # include perturbation in A to avoid singularity - x, _, _, _ = np.linalg.lstsq( - metric + alpha * np.eye(len(gradient)), gradient, rcond=None - ) - alpha *= 10 - return x diff --git a/qiskit/opflow/gradients/qfi.py b/qiskit/opflow/gradients/qfi.py deleted file mode 100644 index ca90bc74165a..000000000000 --- a/qiskit/opflow/gradients/qfi.py +++ /dev/null @@ -1,75 +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. - -"""The module for Quantum the Fisher Information.""" - -from typing import List, Union, Optional - -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.circuit._utils import sort_parameters -from qiskit.utils.deprecation import deprecate_func -from ..list_ops.list_op import ListOp -from ..expectations.pauli_expectation import PauliExpectation -from ..state_fns.circuit_state_fn import CircuitStateFn -from .qfi_base import QFIBase -from .circuit_qfis import CircuitQFI - - -class QFI(QFIBase): - r"""Deprecated: Compute the Quantum Fisher Information (QFI). - - Computes the QFI given a pure, parameterized quantum state, where QFI is: - - .. math:: - - \mathrm{QFI}_{kl}= 4 \mathrm{Re}[\langle \partial_k \psi | \partial_l \psi \rangle - − \langle\partial_k \psi | \psi \rangle \langle\psi | \partial_l \psi \rangle]. - - """ - - @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"): - super().__init__(qfi_method=qfi_method) - - def convert( - self, - operator: CircuitStateFn, - params: Optional[ - Union[ParameterExpression, ParameterVector, List[ParameterExpression]] - ] = None, - ) -> ListOp: - r""" - Args: - operator: The operator corresponding to the quantum state \|ψ(ω)〉for which we compute - the QFI - params: The parameters we are computing the QFI wrt: ω - If not explicitly passed, they are inferred from the operator and sorted by name. - - Returns: - ListOp[ListOp] where the operator at position k,l corresponds to QFI_kl - - Raises: - ValueError: If operator is not parameterized. - """ - if len(operator.parameters) == 0: - raise ValueError("The operator we are taking the gradient of is not parameterized!") - - expec_op = PauliExpectation(group_paulis=False).convert(operator).reduce() - cleaned_op = self._factor_coeffs_out_of_composed_op(expec_op) - - if params is None: - params = sort_parameters(operator.parameters) - return self.qfi_method.convert(cleaned_op, params) diff --git a/qiskit/opflow/gradients/qfi_base.py b/qiskit/opflow/gradients/qfi_base.py deleted file mode 100644 index b36606f3a440..000000000000 --- a/qiskit/opflow/gradients/qfi_base.py +++ /dev/null @@ -1,79 +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. - -"""The module for Quantum the Fisher Information.""" - -from typing import Union - -from qiskit.utils.deprecation import deprecate_func -from .derivative_base import DerivativeBase -from .circuit_qfis import CircuitQFI - - -class QFIBase(DerivativeBase): - - r"""Deprecated: Base class for Quantum Fisher Information (QFI). - - Compute the Quantum Fisher Information (QFI) given a pure, parameterized quantum state. - - The QFI is: - - [QFI]kl= Re[〈∂kψ|∂lψ〉−〈∂kψ|ψ〉〈ψ|∂lψ〉] * 4. - """ - - @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"): - r""" - Args: - qfi_method: The method used to compute the state/probability gradient. Can be either - a :class:`CircuitQFI` instance or one of the following pre-defined strings - ``'lin_comb_full'``, ``'overlap_diag'``` or ``'overlap_block_diag'```. - Raises: - ValueError: if ``qfi_method`` is neither a ``CircuitQFI`` object nor one of the - predefined strings. - """ - super().__init__() - if isinstance(qfi_method, CircuitQFI): - self._qfi_method = qfi_method - - elif qfi_method == "lin_comb_full": - from .circuit_qfis import LinCombFull - - self._qfi_method = LinCombFull() - elif qfi_method == "overlap_block_diag": - from .circuit_qfis import OverlapBlockDiag - - self._qfi_method = OverlapBlockDiag() - elif qfi_method == "overlap_diag": - from .circuit_qfis import OverlapDiag - - self._qfi_method = OverlapDiag() - else: - raise ValueError( - "Unrecognized input provided for `qfi_method`. Please provide" - " a CircuitQFI object or one of the pre-defined string" - " arguments: {'lin_comb_full', 'overlap_diag', " - "'overlap_block_diag'}. " - ) - - @property - def qfi_method(self) -> CircuitQFI: - """Returns ``CircuitQFI``. - - Returns: - ``CircuitQFI``. - """ - return self._qfi_method diff --git a/qiskit/opflow/list_ops/__init__.py b/qiskit/opflow/list_ops/__init__.py deleted file mode 100644 index b4e4a45b3d72..000000000000 --- a/qiskit/opflow/list_ops/__init__.py +++ /dev/null @@ -1,96 +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. - -r""" -List Operators (:mod:`qiskit.opflow.list_ops`) -============================================== - -.. currentmodule:: qiskit.opflow.list_ops - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -List Operators are classes for storing and manipulating lists of Operators, State functions, -or Measurements, and include some rule or ``combo_fn`` defining how the Operator functions of the -list constituents should be combined to form to cumulative Operator function of the -:class:`ListOp`. For example, a :class:`SummedOp` has an addition-based ``combo_fn``, so once -the Operators in its list are evaluated against some bitstring to produce a list of results, -we know to add up those results to produce the final result of the :class:`SummedOp`'s evaluation. -In theory, this ``combo_fn`` can be any function over classical complex values, but for convenience -we've chosen for them to be defined over NumPy arrays and values. This way, large numbers of -evaluations, such as after calling :meth:`~ListOp.to_matrix` on the list constituents, -can be efficiently combined. While the combination function is defined over classical values, -it should be understood as the operation by which each Operators' underlying function is -combined to form the underlying Operator function of the :class:`ListOp`. In this way, the -:mod:`.list_ops` are the basis for constructing large and sophisticated Operators, -State Functions, and Measurements. - - -The base :class:`ListOp` class is particularly interesting, as its ``combo_fn`` is "the identity -list Operation". Meaning, if we understand the ``combo_fn`` as a function from a list of complex -values to some output, one such function is returning the list as\-is. This is powerful for -constructing compact hierarchical Operators which return many measurements in multiple -dimensional lists. For example, if we want to estimate the gradient of some Observable -measurement with respect to some parameters in the State function, we can construct separate -evaluation Operators for each parameter's gradient which we must keep track of ourselves in a -list, or we can construct a single :class:`ListOp` containing the evaluation Operators for each -parameter, so the :meth:`~ListOp.eval` function returns the full gradient vector. Another excellent -example of this power is constructing a Quantum kernel matrix: - -.. code-block:: - - data_sfn_list_op = ListOp(data_circuit_state_fns) - qkernel_op_circuits = ~data_sfn_list_op @ data_sfn_list_op - qkernel_sampled = CircuitSampler(backend).convert(qkernel_op_circuits) - qkernel_sampled.eval() - -This will return the two dimensional Quantum kernel matrix, where each element is the inner product -of some pair of the data State functions, or in other terms, a measurement of one data -:class:`~.state_fns.CircuitStateFn` by another. - -You'll encounter the :class:`ListOp` subclasses (:class:`SummedOp`, :class:`ComposedOp`, -or :class:`TensoredOp`) more often as lazy results of Operator construction operations than as -something you need to explicitly construct. Any time we don't know how to efficiently add, -compose, or tensor two :mod:`.primitive_ops` or :mod:`.state_fns` together, they're returned in -a :class:`SummedOp`, :class:`ComposedOp`, or :class:`TensoredOp`, respectively, so we can still work -with their combined function and perhaps convert them into an efficiently combine-able format later. - -Note: - Combination functions do not always behave predictably, and you must understand the - conversions you're making when you working with :mod:`.list_ops`. Most notably - sampling a sum - of two circuits on Quantum hardware does not incorporate interference between the - wavefunctions! In this case, we're sending our State functions through a depolarizing channel - before adding them, rather than adding them directly before the measurement. - -List Operators --------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - ListOp - ComposedOp - SummedOp - TensoredOp - -""" - -from .list_op import ListOp -from .summed_op import SummedOp -from .composed_op import ComposedOp -from .tensored_op import TensoredOp - -__all__ = ["ListOp", "SummedOp", "TensoredOp", "ComposedOp"] diff --git a/qiskit/opflow/list_ops/composed_op.py b/qiskit/opflow/list_ops/composed_op.py deleted file mode 100644 index 6f569e718ad9..000000000000 --- a/qiskit/opflow/list_ops/composed_op.py +++ /dev/null @@ -1,198 +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. - -"""ComposedOp Class""" - -from functools import partial, reduce -from typing import List, Optional, Union, cast, Dict - -from numbers import Number -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class ComposedOp(ListOp): - """Deprecated: A class for lazily representing compositions of Operators. Often Operators cannot be - efficiently composed with one another, but may be manipulated further so that they can be - composed later. This class holds logic to indicate that the Operators in ``oplist`` are meant to - be composed, and therefore if they reach a point in which they can be, such as after - conversion to QuantumCircuits or matrices, they can be reduced by composition.""" - - @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, - oplist: List[OperatorBase], - coeff: Union[complex, ParameterExpression] = 1.0, - abelian: bool = False, - ) -> None: - """ - Args: - oplist: The Operators being composed. - coeff: A coefficient multiplying the operator - abelian: Indicates whether the Operators in ``oplist`` are known to mutually commute. - """ - super().__init__(oplist, combo_fn=partial(reduce, np.dot), coeff=coeff, abelian=abelian) - - @property - def num_qubits(self) -> int: - return self.oplist[0].num_qubits - - @property - def distributive(self) -> bool: - return False - - @property - def settings(self) -> Dict: - """Return settings.""" - return {"oplist": self._oplist, "coeff": self._coeff, "abelian": self._abelian} - - # TODO take advantage of the mixed product property, tensorpower each element in the composition - # def tensorpower(self, other): - # """ Tensor product with Self Multiple Times """ - # raise NotImplementedError - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - - mat = self.coeff * reduce( - np.dot, [np.asarray(op.to_matrix(massive=massive)) for op in self.oplist] - ) - - # Note: As ComposedOp has a combo function of inner product we can end up here not with - # a matrix (array) but a scalar. In which case we make a single element array of it. - if isinstance(mat, Number): - mat = [mat] - - return np.asarray(mat, dtype=complex) - - def to_circuit(self) -> QuantumCircuit: - """Returns the quantum circuit, representing the composed operator. - - Returns: - The circuit representation of the composed operator. - - Raises: - OpflowError: for operators where a single underlying circuit can not be obtained. - """ - # pylint: disable=cyclic-import - from ..state_fns.circuit_state_fn import CircuitStateFn - from ..primitive_ops.primitive_op import PrimitiveOp - - circuit_op = self.to_circuit_op() - if isinstance(circuit_op, (PrimitiveOp, CircuitStateFn)): - return circuit_op.to_circuit() - raise OpflowError( - "Conversion to_circuit supported only for operators, where a single " - "underlying circuit can be produced." - ) - - def adjoint(self) -> "ComposedOp": - return ComposedOp([op.adjoint() for op in reversed(self.oplist)], coeff=self.coeff) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(ComposedOp, new_self) - - if front: - return other.compose(new_self) - # Try composing with last element in list - if isinstance(other, ComposedOp): - return ComposedOp(new_self.oplist + other.oplist, coeff=new_self.coeff * other.coeff) - - # Try composing with last element of oplist. We only try - # this if that last element isn't itself an - # ComposedOp, so we can tell whether composing the - # two elements directly worked. If it doesn't, - # continue to the final return statement below, appending other to the oplist. - if not isinstance(new_self.oplist[-1], ComposedOp): - comp_with_last = new_self.oplist[-1].compose(other) - # Attempt successful - if not isinstance(comp_with_last, ComposedOp): - new_oplist = new_self.oplist[0:-1] + [comp_with_last] - return ComposedOp(new_oplist, coeff=new_self.coeff) - - return ComposedOp(new_self.oplist + [other], coeff=new_self.coeff) - - def eval( - self, front: Optional[Union[str, dict, np.ndarray, OperatorBase, Statevector]] = None - ) -> Union[OperatorBase, complex]: - if self._is_empty(): - return 0.0 - - # pylint: disable=cyclic-import - from ..state_fns.state_fn import StateFn - - def tree_recursive_eval(r, l_arg): - if isinstance(r, list): - return [tree_recursive_eval(r_op, l_arg) for r_op in r] - else: - return l_arg.eval(r) - - eval_list = self.oplist.copy() - # Only one op needs to be multiplied, so just multiply the first. - eval_list[0] = eval_list[0] * self.coeff # type: ignore - if front and isinstance(front, OperatorBase): - eval_list = eval_list + [front] - elif front: - eval_list = [StateFn(front, is_measurement=True)] + eval_list # type: ignore - - return reduce(tree_recursive_eval, reversed(eval_list)) - - # Try collapsing list or trees of compositions into a single . - def non_distributive_reduce(self) -> OperatorBase: - """Reduce without attempting to expand all distributive compositions. - - Returns: - The reduced Operator. - """ - reduced_ops = [op.reduce() for op in self.oplist] - reduced_ops = reduce(lambda x, y: x.compose(y), reduced_ops) * self.coeff - if isinstance(reduced_ops, ComposedOp) and len(reduced_ops.oplist) > 1: - return reduced_ops - else: - return reduced_ops[0] - - def reduce(self) -> OperatorBase: - reduced_ops = [op.reduce() for op in self.oplist] - if len(reduced_ops) == 0: - return self.__class__([], coeff=self.coeff, abelian=self.abelian) - - def distribute_compose(l_arg, r): - if isinstance(l_arg, ListOp) and l_arg.distributive: - # Either ListOp or SummedOp, returns correct type - return l_arg.__class__( - [distribute_compose(l_op * l_arg.coeff, r) for l_op in l_arg.oplist] - ) - if isinstance(r, ListOp) and r.distributive: - return r.__class__([distribute_compose(l_arg, r_op * r.coeff) for r_op in r.oplist]) - else: - return l_arg.compose(r) - - reduced_ops = reduce(distribute_compose, reduced_ops) * self.coeff - if isinstance(reduced_ops, ListOp) and len(reduced_ops.oplist) == 1: - return reduced_ops.oplist[0] - else: - return cast(OperatorBase, reduced_ops) diff --git a/qiskit/opflow/list_ops/list_op.py b/qiskit/opflow/list_ops/list_op.py deleted file mode 100644 index 1047cc68fb92..000000000000 --- a/qiskit/opflow/list_ops/list_op.py +++ /dev/null @@ -1,641 +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. - -"""ListOp Operator Class""" - -from functools import reduce -from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Sequence, Union, cast - -import numpy as np -from scipy.sparse import spmatrix - -from qiskit.circuit import ParameterExpression, QuantumCircuit -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.operator_base import OperatorBase -from qiskit.quantum_info import Statevector -from qiskit.utils import arithmetic -from qiskit.utils.deprecation import deprecate_func - - -class ListOp(OperatorBase): - """ - Deprecated: A Class for manipulating List Operators, and parent class to ``SummedOp``, - ``ComposedOp`` and ``TensoredOp``. - - List Operators are classes for storing and manipulating lists of Operators, State functions, - or Measurements, and include some rule or ``combo_fn`` defining how the Operator functions - of the list constituents should be combined to form to cumulative Operator function of the - ``ListOp``. For example, a ``SummedOp`` has an addition-based ``combo_fn``, so once the - Operators in its list are evaluated against some bitstring to produce a list of results, - we know to add up those results to produce the final result of the ``SummedOp``'s - evaluation. In theory, this ``combo_fn`` can be any function over classical complex values, - but for convenience we've chosen for them to be defined over NumPy arrays and values. This way, - large numbers of evaluations, such as after calling ``to_matrix`` on the list constituents, - can be efficiently combined. While the combination function is defined over classical - values, it should be understood as the operation by which each Operators' underlying - function is combined to form the underlying Operator function of the ``ListOp``. In this - way, the ``ListOps`` are the basis for constructing large and sophisticated Operators, - State Functions, and Measurements. - - The base ``ListOp`` class is particularly interesting, as its ``combo_fn`` is "the identity - list Operation". Meaning, if we understand the ``combo_fn`` as a function from a list of - complex values to some output, one such function is returning the list as-is. This is - powerful for constructing compact hierarchical Operators which return many measurements in - multiple dimensional lists. - """ - - @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, - oplist: Sequence[OperatorBase], - combo_fn: Optional[Callable] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - abelian: bool = False, - grad_combo_fn: Optional[Callable] = None, - ) -> None: - """ - Args: - oplist: The list of ``OperatorBases`` defining this Operator's underlying function. - combo_fn: The recombination function to combine classical results of the - ``oplist`` Operators' eval functions (e.g. sum). Default is lambda x: x. - coeff: A coefficient multiplying the operator - abelian: Indicates whether the Operators in ``oplist`` are known to mutually commute. - grad_combo_fn: The gradient of recombination function. If None, the gradient will - be computed automatically. - Note that the default "recombination function" lambda above is essentially the - identity - it accepts the list of values, and returns them in a list. - """ - super().__init__() - self._oplist = self._check_input_types(oplist) - self._combo_fn = combo_fn - self._coeff = coeff - self._abelian = abelian - self._grad_combo_fn = grad_combo_fn - - def _check_input_types(self, oplist): - if all(isinstance(x, OperatorBase) for x in oplist): - return list(oplist) - else: - badval = next(x for x in oplist if not isinstance(x, OperatorBase)) - raise TypeError(f"ListOp expecting objects of type OperatorBase, got {badval}") - - def _state( - self, - coeff: Optional[Union[complex, ParameterExpression]] = None, - combo_fn: Optional[Callable] = None, - abelian: Optional[bool] = None, - grad_combo_fn: Optional[Callable] = None, - ) -> Dict: - return { - "coeff": coeff if coeff is not None else self.coeff, - "combo_fn": combo_fn if combo_fn is not None else self.combo_fn, - "abelian": abelian if abelian is not None else self.abelian, - "grad_combo_fn": grad_combo_fn if grad_combo_fn is not None else self.grad_combo_fn, - } - - @property - def settings(self) -> Dict: - """Return settings.""" - return { - "oplist": self._oplist, - "combo_fn": self._combo_fn, - "coeff": self._coeff, - "abelian": self._abelian, - "grad_combo_fn": self._grad_combo_fn, - } - - @property - def oplist(self) -> List[OperatorBase]: - """The list of ``OperatorBases`` defining the underlying function of this - Operator. - - Returns: - The Operators defining the ListOp - """ - return self._oplist - - @staticmethod - def default_combo_fn(x: Any) -> Any: - """ListOp default combo function i.e. lambda x: x""" - return x - - @property - def combo_fn(self) -> Callable: - """The function defining how to combine ``oplist`` (or Numbers, or NumPy arrays) to - produce the Operator's underlying function. For example, SummedOp's combination function - is to add all of the Operators in ``oplist``. - - Returns: - The combination function. - """ - if self._combo_fn is None: - return ListOp.default_combo_fn - return self._combo_fn - - @property - def grad_combo_fn(self) -> Optional[Callable]: - """The gradient of ``combo_fn``.""" - return self._grad_combo_fn - - @property - def abelian(self) -> bool: - """Whether the Operators in ``oplist`` are known to commute with one another. - - Returns: - A bool indicating whether the ``oplist`` is Abelian. - """ - return self._abelian - - @property - def distributive(self) -> bool: - """Indicates whether the ListOp or subclass is distributive under composition. - ListOp and SummedOp are, meaning that (opv @ op) = (opv[0] @ op + opv[1] @ op) - (using plus for SummedOp, list for ListOp, etc.), while ComposedOp and TensoredOp - do not behave this way. - - Returns: - A bool indicating whether the ListOp is distributive under composition. - """ - return True - - @property - def coeff(self) -> Union[complex, ParameterExpression]: - """The scalar coefficient multiplying the Operator. - - Returns: - The coefficient. - """ - return self._coeff - - @property - def coeffs(self) -> List[Union[complex, ParameterExpression]]: - """Return a list of the coefficients of the operators listed. - Raises exception for nested Listops. - """ - if any(isinstance(op, ListOp) for op in self.oplist): - raise TypeError("Coefficients are not returned for nested ListOps.") - return [self.coeff * op.coeff for op in self.oplist] - - def primitive_strings(self) -> Set[str]: - return reduce(set.union, [op.primitive_strings() for op in self.oplist]) - - @property - def num_qubits(self) -> int: - num_qubits0 = self.oplist[0].num_qubits - if not all(num_qubits0 == op.num_qubits for op in self.oplist): - raise ValueError("Operators in ListOp have differing numbers of qubits.") - return num_qubits0 - - def add(self, other: OperatorBase) -> "ListOp": - if self == other: - return self.mul(2.0) - - # Avoid circular dependency - # pylint: disable=cyclic-import - from .summed_op import SummedOp - - return SummedOp([self, other]) - - def adjoint(self) -> "ListOp": - # TODO do this lazily? Basically rebuilds the entire tree, and ops and adjoints almost - # always come in pairs, so an AdjointOp holding a reference could save copying. - if self.__class__ == ListOp: - return ListOp( - [op.adjoint() for op in self.oplist], **self._state(coeff=self.coeff.conjugate()) - ) # coeff is conjugated - return self.__class__( - [op.adjoint() for op in self.oplist], coeff=self.coeff.conjugate(), abelian=self.abelian - ) - - def traverse( - self, convert_fn: Callable, coeff: Optional[Union[complex, ParameterExpression]] = None - ) -> "ListOp": - """Apply the convert_fn to each node in the oplist. - - Args: - convert_fn: The function to apply to the internal OperatorBase. - coeff: A coefficient to multiply by after applying convert_fn. - If it is None, self.coeff is used instead. - - Returns: - The converted ListOp. - """ - if coeff is None: - coeff = self.coeff - - if self.__class__ == ListOp: - return ListOp([convert_fn(op) for op in self.oplist], **self._state(coeff=coeff)) - return self.__class__( - [convert_fn(op) for op in self.oplist], coeff=coeff, abelian=self.abelian - ) - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, type(self)) or not len(self.oplist) == len(other.oplist): - return False - # Note, ordering matters here (i.e. different list orders will return False) - return self.coeff == other.coeff and all( - op1 == op2 for op1, op2 in zip(self.oplist, other.oplist) - ) - - # We need to do this because otherwise Numpy takes over scalar multiplication and wrecks it if - # isinstance(scalar, np.number) - this started happening when we added __get_item__(). - __array_priority__ = 10000 - - def mul(self, scalar: Union[complex, ParameterExpression]) -> "ListOp": - if not isinstance(scalar, (int, float, complex, ParameterExpression)): - raise ValueError( - "Operators can only be scalar multiplied by float or complex, not " - "{} of type {}.".format(scalar, type(scalar)) - ) - if self.__class__ == ListOp: - return ListOp(self.oplist, **self._state(coeff=scalar * self.coeff)) - return self.__class__(self.oplist, coeff=scalar * self.coeff, abelian=self.abelian) - - def tensor(self, other: OperatorBase) -> OperatorBase: - # Avoid circular dependency - # pylint: disable=cyclic-import - from .tensored_op import TensoredOp - - return TensoredOp([self, other]) - - def tensorpower(self, other: int) -> Union[OperatorBase, int]: - # Hack to make op1^(op2^0) work as intended. - if other == 0: - return 1 - if not isinstance(other, int) or other <= 0: - raise TypeError("Tensorpower can only take positive int arguments") - - # Avoid circular dependency - # pylint: disable=cyclic-import - from .tensored_op import TensoredOp - - return TensoredOp([self] * other) - - def _expand_dim(self, num_qubits: int) -> "ListOp": - oplist = [ - op._expand_dim(num_qubits + self.num_qubits - op.num_qubits) for op in self.oplist - ] - return ListOp(oplist, **self._state()) - - def permute(self, permutation: List[int]) -> "OperatorBase": - """Permute the qubits of the operator. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j should be permuted to position permutation[j]. - - Returns: - A new ListOp representing the permuted operator. - - Raises: - OpflowError: if indices do not define a new index for each qubit. - """ - new_self = self - circuit_size = max(permutation) + 1 - - try: - if self.num_qubits != len(permutation): - raise OpflowError("New index must be defined for each qubit of the operator.") - except ValueError: - raise OpflowError( - "Permute is only possible if all operators in the ListOp have the " - "same number of qubits." - ) from ValueError - if self.num_qubits < circuit_size: - # pad the operator with identities - new_self = self._expand_dim(circuit_size - self.num_qubits) - qc = QuantumCircuit(circuit_size) - # extend the indices to match the size of the circuit - permutation = ( - list(filter(lambda x: x not in permutation, range(circuit_size))) + permutation - ) - - # decompose permutation into sequence of transpositions - transpositions = arithmetic.transpositions(permutation) - for trans in transpositions: - qc.swap(trans[0], trans[1]) - - # pylint: disable=cyclic-import - from ..primitive_ops.circuit_op import CircuitOp - - return CircuitOp(qc.reverse_ops()) @ new_self @ CircuitOp(qc) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(ListOp, new_self) - - if front: - return other.compose(new_self) - # Avoid circular dependency - # pylint: disable=cyclic-import - from .composed_op import ComposedOp - - return ComposedOp([new_self, other]) - - def power(self, exponent: int) -> OperatorBase: - if not isinstance(exponent, int) or exponent <= 0: - raise TypeError("power can only take positive int arguments") - - # Avoid circular dependency - # pylint: disable=cyclic-import - from .composed_op import ComposedOp - - return ComposedOp([self] * exponent) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - - # Combination function must be able to handle classical values. - # Note: this can end up, when we have list operators containing other list operators, as a - # ragged array and numpy 1.19 raises a deprecation warning unless this is explicitly - # done as object type now - was implicit before. - mat = self.combo_fn( - np.asarray( - [op.to_matrix(massive=massive) * self.coeff for op in self.oplist], dtype=object - ) - ) - return np.asarray(mat, dtype=complex) - - def to_spmatrix(self) -> Union[spmatrix, List[spmatrix]]: - """Returns SciPy sparse matrix representation of the Operator. - - Returns: - CSR sparse matrix representation of the Operator, or List thereof. - """ - - # Combination function must be able to handle classical values - return self.combo_fn([op.to_spmatrix() for op in self.oplist]) * self.coeff - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - """ - Evaluate the Operator's underlying function, either on a binary string or another Operator. - A square binary Operator can be defined as a function taking a binary function to another - binary function. This method returns the value of that function for a given StateFn or - binary string. For example, ``op.eval('0110').eval('1110')`` can be seen as querying the - Operator's matrix representation by row 6 and column 14, and will return the complex - value at those "indices." Similarly for a StateFn, ``op.eval('1011')`` will return the - complex value at row 11 of the vector representation of the StateFn, as all StateFns are - defined to be evaluated from Zero implicitly (i.e. it is as if ``.eval('0000')`` is already - called implicitly to always "indexing" from column 0). - - ListOp's eval recursively evaluates each Operator in ``oplist``, - and combines the results using the recombination function ``combo_fn``. - - Args: - front: The bitstring, dict of bitstrings (with values being coefficients), or - StateFn to evaluated by the Operator's underlying function. - - Returns: - The output of the ``oplist`` Operators' evaluation function, combined with the - ``combo_fn``. If either self or front contain proper ``ListOps`` (not ListOp - subclasses), the result is an n-dimensional list of complex or StateFn results, - resulting from the recursive evaluation by each OperatorBase in the ListOps. - - Raises: - NotImplementedError: Raised if called for a subclass which is not distributive. - TypeError: Operators with mixed hierarchies, such as a ListOp containing both - PrimitiveOps and ListOps, are not supported. - NotImplementedError: Attempting to call ListOp's eval from a non-distributive subclass. - - """ - # pylint: disable=cyclic-import - from ..state_fns.dict_state_fn import DictStateFn - from ..state_fns.vector_state_fn import VectorStateFn - from ..state_fns.sparse_vector_state_fn import SparseVectorStateFn - - # The below code only works for distributive ListOps, e.g. ListOp and SummedOp - if not self.distributive: - raise NotImplementedError( - "ListOp's eval function is only defined for distributive ListOps." - ) - - evals = [op.eval(front) for op in self.oplist] - - # Handle application of combo_fn for DictStateFn resp VectorStateFn operators - if self._combo_fn is not None: # If not using default. - if ( - all(isinstance(op, DictStateFn) for op in evals) - or all(isinstance(op, VectorStateFn) for op in evals) - or all(isinstance(op, SparseVectorStateFn) for op in evals) - ): - if not all( - op.is_measurement == evals[0].is_measurement for op in evals # type: ignore - ): - raise NotImplementedError( - "Combo_fn not yet supported for mixed measurement " - "and non-measurement StateFns" - ) - result = self.combo_fn(evals) - if isinstance(result, list): - multiplied = self.coeff * np.array(result) - return multiplied.tolist() - return self.coeff * result - - if all(isinstance(op, OperatorBase) for op in evals): - return self.__class__(evals) # type: ignore - elif any(isinstance(op, OperatorBase) for op in evals): - raise TypeError("Cannot handle mixed scalar and Operator eval results.") - else: - result = self.combo_fn(evals) - if isinstance(result, list): - multiplied = self.coeff * np.array(result) - return multiplied.tolist() - return self.coeff * result - - def exp_i(self) -> OperatorBase: - """Return an ``OperatorBase`` equivalent to an exponentiation of self * -i, e^(-i*op).""" - # pylint: disable=unidiomatic-typecheck - if type(self) == ListOp: - return ListOp( - [op.exp_i() for op in self.oplist], **self._state(abelian=False) # type: ignore - ) - - # pylint: disable=cyclic-import - from ..evolutions.evolved_op import EvolvedOp - - return EvolvedOp(self) - - def log_i(self, massive: bool = False) -> OperatorBase: - """Return a ``MatrixOp`` equivalent to log(H)/-i for this operator H. This - function is the effective inverse of exp_i, equivalent to finding the Hermitian - Operator which produces self when exponentiated. For proper ListOps, applies ``log_i`` - to all ops in oplist. - """ - if self.__class__.__name__ == ListOp.__name__: - return ListOp( - [op.log_i(massive=massive) for op in self.oplist], # type: ignore - **self._state(abelian=False), - ) - - return self.to_matrix_op(massive=massive).log_i(massive=massive) - - def __str__(self) -> str: - content_string = ",\n".join([str(op) for op in self.oplist]) - main_string = "{}([\n{}\n])".format( - self.__class__.__name__, self._indent(content_string, indentation=self.INDENTATION) - ) - if self.abelian: - main_string = "Abelian" + main_string - if self.coeff != 1.0: - main_string = f"{self.coeff} * " + main_string - return main_string - - def __repr__(self) -> str: - return "{}({}, coeff={}, abelian={})".format( - self.__class__.__name__, repr(self.oplist), self.coeff, self.abelian - ) - - @property - def parameters(self): - params = set() - for op in self.oplist: - params.update(op.parameters) - if isinstance(self.coeff, ParameterExpression): - params.update(self.coeff.parameters) - return params - - def assign_parameters(self, param_dict: dict) -> OperatorBase: - param_value = self.coeff - if isinstance(self.coeff, ParameterExpression): - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if self.coeff.parameters <= set(unrolled_dict.keys()): - binds = {param: unrolled_dict[param] for param in self.coeff.parameters} - param_value = float(self.coeff.bind(binds)) - return self.traverse(lambda x: x.assign_parameters(param_dict), coeff=param_value) - - def reduce(self) -> OperatorBase: - reduced_ops = [op.reduce() for op in self.oplist] - if self.__class__ == ListOp: - return ListOp(reduced_ops, **self._state()) - return self.__class__(reduced_ops, coeff=self.coeff, abelian=self.abelian) - - def to_matrix_op(self, massive: bool = False) -> "ListOp": - """Returns an equivalent Operator composed of only NumPy-based primitives, such as - ``MatrixOp`` and ``VectorStateFn``.""" - if self.__class__ == ListOp: - return cast( - ListOp, - ListOp( - [op.to_matrix_op(massive=massive) for op in self.oplist], **self._state() - ).reduce(), - ) - return cast( - ListOp, - self.__class__( - [op.to_matrix_op(massive=massive) for op in self.oplist], - coeff=self.coeff, - abelian=self.abelian, - ).reduce(), - ) - - def to_circuit_op(self) -> OperatorBase: - """Returns an equivalent Operator composed of only QuantumCircuit-based primitives, - such as ``CircuitOp`` and ``CircuitStateFn``.""" - # pylint: disable=cyclic-import - from ..state_fns.operator_state_fn import OperatorStateFn - - if self.__class__ == ListOp: - return ListOp( - [ - op.to_circuit_op() if not isinstance(op, OperatorStateFn) else op - for op in self.oplist - ], - **self._state(), - ).reduce() - return self.__class__( - [ - op.to_circuit_op() if not isinstance(op, OperatorStateFn) else op - for op in self.oplist - ], - coeff=self.coeff, - abelian=self.abelian, - ).reduce() - - def to_pauli_op(self, massive: bool = False) -> "ListOp": - """Returns an equivalent Operator composed of only Pauli-based primitives, - such as ``PauliOp``.""" - # pylint: disable=cyclic-import - from ..state_fns.state_fn import StateFn - - if self.__class__ == ListOp: - return ListOp( - [ - op.to_pauli_op(massive=massive) # type: ignore - if not isinstance(op, StateFn) - else op - for op in self.oplist - ], - **self._state(), - ).reduce() - return self.__class__( - [ - op.to_pauli_op(massive=massive) # type: ignore - if not isinstance(op, StateFn) - else op - for op in self.oplist - ], - coeff=self.coeff, - abelian=self.abelian, - ).reduce() - - def _is_empty(self): - return len(self.oplist) == 0 - - # Array operations: - - def __getitem__(self, offset: Union[int, slice]) -> OperatorBase: - """Allows array-indexing style access to the Operators in ``oplist``. - - Args: - offset: The index of ``oplist`` desired. - - Returns: - The ``OperatorBase`` at index ``offset`` of ``oplist``, - or another ListOp with the same properties as this one if offset is a slice. - """ - if isinstance(offset, int): - return self.oplist[offset] - - if self.__class__ == ListOp: - return ListOp(oplist=self._oplist[offset], **self._state()) - - return self.__class__(oplist=self._oplist[offset], coeff=self._coeff, abelian=self._abelian) - - def __iter__(self) -> Iterator: - """Returns an iterator over the operators in ``oplist``. - - Returns: - An iterator over the operators in ``oplist`` - """ - return iter(self.oplist) - - def __len__(self) -> int: - """Length of ``oplist``. - - Returns: - An int equal to the length of ``oplist``. - """ - return len(self.oplist) diff --git a/qiskit/opflow/list_ops/summed_op.py b/qiskit/opflow/list_ops/summed_op.py deleted file mode 100644 index 625445e24318..000000000000 --- a/qiskit/opflow/list_ops/summed_op.py +++ /dev/null @@ -1,251 +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. - -"""SummedOp Class""" - -from typing import List, Union, cast, Dict - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.utils.deprecation import deprecate_func - - -class SummedOp(ListOp): - """Deprecated: A class for lazily representing sums of Operators. Often Operators cannot be - efficiently added to one another, but may be manipulated further so that they can be - later. This class holds logic to indicate that the Operators in ``oplist`` are meant to - be added together, and therefore if they reach a point in which they can be, such as after - evaluation or conversion to matrices, they can be reduced by addition.""" - - @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, - oplist: List[OperatorBase], - coeff: Union[complex, ParameterExpression] = 1.0, - abelian: bool = False, - ) -> None: - """ - Args: - oplist: The Operators being summed. - coeff: A coefficient multiplying the operator - abelian: Indicates whether the Operators in ``oplist`` are known to mutually commute. - """ - super().__init__(oplist, combo_fn=lambda x: np.sum(x, axis=0), coeff=coeff, abelian=abelian) - - @property - def num_qubits(self) -> int: - return self.oplist[0].num_qubits - - @property - def distributive(self) -> bool: - return True - - @property - def settings(self) -> Dict: - """Return settings.""" - return {"oplist": self._oplist, "coeff": self._coeff, "abelian": self._abelian} - - def add(self, other: OperatorBase) -> "SummedOp": - """Return Operator addition of ``self`` and ``other``, overloaded by ``+``. - - Note: - This appends ``other`` to ``self.oplist`` without checking ``other`` is already - included or not. If you want to simplify them, please use :meth:`simplify`. - - Args: - other: An ``OperatorBase`` with the same number of qubits as self, and in the same - 'Operator', 'State function', or 'Measurement' category as self (i.e. the same type - of underlying function). - - Returns: - A ``SummedOp`` equivalent to the sum of self and other. - """ - self_new_ops = ( - self.oplist if self.coeff == 1 else [op.mul(self.coeff) for op in self.oplist] - ) - if isinstance(other, SummedOp): - other_new_ops = ( - other.oplist if other.coeff == 1 else [op.mul(other.coeff) for op in other.oplist] - ) - else: - other_new_ops = [other] - return SummedOp(self_new_ops + other_new_ops) - - def collapse_summands(self) -> "SummedOp": - """Return Operator by simplifying duplicate operators. - - E.g., ``SummedOp([2 * X ^ Y, X ^ Y]).collapse_summands() -> SummedOp([3 * X ^ Y])``. - - Returns: - A simplified ``SummedOp`` equivalent to self. - """ - # pylint: disable=cyclic-import - from ..primitive_ops.primitive_op import PrimitiveOp - - oplist = [] # type: List[OperatorBase] - coeffs = [] # type: List[Union[int, float, complex, ParameterExpression]] - for op in self.oplist: - if isinstance(op, PrimitiveOp): - new_op = PrimitiveOp(op.primitive) - new_coeff = op.coeff * self.coeff - if new_op in oplist: - index = oplist.index(new_op) - coeffs[index] += new_coeff - else: - oplist.append(new_op) - coeffs.append(new_coeff) - else: - if op in oplist: - index = oplist.index(op) - coeffs[index] += self.coeff - else: - oplist.append(op) - coeffs.append(self.coeff) - return SummedOp([op * coeff for op, coeff in zip(oplist, coeffs)]) - - # TODO be smarter about the fact that any two ops in oplist could be evaluated for sum. - def reduce(self) -> OperatorBase: - """Try collapsing list or trees of sums. - - Tries to sum up duplicate operators and reduces the operators - in the sum. - - Returns: - A collapsed version of self, if possible. - """ - if len(self.oplist) == 0: - return SummedOp([], coeff=self.coeff, abelian=self.abelian) - - # reduce constituents - reduced_ops = sum(op.reduce() for op in self.oplist) * self.coeff - - # group duplicate operators - if isinstance(reduced_ops, SummedOp): - reduced_ops = reduced_ops.collapse_summands() - - # pylint: disable=cyclic-import - from ..primitive_ops.pauli_sum_op import PauliSumOp - - if isinstance(reduced_ops, PauliSumOp): - reduced_ops = reduced_ops.reduce() - - if isinstance(reduced_ops, SummedOp) and len(reduced_ops.oplist) == 1: - return reduced_ops.oplist[0] - else: - return cast(OperatorBase, reduced_ops) - - def to_circuit(self) -> QuantumCircuit: - """Returns the quantum circuit, representing the SummedOp. In the first step, - the SummedOp is converted to MatrixOp. This is straightforward for most operators, - but it is not supported for operators containing parameterized PrimitiveOps (in that case, - OpflowError is raised). In the next step, the MatrixOp representation of SummedOp is - converted to circuit. In most cases, if the summands themselves are unitary operators, - the SummedOp itself is non-unitary and can not be converted to circuit. In that case, - ExtensionError is raised in the underlying modules. - - Returns: - The circuit representation of the summed operator. - - Raises: - OpflowError: if SummedOp can not be converted to MatrixOp (e.g. SummedOp is composed of - parameterized PrimitiveOps). - """ - # pylint: disable=cyclic-import - from ..primitive_ops.matrix_op import MatrixOp - - matrix_op = self.to_matrix_op() - if isinstance(matrix_op, MatrixOp): - return matrix_op.to_circuit() - raise OpflowError( - "The SummedOp can not be converted to circuit, because to_matrix_op did " - "not return a MatrixOp." - ) - - def to_matrix_op(self, massive: bool = False) -> "SummedOp": - """Returns an equivalent Operator composed of only NumPy-based primitives, such as - ``MatrixOp`` and ``VectorStateFn``.""" - accum = self.oplist[0].to_matrix_op(massive=massive) - for i in range(1, len(self.oplist)): - accum += self.oplist[i].to_matrix_op(massive=massive) - - return cast(SummedOp, accum * self.coeff) - - def to_pauli_op(self, massive: bool = False) -> "SummedOp": - # pylint: disable=cyclic-import - from ..state_fns.state_fn import StateFn - - pauli_sum = SummedOp( - [ - op.to_pauli_op(massive=massive) # type: ignore - if not isinstance(op, StateFn) - else op - for op in self.oplist - ], - coeff=self.coeff, - abelian=self.abelian, - ).reduce() - if isinstance(pauli_sum, SummedOp): - return pauli_sum - return pauli_sum.to_pauli_op() # type: ignore - - def equals(self, other: OperatorBase) -> bool: - """Check if other is equal to self. - - Note: - This is not a mathematical check for equality. - If ``self`` and ``other`` implement the same operation but differ - in the representation (e.g. different type of summands) - ``equals`` will evaluate to ``False``. - - Args: - other: The other operator to check for equality. - - Returns: - True, if other and self are equal, otherwise False. - - Examples: - >>> from qiskit.opflow import X, Z - >>> 2 * X == X + X - True - >>> X + Z == Z + X - True - """ - self_reduced, other_reduced = self.reduce(), other.reduce() - if not isinstance(other_reduced, type(self_reduced)): - return False - - # check if reduced op is still a SummedOp - if not isinstance(self_reduced, SummedOp): - return self_reduced == other_reduced - - self_reduced = cast(SummedOp, self_reduced) - other_reduced = cast(SummedOp, other_reduced) - if len(self_reduced.oplist) != len(other_reduced.oplist): - return False - - # absorb coeffs into the operators - if self_reduced.coeff != 1: - self_reduced = SummedOp([op * self_reduced.coeff for op in self_reduced.oplist]) - if other_reduced.coeff != 1: - other_reduced = SummedOp([op * other_reduced.coeff for op in other_reduced.oplist]) - - # compare independent of order - return all(any(i == j for j in other_reduced) for i in self_reduced) diff --git a/qiskit/opflow/list_ops/tensored_op.py b/qiskit/opflow/list_ops/tensored_op.py deleted file mode 100644 index f3eff0a57f9d..000000000000 --- a/qiskit/opflow/list_ops/tensored_op.py +++ /dev/null @@ -1,130 +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. - -"""TensoredOp Class""" - -from functools import partial, reduce -from typing import List, Union, cast, Dict - -import numpy as np - -from qiskit.circuit import ParameterExpression, QuantumCircuit -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class TensoredOp(ListOp): - """Deprecated: A class for lazily representing tensor products of Operators. Often Operators - cannot be efficiently tensored to one another, but may be manipulated further so that they can be - later. This class holds logic to indicate that the Operators in ``oplist`` are meant to - be tensored together, and therefore if they reach a point in which they can be, such as after - conversion to QuantumCircuits, they can be reduced by tensor product.""" - - @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, - oplist: List[OperatorBase], - coeff: Union[complex, ParameterExpression] = 1.0, - abelian: bool = False, - ) -> None: - """ - Args: - oplist: The Operators being tensored. - coeff: A coefficient multiplying the operator - abelian: Indicates whether the Operators in ``oplist`` are known to mutually commute. - """ - super().__init__(oplist, combo_fn=partial(reduce, np.kron), coeff=coeff, abelian=abelian) - - @property - def num_qubits(self) -> int: - return sum(op.num_qubits for op in self.oplist) - - @property - def distributive(self) -> bool: - return False - - @property - def settings(self) -> Dict: - """Return settings.""" - return {"oplist": self._oplist, "coeff": self._coeff, "abelian": self._abelian} - - def _expand_dim(self, num_qubits: int) -> "TensoredOp": - """Appends I ^ num_qubits to ``oplist``. Choice of PauliOp as - identity is arbitrary and can be substituted for other PrimitiveOp identity. - - Returns: - TensoredOp expanded with identity operator. - """ - # pylint: disable=cyclic-import - from ..operator_globals import I - - return TensoredOp(self.oplist + [I ^ num_qubits], coeff=self.coeff) - - def tensor(self, other: OperatorBase) -> OperatorBase: - if isinstance(other, TensoredOp): - return TensoredOp(self.oplist + other.oplist, coeff=self.coeff * other.coeff) - return TensoredOp(self.oplist + [other], coeff=self.coeff) - - # TODO eval should partial trace the input into smaller StateFns each of size - # op.num_qubits for each op in oplist. Right now just works through matmul. - def eval( - self, front: Union[str, dict, np.ndarray, OperatorBase, Statevector] = None - ) -> Union[OperatorBase, complex]: - if self._is_empty(): - return 0.0 - return cast(Union[OperatorBase, complex], self.to_matrix_op().eval(front=front)) - - # Try collapsing list or trees of tensor products. - # TODO do this smarter - def reduce(self) -> OperatorBase: - reduced_ops = [op.reduce() for op in self.oplist] - if self._is_empty(): - return self.__class__([], coeff=self.coeff, abelian=self.abelian) - reduced_ops = reduce(lambda x, y: x.tensor(y), reduced_ops) * self.coeff - if isinstance(reduced_ops, ListOp) and len(reduced_ops.oplist) == 1: - return reduced_ops.oplist[0] - else: - return cast(OperatorBase, reduced_ops) - - def to_circuit(self) -> QuantumCircuit: - """Returns the quantum circuit, representing the tensored operator. - - Returns: - The circuit representation of the tensored operator. - - Raises: - OpflowError: for operators where a single underlying circuit can not be produced. - """ - circuit_op = self.to_circuit_op() - # pylint: disable=cyclic-import - from ..state_fns.circuit_state_fn import CircuitStateFn - from ..primitive_ops.primitive_op import PrimitiveOp - - if isinstance(circuit_op, (PrimitiveOp, CircuitStateFn)): - return circuit_op.to_circuit() - raise OpflowError( - "Conversion to_circuit supported only for operators, where a single " - "underlying circuit can be produced." - ) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - - mat = self.coeff * reduce(np.kron, [np.asarray(op.to_matrix()) for op in self.oplist]) - return np.asarray(mat, dtype=complex) diff --git a/qiskit/opflow/mixins/__init__.py b/qiskit/opflow/mixins/__init__.py deleted file mode 100644 index 705400f844d5..000000000000 --- a/qiskit/opflow/mixins/__init__.py +++ /dev/null @@ -1,18 +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. - -""" -OpFlow Mixins -""" - -from .star_algebra import StarAlgebraMixin -from .tensor import TensorMixin diff --git a/qiskit/opflow/mixins/star_algebra.py b/qiskit/opflow/mixins/star_algebra.py deleted file mode 100644 index 57994a4cdd88..000000000000 --- a/qiskit/opflow/mixins/star_algebra.py +++ /dev/null @@ -1,133 +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. - -"""The star algebra mixin abstract base class.""" - -from abc import ABC, abstractmethod -from numbers import Integral - -from qiskit.quantum_info.operators.mixins import MultiplyMixin -from qiskit.utils.deprecation import deprecate_func - - -class StarAlgebraMixin(MultiplyMixin, ABC): - """Deprecated: The star algebra mixin class. - Star algebra is an algebra with an adjoint. - - This class overrides: - - ``*``, ``__mul__``, `__rmul__`, -> :meth:`mul` - - ``/``, ``__truediv__``, -> :meth:`mul` - - ``__neg__`` -> :meth:``mul` - - ``+``, ``__add__``, ``__radd__`` -> :meth:`add` - - ``-``, ``__sub__``, `__rsub__`, -> :meth:a`add` - - ``@``, ``__matmul__`` -> :meth:`compose` - - ``**``, ``__pow__`` -> :meth:`power` - - ``~``, ``__invert__`` -> :meth:`adjoint` - - The following abstract methods must be implemented by subclasses: - - :meth:`mul(self, other)` - - :meth:`add(self, other)` - - :meth:`compose(self, other)` - - :meth:`adjoint(self)` - """ - - @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: - pass - - # Scalar multiplication - - @abstractmethod - def mul(self, other: complex): - """Return scalar multiplication of self and other, overloaded by `*`.""" - - def __mul__(self, other: complex): - return self.mul(other) - - def _multiply(self, other: complex): - return self.mul(other) - - # Addition, substitution - - @abstractmethod - def add(self, other): - """Return Operator addition of self and other, overloaded by `+`.""" - - def __add__(self, other): - # Hack to be able to use sum(list_of_ops) nicely because - # sum adds 0 to the first element of the list. - if other == 0: - return self - - return self.add(other) - - def __radd__(self, other): - # Hack to be able to use sum(list_of_ops) nicely because - # sum adds 0 to the first element of the list. - if other == 0: - return self - return self.add(other) - - def __sub__(self, other): - return self.add(-other) - - def __rsub__(self, other): - return self.neg().add(other) - - # Operator multiplication - - @abstractmethod - def compose(self, other): - """Overloads the matrix multiplication operator `@` for self and other. - `Compose` computes operator composition between self and other (linear algebra-style: - A@B(x) = A(B(x))). - """ - - def power(self, exponent: int): - r"""Return Operator composed with self multiple times, overloaded by ``**``.""" - if not isinstance(exponent, Integral): - raise TypeError( - f"Unsupported operand type(s) for **: '{type(self).__name__}' and " - f"'{type(exponent).__name__}'" - ) - - if exponent < 1: - raise ValueError("The input `exponent` must be a positive integer.") - - res = self - for _ in range(1, exponent): - res = res.compose(self) - return res - - def __matmul__(self, other): - return self.compose(other) - - def __pow__(self, exponent: int): - return self.power(exponent) - - # Adjoint - - @abstractmethod - def adjoint(self): - """Returns the complex conjugate transpose (dagger) of self.adjoint - - Returns: - An operator equivalent to self's adjoint. - """ - - def __invert__(self): - """Overload unary `~` to return Operator adjoint.""" - return self.adjoint() diff --git a/qiskit/opflow/mixins/tensor.py b/qiskit/opflow/mixins/tensor.py deleted file mode 100644 index a138bda6009f..000000000000 --- a/qiskit/opflow/mixins/tensor.py +++ /dev/null @@ -1,58 +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. - -"""The tensor mixin abstract base class.""" - -from abc import ABC, abstractmethod -from numbers import Integral -from qiskit.utils.deprecation import deprecate_func - - -class TensorMixin(ABC): - """Deprecated: The mixin class for tensor operations. - - This class overrides: - - ``^``, ``__xor__``, `__rxor__` -> :meth:`tensor` between two operators and - :meth:`tensorpower` with integer. - The following abstract methods must be implemented by subclasses: - - :meth:``tensor(self, other)`` - - :meth:``tensorpower(self, other: int)`` - """ - - @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: - pass - - def __xor__(self, other): - if isinstance(other, Integral): - return self.tensorpower(other) - else: - return self.tensor(other) - - def __rxor__(self, other): - # a hack to make (I^0)^Z work as intended. - if other == 1: - return self - else: - return other.tensor(self) - - @abstractmethod - def tensor(self, other): - r"""Return tensor product between self and other, overloaded by ``^``.""" - - @abstractmethod - def tensorpower(self, other: int): - r"""Return tensor product with self multiple times, overloaded by ``^``.""" diff --git a/qiskit/opflow/operator_base.py b/qiskit/opflow/operator_base.py deleted file mode 100644 index 818e0f3d960e..000000000000 --- a/qiskit/opflow/operator_base.py +++ /dev/null @@ -1,516 +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. - -"""OperatorBase Class""" - -import itertools -import warnings -from abc import ABC, abstractmethod -from copy import deepcopy -from typing import Dict, List, Optional, Set, Tuple, Union, cast - -import numpy as np -from scipy.sparse import csr_matrix, spmatrix - -from qiskit.circuit import ParameterExpression, ParameterVector -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.mixins import StarAlgebraMixin, TensorMixin -from qiskit.quantum_info import Statevector -from qiskit.utils import algorithm_globals -from qiskit.utils.deprecation import deprecate_func - - -class OperatorBase(StarAlgebraMixin, TensorMixin, ABC): - """Deprecated: A base class for all Operators: PrimitiveOps, StateFns, ListOps, etc. Operators are - defined as functions which take one complex binary function to another. These complex binary - functions are represented by StateFns, which are themselves a special class of Operators - taking only the ``Zero`` StateFn to the complex binary function they represent. - - Operators can be used to construct complicated functions and computation, and serve as the - building blocks for algorithms. - - """ - - # Indentation used in string representation of list operators - # Can be changed to use another indentation than two whitespaces - INDENTATION = " " - - _count = itertools.count() - - @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: - super().__init__() - self._instance_id = next(self._count) - - @property - @abstractmethod - def settings(self) -> Dict: - """Return settings of this object in a dictionary. - - You can, for example, use this ``settings`` dictionary to serialize the - object in JSON format, if the JSON encoder you use supports all types in - the dictionary. - - Returns: - Object settings in a dictionary. - """ - raise NotImplementedError - - @property - def instance_id(self) -> int: - """Return the unique instance id.""" - return self._instance_id - - @property - @abstractmethod - def num_qubits(self) -> int: - r"""The number of qubits over which the Operator is defined. If - ``op.num_qubits == 5``, then ``op.eval('1' * 5)`` will be valid, but - ``op.eval('11')`` will not. - - Returns: - The number of qubits accepted by the Operator's underlying function. - """ - raise NotImplementedError - - @abstractmethod - def primitive_strings(self) -> Set[str]: - r"""Return a set of strings describing the primitives contained in the Operator. For - example, ``{'QuantumCircuit', 'Pauli'}``. For hierarchical Operators, such as ``ListOps``, - this can help illuminate the primitives represented in the various recursive levels, - and therefore which conversions can be applied. - - Returns: - A set of strings describing the primitives contained within the Operator. - """ - raise NotImplementedError - - @abstractmethod - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, "OperatorBase", Statevector] - ] = None, - ) -> Union["OperatorBase", complex]: - r""" - Evaluate the Operator's underlying function, either on a binary string or another Operator. - A square binary Operator can be defined as a function taking a binary function to another - binary function. This method returns the value of that function for a given StateFn or - binary string. For example, ``op.eval('0110').eval('1110')`` can be seen as querying the - Operator's matrix representation by row 6 and column 14, and will return the complex - value at those "indices." Similarly for a StateFn, ``op.eval('1011')`` will return the - complex value at row 11 of the vector representation of the StateFn, as all StateFns are - defined to be evaluated from Zero implicitly (i.e. it is as if ``.eval('0000')`` is already - called implicitly to always "indexing" from column 0). - - If ``front`` is None, the matrix-representation of the operator is returned. - - Args: - front: The bitstring, dict of bitstrings (with values being coefficients), or - StateFn to evaluated by the Operator's underlying function, or None. - - Returns: - The output of the Operator's evaluation function. If self is a ``StateFn``, the result - is a float or complex. If self is an Operator (``PrimitiveOp, ComposedOp, SummedOp, - EvolvedOp,`` etc.), the result is a StateFn. - If ``front`` is None, the matrix-representation of the operator is returned, which - is a ``MatrixOp`` for the operators and a ``VectorStateFn`` for state-functions. - If either self or front contain proper - ``ListOps`` (not ListOp subclasses), the result is an n-dimensional list of complex - or StateFn results, resulting from the recursive evaluation by each OperatorBase - in the ListOps. - - """ - raise NotImplementedError - - @abstractmethod - def reduce(self): - r"""Try collapsing the Operator structure, usually after some type of conversion, - e.g. trying to add Operators in a SummedOp or delete needless IGates in a CircuitOp. - If no reduction is available, just returns self. - - Returns: - The reduced ``OperatorBase``. - """ - raise NotImplementedError - - @abstractmethod - def to_matrix(self, massive: bool = False) -> np.ndarray: - r"""Return NumPy representation of the Operator. Represents the evaluation of - the Operator's underlying function on every combination of basis binary strings. - Warn if more than 16 qubits to force having to set ``massive=True`` if such a - large vector is desired. - - Returns: - The NumPy ``ndarray`` equivalent to this Operator. - """ - raise NotImplementedError - - @abstractmethod - def to_matrix_op(self, massive: bool = False) -> "OperatorBase": - """Returns a ``MatrixOp`` equivalent to this Operator.""" - raise NotImplementedError - - @abstractmethod - def to_circuit_op(self) -> "OperatorBase": - """Returns a ``CircuitOp`` equivalent to this Operator.""" - raise NotImplementedError - - def to_spmatrix(self) -> spmatrix: - r"""Return SciPy sparse matrix representation of the Operator. Represents the evaluation of - the Operator's underlying function on every combination of basis binary strings. - - Returns: - The SciPy ``spmatrix`` equivalent to this Operator. - """ - return csr_matrix(self.to_matrix()) - - def is_hermitian(self) -> bool: - """Return True if the operator is hermitian. - - Returns: Boolean value - """ - return (self.to_spmatrix() != self.to_spmatrix().getH()).nnz == 0 - - @staticmethod - def _indent(lines: str, indentation: str = INDENTATION) -> str: - """Indented representation to allow pretty representation of nested operators.""" - indented_str = indentation + lines.replace("\n", f"\n{indentation}") - if indented_str.endswith(f"\n{indentation}"): - indented_str = indented_str[: -len(indentation)] - return indented_str - - # Addition / Subtraction - - @abstractmethod - def add(self, other: "OperatorBase") -> "OperatorBase": - r"""Return Operator addition of self and other, overloaded by ``+``. - - Args: - other: An ``OperatorBase`` with the same number of qubits as self, and in the same - 'Operator', 'State function', or 'Measurement' category as self (i.e. the same type - of underlying function). - - Returns: - An ``OperatorBase`` equivalent to the sum of self and other. - """ - raise NotImplementedError - - # Negation - - def neg(self) -> "OperatorBase": - r"""Return the Operator's negation, effectively just multiplying by -1.0, - overloaded by ``-``. - - Returns: - An ``OperatorBase`` equivalent to the negation of self. - """ - return self.mul(-1.0) - - # Adjoint - - @abstractmethod - def adjoint(self) -> "OperatorBase": - r"""Return a new Operator equal to the Operator's adjoint (conjugate transpose), - overloaded by ``~``. For StateFns, this also turns the StateFn into a measurement. - - Returns: - An ``OperatorBase`` equivalent to the adjoint of self. - """ - raise NotImplementedError - - # Equality - - def __eq__(self, other: object) -> bool: - r"""Overload ``==`` operation to evaluate equality between Operators. - - Args: - other: The ``OperatorBase`` to compare to self. - - Returns: - A bool equal to the equality of self and other. - """ - if not isinstance(other, OperatorBase): - return NotImplemented - return self.equals(cast(OperatorBase, other)) - - @abstractmethod - def equals(self, other: "OperatorBase") -> bool: - r""" - Evaluate Equality between Operators, overloaded by ``==``. Only returns True if self and - other are of the same representation (e.g. a DictStateFn and CircuitStateFn will never be - equal, even if their vector representations are equal), their underlying primitives are - equal (this means for ListOps, OperatorStateFns, or EvolvedOps the equality is evaluated - recursively downwards), and their coefficients are equal. - - Args: - other: The ``OperatorBase`` to compare to self. - - Returns: - A bool equal to the equality of self and other. - - """ - raise NotImplementedError - - # Scalar Multiplication - - @abstractmethod - def mul(self, scalar: Union[complex, ParameterExpression]) -> "OperatorBase": - r""" - Returns the scalar multiplication of the Operator, overloaded by ``*``, including - support for Terra's ``Parameters``, which can be bound to values later (via - ``bind_parameters``). - - Args: - scalar: The real or complex scalar by which to multiply the Operator, - or the ``ParameterExpression`` to serve as a placeholder for a scalar factor. - - Returns: - An ``OperatorBase`` equivalent to product of self and scalar. - """ - raise NotImplementedError - - @abstractmethod - def tensor(self, other: "OperatorBase") -> "OperatorBase": - r"""Return tensor product between self and other, overloaded by ``^``. - Note: You must be conscious of Qiskit's big-endian bit printing convention. - Meaning, X.tensor(Y) produces an X on qubit 0 and an Y on qubit 1, or X⨂Y, - but would produce a QuantumCircuit which looks like - - -[Y]- - -[X]- - - Because Terra prints circuits and results with qubit 0 at the end of the string - or circuit. - - Args: - other: The ``OperatorBase`` to tensor product with self. - - Returns: - An ``OperatorBase`` equivalent to the tensor product of self and other. - """ - raise NotImplementedError - - @abstractmethod - def tensorpower(self, other: int) -> Union["OperatorBase", int]: - r"""Return tensor product with self multiple times, overloaded by ``^``. - - Args: - other: The int number of times to tensor product self with itself via ``tensorpower``. - - Returns: - An ``OperatorBase`` equivalent to the tensorpower of self by other. - """ - raise NotImplementedError - - @property - @abstractmethod - def parameters(self): - r"""Return a set of Parameter objects contained in the Operator.""" - raise NotImplementedError - - # Utility functions for parameter binding - - @abstractmethod - def assign_parameters( - self, - param_dict: Dict[ - ParameterExpression, - Union[complex, ParameterExpression, List[Union[complex, ParameterExpression]]], - ], - ) -> "OperatorBase": - """Binds scalar values to any Terra ``Parameters`` in the coefficients or primitives of - the Operator, or substitutes one ``Parameter`` for another. This method differs from - Terra's ``assign_parameters`` in that it also supports lists of values to assign for a - give ``Parameter``, in which case self will be copied for each parameterization in the - binding list(s), and all the copies will be returned in an ``OpList``. If lists of - parameterizations are used, every ``Parameter`` in the param_dict must have the same - length list of parameterizations. - - Args: - param_dict: The dictionary of ``Parameters`` to replace, and values or lists of - values by which to replace them. - - Returns: - The ``OperatorBase`` with the ``Parameters`` in self replaced by the - values or ``Parameters`` in param_dict. If param_dict contains parameterization lists, - this ``OperatorBase`` is an ``OpList``. - """ - raise NotImplementedError - - @abstractmethod - def _expand_dim(self, num_qubits: int) -> "OperatorBase": - """Expands the operator with identity operator of dimension 2**num_qubits. - - Returns: - Operator corresponding to self.tensor(identity_operator), where dimension of identity - operator is 2 ** num_qubits. - """ - raise NotImplementedError - - @abstractmethod - def permute(self, permutation: List[int]) -> "OperatorBase": - """Permutes the qubits of the operator. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j should be permuted to position permutation[j]. - - Returns: - A new OperatorBase containing the permuted operator. - - Raises: - OpflowError: if indices do not define a new index for each qubit. - """ - raise NotImplementedError - - def bind_parameters( - self, - param_dict: Dict[ - ParameterExpression, - Union[complex, ParameterExpression, List[Union[complex, ParameterExpression]]], - ], - ) -> "OperatorBase": - r""" - Same as assign_parameters, but maintained for consistency with QuantumCircuit in - Terra (which has both assign_parameters and bind_parameters). - """ - return self.assign_parameters(param_dict) - - # Mostly copied from terra, but with list unrolling added: - @staticmethod - def _unroll_param_dict( - value_dict: Dict[Union[ParameterExpression, ParameterVector], Union[complex, List[complex]]] - ) -> Union[Dict[ParameterExpression, complex], List[Dict[ParameterExpression, complex]]]: - """Unrolls the ParameterVectors in a param_dict into separate Parameters, and unrolls - parameterization value lists into separate param_dicts without list nesting.""" - unrolled_value_dict = {} - for (param, value) in value_dict.items(): - if isinstance(param, ParameterExpression): - unrolled_value_dict[param] = value - if isinstance(param, ParameterVector) and isinstance(value, (list, np.ndarray)): - if not len(param) == len(value): - raise ValueError( - "ParameterVector {} has length {}, which differs from value list {} of " - "len {}".format(param, len(param), value, len(value)) - ) - unrolled_value_dict.update(zip(param, value)) - if isinstance(list(unrolled_value_dict.values())[0], list): - # check that all are same length - unrolled_value_dict_list = [] - try: - for i in range(len(list(unrolled_value_dict.values())[0])): # type: ignore - unrolled_value_dict_list.append( - OperatorBase._get_param_dict_for_index( - unrolled_value_dict, i # type: ignore - ) - ) - return unrolled_value_dict_list - except IndexError as ex: - raise OpflowError("Parameter binding lists must all be the same length.") from ex - return unrolled_value_dict # type: ignore - - @staticmethod - def _get_param_dict_for_index(unrolled_dict: Dict[ParameterExpression, List[complex]], i: int): - """Gets a single non-list-nested param_dict for a given list index from a nested one.""" - return {k: v[i] for (k, v) in unrolled_dict.items()} - - def _expand_shorter_operator_and_permute( - self, other: "OperatorBase", permutation: Optional[List[int]] = None - ) -> Tuple["OperatorBase", "OperatorBase"]: - if permutation is not None: - other = other.permute(permutation) - new_self = self - if not self.num_qubits == other.num_qubits: - # pylint: disable=cyclic-import - from .operator_globals import Zero - - if other == Zero: - # Zero is special - we'll expand it to the correct qubit number. - other = Zero.__class__("0" * self.num_qubits) - elif other.num_qubits < self.num_qubits: - other = other._expand_dim(self.num_qubits - other.num_qubits) - elif other.num_qubits > self.num_qubits: - new_self = self._expand_dim(other.num_qubits - self.num_qubits) - return new_self, other - - def copy(self) -> "OperatorBase": - """Return a deep copy of the Operator.""" - return deepcopy(self) - - # Composition - - @abstractmethod - def compose( - self, other: "OperatorBase", permutation: Optional[List[int]] = None, front: bool = False - ) -> "OperatorBase": - r"""Return Operator Composition between self and other (linear algebra-style: - A@B(x) = A(B(x))), overloaded by ``@``. - - Note: You must be conscious of Quantum Circuit vs. Linear Algebra ordering - conventions. Meaning, X.compose(Y) - produces an X∘Y on qubit 0, but would produce a QuantumCircuit which looks like - - -[Y]-[X]- - - Because Terra prints circuits with the initial state at the left side of the circuit. - - Args: - other: The ``OperatorBase`` with which to compose self. - permutation: ``List[int]`` which defines permutation on other operator. - front: If front==True, return ``other.compose(self)``. - - Returns: - An ``OperatorBase`` equivalent to the function composition of self and other. - """ - raise NotImplementedError - - @staticmethod - def _check_massive(method: str, matrix: bool, num_qubits: int, massive: bool) -> None: - """ - Checks if matrix or vector generated will be too large. - - Args: - method: Name of the calling method - matrix: True if object is matrix, otherwise vector - num_qubits: number of qubits - massive: True if it is ok to proceed with large matrix - - Raises: - ValueError: Massive is False and number of qubits is greater than 16 - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - if num_qubits > 16 and not massive and not algorithm_globals.massive: - dim = 2**num_qubits - if matrix: - obj_type = "matrix" - dimensions = f"{dim}x{dim}" - else: - obj_type = "vector" - dimensions = f"{dim}" - raise ValueError( - f"'{method}' will return an exponentially large {obj_type}, " - f"in this case '{dimensions}' elements. " - "Set algorithm_globals.massive=True or the method argument massive=True " - "if you want to proceed." - ) - - # Printing - - @abstractmethod - def __str__(self) -> str: - raise NotImplementedError diff --git a/qiskit/opflow/operator_globals.py b/qiskit/opflow/operator_globals.py deleted file mode 100644 index 73e2303b3bfb..000000000000 --- a/qiskit/opflow/operator_globals.py +++ /dev/null @@ -1,80 +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. - -""" -Operator Globals -""" - -import warnings - -from qiskit.quantum_info import Pauli -from qiskit.circuit.library import CXGate, SGate, TGate, HGate, SwapGate, CZGate - -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.circuit_op import CircuitOp -from qiskit.opflow.state_fns.dict_state_fn import DictStateFn -from qiskit.utils.deprecation import deprecate_func - -# Digits of precision when returning values from eval functions. Without rounding, 1e-17 or 1e-32 -# values often show up in place of 0, etc. -# Note: care needs to be taken in rounding otherwise some behavior may not be as expected. E.g -# evolution is used in QAOA variational form and difference when optimizing may be small - round -# the outcome too much and a small difference may become none and the optimizer gets stuck where -# otherwise it would not. -EVAL_SIG_DIGITS = 18 - -# Immutable convenience objects - - -@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): - r"""Deprecate\: Delete the __setattr__ property to make the object mostly immutable.""" - - # TODO figure out how to get correct error message - # def throw_immutability_exception(self, *args): - # raise OpflowError('Operator convenience globals are immutable.') - - obj.__setattr__ = None - return obj - - -# All the deprecation warnings triggered by these object creations correctly blame `qiskit.opflow` -# and so are not shown to users by default. However, since they are eagerly triggered at `import -# qiskit.opflow`, they obscure the one "true" warning of the import when downstream testing code is -# running with all warnings showing. The true warning that really needs attention becomes easy to -# overlook because there's so many that the downstream code didn't explicitly call. -with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning, module=r"qiskit\.opflow\.") - - # 1-Qubit Paulis - X = make_immutable(PauliOp(Pauli("X"))) - Y = make_immutable(PauliOp(Pauli("Y"))) - Z = make_immutable(PauliOp(Pauli("Z"))) - I = make_immutable(PauliOp(Pauli("I"))) - - # Clifford+T, and some other common non-parameterized gates - CX = make_immutable(CircuitOp(CXGate())) - S = make_immutable(CircuitOp(SGate())) - H = make_immutable(CircuitOp(HGate())) - T = make_immutable(CircuitOp(TGate())) - Swap = make_immutable(CircuitOp(SwapGate())) - CZ = make_immutable(CircuitOp(CZGate())) - - # 1-Qubit states - Zero = make_immutable(DictStateFn("0")) - One = make_immutable(DictStateFn("1")) - Plus = make_immutable(H.compose(Zero)) - Minus = make_immutable(H.compose(X).compose(Zero)) diff --git a/qiskit/opflow/primitive_ops/__init__.py b/qiskit/opflow/primitive_ops/__init__.py deleted file mode 100644 index 7e5cc72fad6b..000000000000 --- a/qiskit/opflow/primitive_ops/__init__.py +++ /dev/null @@ -1,79 +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. - -""" -Primitive Operators (:mod:`qiskit.opflow.primitive_ops`) -======================================================== - -.. currentmodule:: qiskit.opflow.primitive_ops - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -Operators are defined to be functions which take State functions to State functions. - -PrimitiveOps are the classes for representing basic Operators, backed by computational -Operator primitives from Terra. These classes (and inheritors) primarily serve to allow the -underlying primitives to "flow" - i.e. interoperability and adherence to the Operator -formalism - while the core computational logic mostly remains in the underlying primitives. -For example, we would not produce an interface in Terra in which -``QuantumCircuit1 + QuantumCircuit2`` equaled the Operator sum of the circuit -unitaries, rather than simply appending the circuits. However, within the Operator -flow summing the unitaries is the expected behavior. - -Note: - All mathematical methods are not in-place, meaning that they return a - new object, but the underlying primitives are not copied. - -Primitive Operators -------------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - PrimitiveOp - CircuitOp - MatrixOp - PauliOp - PauliSumOp - TaperedPauliSumOp - -Symmetries ----------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - Z2Symmetries -""" - -from .primitive_op import PrimitiveOp -from .pauli_op import PauliOp -from .matrix_op import MatrixOp -from .circuit_op import CircuitOp -from .pauli_sum_op import PauliSumOp -from .tapered_pauli_sum_op import TaperedPauliSumOp, Z2Symmetries - -__all__ = [ - "PrimitiveOp", - "PauliOp", - "MatrixOp", - "CircuitOp", - "PauliSumOp", - "TaperedPauliSumOp", - "Z2Symmetries", -] diff --git a/qiskit/opflow/primitive_ops/circuit_op.py b/qiskit/opflow/primitive_ops/circuit_op.py deleted file mode 100644 index c063ffd63cf4..000000000000 --- a/qiskit/opflow/primitive_ops/circuit_op.py +++ /dev/null @@ -1,252 +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. - -"""CircuitOp Class""" - -from typing import Dict, List, Optional, Set, Union, cast -import numpy as np - -import qiskit -from qiskit import QuantumCircuit -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.circuit.library import IGate -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class CircuitOp(PrimitiveOp): - """Deprecated: Class for Operators backed by Terra's ``QuantumCircuit`` module.""" - - primitive: QuantumCircuit - - @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: Union[Instruction, QuantumCircuit], - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> None: - """ - Args: - primitive: The QuantumCircuit which defines the - behavior of the underlying function. - coeff: A coefficient multiplying the primitive - - Raises: - TypeError: Unsupported primitive, or primitive has ClassicalRegisters. - """ - if isinstance(primitive, Instruction): - qc = QuantumCircuit(primitive.num_qubits) - qc.append(primitive, qargs=range(primitive.num_qubits)) - primitive = qc - - if not isinstance(primitive, QuantumCircuit): - raise TypeError( - "CircuitOp can only be instantiated with " - "QuantumCircuit, not {}".format(type(primitive)) - ) - - if len(primitive.clbits) != 0: - raise TypeError("CircuitOp does not support QuantumCircuits with ClassicalRegisters.") - - super().__init__(primitive, coeff) - self._coeff = coeff - - def primitive_strings(self) -> Set[str]: - return {"QuantumCircuit"} - - @property - def num_qubits(self) -> int: - return self.primitive.num_qubits - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over operators with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - if isinstance(other, CircuitOp) and self.primitive == other.primitive: - return CircuitOp(self.primitive, coeff=self.coeff + other.coeff) - - # Covers all else. - # pylint: disable=cyclic-import - from ..list_ops.summed_op import SummedOp - - return SummedOp([self, other]) - - def adjoint(self) -> "CircuitOp": - return CircuitOp(self.primitive.inverse(), coeff=self.coeff.conjugate()) - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, CircuitOp) or not self.coeff == other.coeff: - return False - - return self.primitive == other.primitive - - def tensor(self, other: OperatorBase) -> Union["CircuitOp", TensoredOp]: - # pylint: disable=cyclic-import - from .pauli_op import PauliOp - from .matrix_op import MatrixOp - - if isinstance(other, (PauliOp, CircuitOp, MatrixOp)): - other = other.to_circuit_op() - - if isinstance(other, CircuitOp): - new_qc = QuantumCircuit(self.num_qubits + other.num_qubits) - # NOTE!!! REVERSING QISKIT ENDIANNESS HERE - new_qc.append( - other.to_instruction(), qargs=new_qc.qubits[0 : other.primitive.num_qubits] - ) - new_qc.append(self.to_instruction(), qargs=new_qc.qubits[other.primitive.num_qubits :]) - new_qc = new_qc.decompose() - return CircuitOp(new_qc, coeff=self.coeff * other.coeff) - - return TensoredOp([self, other]) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(CircuitOp, new_self) - - if front: - return other.compose(new_self) - # pylint: disable=cyclic-import - from ..operator_globals import Zero - from ..state_fns import CircuitStateFn - from .pauli_op import PauliOp - from .matrix_op import MatrixOp - - if other == Zero ^ new_self.num_qubits: - return CircuitStateFn(new_self.primitive, coeff=new_self.coeff) - - if isinstance(other, (PauliOp, CircuitOp, MatrixOp)): - other = other.to_circuit_op() - - if isinstance(other, (CircuitOp, CircuitStateFn)): - new_qc = other.primitive.compose(new_self.primitive) - if isinstance(other, CircuitStateFn): - return CircuitStateFn( - new_qc, is_measurement=other.is_measurement, coeff=new_self.coeff * other.coeff - ) - else: - return CircuitOp(new_qc, coeff=new_self.coeff * other.coeff) - - return super(CircuitOp, new_self).compose(other) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - unitary = qiskit.quantum_info.Operator(self.to_circuit()).data - return unitary * self.coeff - - def __str__(self) -> str: - qc = self.to_circuit() - prim_str = str(qc.draw(output="text")) - if self.coeff == 1.0: - return prim_str - else: - return f"{self.coeff} * {prim_str}" - - def assign_parameters(self, param_dict: dict) -> OperatorBase: - param_value = self.coeff - qc = self.primitive - if isinstance(self.coeff, ParameterExpression) or self.primitive.parameters: - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - from ..list_ops.list_op import ListOp - - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if isinstance(self.coeff, ParameterExpression) and self.coeff.parameters <= set( - unrolled_dict.keys() - ): - param_instersection = set(unrolled_dict.keys()) & self.coeff.parameters - binds = {param: unrolled_dict[param] for param in param_instersection} - param_value = float(self.coeff.bind(binds)) - # & is set intersection, check if any parameters in unrolled are present in circuit - # This is different from bind_parameters in Terra because they check for set equality - if set(unrolled_dict.keys()) & self.primitive.parameters: - # Only bind the params found in the circuit - param_instersection = set(unrolled_dict.keys()) & self.primitive.parameters - binds = {param: unrolled_dict[param] for param in param_instersection} - qc = self.to_circuit().assign_parameters(binds) - return self.__class__(qc, coeff=param_value) - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - from ..state_fns import CircuitStateFn - from ..list_ops import ListOp - from .pauli_op import PauliOp - from .matrix_op import MatrixOp - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - # Composable with circuit - if isinstance(front, (PauliOp, CircuitOp, MatrixOp, CircuitStateFn)): - return self.compose(front) - - return self.to_matrix_op().eval(front) - - def to_circuit(self) -> QuantumCircuit: - return self.primitive - - def to_circuit_op(self) -> "CircuitOp": - return self - - def to_instruction(self) -> Instruction: - return self.primitive.to_instruction() - - # Warning - modifying immutable object!! - def reduce(self) -> OperatorBase: - if self.primitive.data is not None: - # Need to do this from the end because we're deleting items! - for i in reversed(range(len(self.primitive.data))): - gate = self.primitive.data[i].operation - # Check if Identity or empty instruction (need to check that type is exactly - # Instruction because some gates have lazy gate.definition population) - # pylint: disable=unidiomatic-typecheck - if isinstance(gate, IGate) or ( - type(gate) == Instruction and gate.definition.data == [] - ): - del self.primitive.data[i] - return self - - def _expand_dim(self, num_qubits: int) -> "CircuitOp": - return self.permute(list(range(num_qubits, num_qubits + self.num_qubits))) - - def permute(self, permutation: List[int]) -> "CircuitOp": - r""" - Permute the qubits of the circuit. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j of the circuit should be permuted to position permutation[j]. - - Returns: - A new CircuitOp containing the permuted circuit. - """ - new_qc = QuantumCircuit(max(permutation) + 1).compose(self.primitive, qubits=permutation) - return CircuitOp(new_qc, coeff=self.coeff) diff --git a/qiskit/opflow/primitive_ops/matrix_op.py b/qiskit/opflow/primitive_ops/matrix_op.py deleted file mode 100644 index c32ef4334268..000000000000 --- a/qiskit/opflow/primitive_ops/matrix_op.py +++ /dev/null @@ -1,237 +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. - -"""MatrixOp Class""" - -from typing import Dict, List, Optional, Set, Union, cast, get_type_hints -import numpy as np -from scipy.sparse import spmatrix - -from qiskit import QuantumCircuit -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.circuit.library.hamiltonian_gate import HamiltonianGate -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.circuit_op import CircuitOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.quantum_info import Operator, Statevector -from qiskit.utils import arithmetic -from qiskit.utils.deprecation import deprecate_func - - -class MatrixOp(PrimitiveOp): - """Deprecated: Class for Operators represented by matrices, - backed by Terra's ``Operator`` module.""" - - primitive: Operator - - @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: Union[list, np.ndarray, spmatrix, Operator], - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> None: - """ - Args: - primitive: The matrix-like object which defines the behavior of the underlying function. - coeff: A coefficient multiplying the primitive - - Raises: - TypeError: invalid parameters. - ValueError: invalid parameters. - """ - primitive_orig = primitive - if isinstance(primitive, spmatrix): - primitive = primitive.toarray() - - if isinstance(primitive, (list, np.ndarray)): - primitive = Operator(primitive) - - if not isinstance(primitive, Operator): - type_hints = get_type_hints(MatrixOp.__init__).get("primitive") - valid_cls = [cls.__name__ for cls in type_hints.__args__] - raise TypeError( - f"MatrixOp can only be instantiated with {valid_cls}, " - f"not '{primitive_orig.__class__.__name__}'" - ) - - if primitive.input_dims() != primitive.output_dims(): - raise ValueError("Cannot handle non-square matrices yet.") - - super().__init__(primitive, coeff=coeff) - - def primitive_strings(self) -> Set[str]: - return {"Matrix"} - - @property - def num_qubits(self) -> int: - return len(self.primitive.input_dims()) - - def add(self, other: OperatorBase) -> Union["MatrixOp", SummedOp]: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over operators with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - if isinstance(other, MatrixOp) and self.primitive == other.primitive: - return MatrixOp(self.primitive, coeff=self.coeff + other.coeff) - - # Terra's Operator cannot handle ParameterExpressions - if ( - isinstance(other, MatrixOp) - and not isinstance(self.coeff, ParameterExpression) - and not isinstance(other.coeff, ParameterExpression) - ): - return MatrixOp((self.coeff * self.primitive) + (other.coeff * other.primitive)) - - # Covers Paulis, Circuits, and all else. - return SummedOp([self, other]) - - def adjoint(self) -> "MatrixOp": - return MatrixOp(self.primitive.adjoint(), coeff=self.coeff.conjugate()) - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, MatrixOp): - return False - if isinstance(self.coeff, ParameterExpression) ^ isinstance( - other.coeff, ParameterExpression - ): - return False - if isinstance(self.coeff, ParameterExpression) and isinstance( - other.coeff, ParameterExpression - ): - return self.coeff == other.coeff and self.primitive == other.primitive - return self.coeff * self.primitive == other.coeff * other.primitive - - def _expand_dim(self, num_qubits: int) -> "MatrixOp": - identity = np.identity(2**num_qubits, dtype=complex) - return MatrixOp(self.primitive.tensor(Operator(identity)), coeff=self.coeff) - - def tensor(self, other: OperatorBase) -> Union["MatrixOp", TensoredOp]: - if isinstance(other, MatrixOp): - return MatrixOp(self.primitive.tensor(other.primitive), coeff=self.coeff * other.coeff) - - return TensoredOp([self, other]) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(MatrixOp, new_self) - - if front: - return other.compose(new_self) - if isinstance(other, MatrixOp): - return MatrixOp( - new_self.primitive.compose(other.primitive, front=True), - coeff=new_self.coeff * other.coeff, - ) - - return super(MatrixOp, new_self).compose(other) - - def permute(self, permutation: Optional[List[int]] = None) -> OperatorBase: - """Creates a new MatrixOp that acts on the permuted qubits. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j should be permuted to position permutation[j]. - - Returns: - A new MatrixOp representing the permuted operator. - - Raises: - OpflowError: if indices do not define a new index for each qubit. - """ - new_self = self - new_matrix_size = max(permutation) + 1 - - if self.num_qubits != len(permutation): - raise OpflowError("New index must be defined for each qubit of the operator.") - if self.num_qubits < new_matrix_size: - # pad the operator with identities - new_self = self._expand_dim(new_matrix_size - self.num_qubits) - qc = QuantumCircuit(new_matrix_size) - - # extend the indices to match the size of the new matrix - permutation = ( - list(filter(lambda x: x not in permutation, range(new_matrix_size))) + permutation - ) - - # decompose permutation into sequence of transpositions - transpositions = arithmetic.transpositions(permutation) - for trans in transpositions: - qc.swap(trans[0], trans[1]) - matrix = CircuitOp(qc).to_matrix() - return MatrixOp(matrix.transpose()) @ new_self @ MatrixOp(matrix) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - return self.primitive.data * self.coeff - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return prim_str - else: - return f"{self.coeff} * {prim_str}" - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - # For other ops' eval we return self.to_matrix_op() here, but that's unnecessary here. - if front is None: - return self - - # pylint: disable=cyclic-import - from ..list_ops import ListOp - from ..state_fns import StateFn, VectorStateFn, OperatorStateFn - - new_front = None - - # For now, always do this. If it's not performant, we can be more granular. - if not isinstance(front, OperatorBase): - front = StateFn(front, is_measurement=False) - - if isinstance(front, ListOp) and front.distributive: - new_front = front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - elif isinstance(front, OperatorStateFn): - new_front = OperatorStateFn(self.adjoint().compose(front.to_matrix_op()).compose(self)) - - elif isinstance(front, OperatorBase): - new_front = VectorStateFn(self.to_matrix() @ front.to_matrix()) - - return new_front - - def exp_i(self) -> OperatorBase: - """Return a ``CircuitOp`` equivalent to e^-iH for this operator H""" - return CircuitOp(HamiltonianGate(self.primitive, time=self.coeff)) - - # Op Conversions - - def to_matrix_op(self, massive: bool = False) -> "MatrixOp": - return self - - def to_instruction(self) -> Instruction: - return (self.coeff * self.primitive).to_instruction() diff --git a/qiskit/opflow/primitive_ops/pauli_op.py b/qiskit/opflow/primitive_ops/pauli_op.py deleted file mode 100644 index 51ec601297b7..000000000000 --- a/qiskit/opflow/primitive_ops/pauli_op.py +++ /dev/null @@ -1,356 +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. - -"""PauliOp Class""" - -from math import pi -from typing import Dict, List, Optional, Set, Union, cast -import numpy as np -from scipy.sparse import spmatrix - -from qiskit import QuantumCircuit -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.circuit.library import RXGate, RYGate, RZGate, XGate, YGate, ZGate -from qiskit.circuit.library.generalized_gates import PauliGate -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector -from qiskit.utils.deprecation import deprecate_func - - -class PauliOp(PrimitiveOp): - """Deprecated: Class for Operators backed by Terra's ``Pauli`` module.""" - - primitive: Pauli - - @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: - """ - Args: - primitive: The Pauli which defines the behavior of the underlying function. - coeff: A coefficient multiplying the primitive. - - Raises: - TypeError: invalid parameters. - """ - if not isinstance(primitive, Pauli): - raise TypeError(f"PauliOp can only be instantiated with Paulis, not {type(primitive)}") - - super().__init__(primitive, coeff=coeff) - - def primitive_strings(self) -> Set[str]: - return {"Pauli"} - - @property - def num_qubits(self) -> int: - return len(self.primitive) - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over operators with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - if isinstance(other, PauliOp) and self.primitive == other.primitive: - return PauliOp(self.primitive, coeff=self.coeff + other.coeff) - - # pylint: disable=cyclic-import - from .pauli_sum_op import PauliSumOp - - if ( - isinstance(other, PauliOp) - and isinstance(self.coeff, (int, float, complex)) - and isinstance(other.coeff, (int, float, complex)) - ): - return PauliSumOp( - SparsePauliOp(self.primitive, coeffs=[self.coeff]) - + SparsePauliOp(other.primitive, coeffs=[other.coeff]) - ) - - if isinstance(other, PauliSumOp) and isinstance(self.coeff, (int, float, complex)): - return PauliSumOp(SparsePauliOp(self.primitive, coeffs=[self.coeff])) + other - - return SummedOp([self, other]) - - def adjoint(self) -> "PauliOp": - return PauliOp(self.primitive.adjoint(), coeff=self.coeff.conjugate()) - - def equals(self, other: OperatorBase) -> bool: - if isinstance(other, PauliOp) and self.coeff == other.coeff: - return self.primitive == other.primitive - - # pylint: disable=cyclic-import - from .pauli_sum_op import PauliSumOp - - if isinstance(other, PauliSumOp): - return other == self - - return False - - def _expand_dim(self, num_qubits: int) -> "PauliOp": - return PauliOp(Pauli("I" * num_qubits).expand(self.primitive), coeff=self.coeff) - - def tensor(self, other: OperatorBase) -> OperatorBase: - # Both Paulis - if isinstance(other, PauliOp): - return PauliOp(self.primitive.tensor(other.primitive), coeff=self.coeff * other.coeff) - - # pylint: disable=cyclic-import - from .pauli_sum_op import PauliSumOp - - if isinstance(other, PauliSumOp): - new_primitive = SparsePauliOp(self.primitive).tensor(other.primitive) - return PauliSumOp(new_primitive, coeff=self.coeff * other.coeff) - - from .circuit_op import CircuitOp - - if isinstance(other, CircuitOp): - return self.to_circuit_op().tensor(other) - - return TensoredOp([self, other]) - - def permute(self, permutation: List[int]) -> "PauliOp": - """Permutes the sequence of Pauli matrices. - - Args: - permutation: A list defining where each Pauli should be permuted. The Pauli at index - j of the primitive should be permuted to position permutation[j]. - - Returns: - A new PauliOp representing the permuted operator. For operator (X ^ Y ^ Z) and - indices=[1,2,4], it returns (X ^ I ^ Y ^ Z ^ I). - - Raises: - OpflowError: if indices do not define a new index for each qubit. - """ - pauli_string = self.primitive.__str__() - length = max(permutation) + 1 # size of list must be +1 larger then its max index - new_pauli_list = ["I"] * length - if len(permutation) != self.num_qubits: - raise OpflowError( - "List of indices to permute must have the same size as Pauli Operator" - ) - for i, index in enumerate(permutation): - new_pauli_list[-index - 1] = pauli_string[-i - 1] - return PauliOp(Pauli("".join(new_pauli_list)), self.coeff) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(PauliOp, new_self) - - if front: - return other.compose(new_self) - - # Both Paulis - if isinstance(other, PauliOp): - product = new_self.primitive.dot(other.primitive) - return PrimitiveOp(product, coeff=new_self.coeff * other.coeff) - - # pylint: disable=cyclic-import - from .pauli_sum_op import PauliSumOp - - if isinstance(other, PauliSumOp): - return PauliSumOp( - SparsePauliOp(new_self.primitive).dot(other.primitive), - coeff=new_self.coeff * other.coeff, - ) - - # pylint: disable=cyclic-import - from ..state_fns.circuit_state_fn import CircuitStateFn - from .circuit_op import CircuitOp - - if isinstance(other, (CircuitOp, CircuitStateFn)): - return new_self.to_circuit_op().compose(other) - - return super(PauliOp, new_self).compose(other) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - return self.primitive.to_matrix() * self.coeff - - def to_spmatrix(self) -> spmatrix: - """Returns SciPy sparse matrix representation of the Operator. - - Returns: - CSR sparse matrix representation of the Operator. - - Raises: - ValueError: invalid parameters. - """ - return self.primitive.to_matrix(sparse=True) * self.coeff - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return prim_str - else: - return f"{self.coeff} * {prim_str}" - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: - return self.to_matrix_op() - - # pylint: disable=cyclic-import - from ..list_ops.list_op import ListOp - from ..state_fns.circuit_state_fn import CircuitStateFn - from ..state_fns.dict_state_fn import DictStateFn - from ..state_fns.state_fn import StateFn - from .circuit_op import CircuitOp - - new_front = None - - # For now, always do this. If it's not performant, we can be more granular. - if not isinstance(front, OperatorBase): - front = StateFn(front, is_measurement=False) - - if isinstance(front, ListOp) and front.distributive: - new_front = front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - else: - - if self.num_qubits != front.num_qubits: - raise ValueError( - "eval does not support operands with differing numbers of qubits, " - "{} and {}, respectively.".format(self.num_qubits, front.num_qubits) - ) - - if isinstance(front, DictStateFn): - - new_dict: Dict[str, complex] = {} - corrected_x_bits = self.primitive.x[::-1] - corrected_z_bits = self.primitive.z[::-1] - - for bstr, v in front.primitive.items(): - bitstr = np.fromiter(bstr, dtype=int).astype(bool) - new_b_str = np.logical_xor(bitstr, corrected_x_bits) - new_str = "".join(map(str, 1 * new_b_str)) - z_factor = np.prod(1 - 2 * np.logical_and(bitstr, corrected_z_bits)) - y_factor = np.prod( - np.sqrt(1 - 2 * np.logical_and(corrected_x_bits, corrected_z_bits) + 0j) - ) - new_dict[new_str] = (v * z_factor * y_factor) + new_dict.get(new_str, 0) - # The coefficient consists of: - # 1. the coefficient of *this* PauliOp (self) - # 2. the coefficient of the evaluated DictStateFn (front) - # 3. AND acquires the phase of the internal primitive. This is necessary to - # ensure that (X @ Z) and (-iY) return the same result. - new_front = StateFn( - new_dict, coeff=self.coeff * front.coeff * (-1j) ** self.primitive.phase - ) - - elif isinstance(front, StateFn) and front.is_measurement: - raise ValueError("Operator composed with a measurement is undefined.") - - # Composable types with PauliOp - elif isinstance(front, (PauliOp, CircuitOp, CircuitStateFn)): - new_front = self.compose(front) - - # Covers VectorStateFn and OperatorStateFn - elif isinstance(front, StateFn): - new_front = self.to_matrix_op().eval(front.to_matrix_op()) - - return new_front - - def exp_i(self) -> OperatorBase: - """Return a ``CircuitOp`` equivalent to e^-iH for this operator H.""" - # if only one qubit is significant, we can perform the evolution - corrected_x = self.primitive.x[::-1] - corrected_z = self.primitive.z[::-1] - sig_qubits = np.logical_or(corrected_x, corrected_z) - if np.sum(sig_qubits) == 0: - # e^I is just a global phase, but we can keep track of it! Should we? - # For now, just return identity - return PauliOp(self.primitive) - if np.sum(sig_qubits) == 1: - sig_qubit_index = sig_qubits.tolist().index(True) - coeff = ( - np.real(self.coeff) - if not isinstance(self.coeff, ParameterExpression) - else self.coeff - ) - - from .circuit_op import CircuitOp - - # Y rotation - if corrected_x[sig_qubit_index] and corrected_z[sig_qubit_index]: - rot_op = CircuitOp(RYGate(2 * coeff)) - # Z rotation - elif corrected_z[sig_qubit_index]: - rot_op = CircuitOp(RZGate(2 * coeff)) - # X rotation - elif corrected_x[sig_qubit_index]: - rot_op = CircuitOp(RXGate(2 * coeff)) - - # pylint: disable=cyclic-import - from ..operator_globals import I - - left_pad = I.tensorpower(sig_qubit_index) - right_pad = I.tensorpower(self.num_qubits - sig_qubit_index - 1) - # Need to use overloaded operators here in case left_pad == I^0 - return left_pad ^ rot_op ^ right_pad - else: - from ..evolutions.evolved_op import EvolvedOp - - return EvolvedOp(self) - - def to_circuit(self) -> QuantumCircuit: - - pauli = self.primitive.to_label()[-self.num_qubits :] - phase = self.primitive.phase - - qc = QuantumCircuit(self.num_qubits) - if pauli == "I" * self.num_qubits: - qc.global_phase = -phase * pi / 2 - return qc - - if self.num_qubits == 1: - if pauli != "I": - gate = {"X": XGate, "Y": YGate, "Z": ZGate}[pauli] - qc.append(gate(), [0]) - else: - gate = PauliGate(pauli) - qc.append(gate, range(self.num_qubits)) - - if not phase: - return qc - - qc.global_phase = -phase * pi / 2 - return qc - - def to_instruction(self) -> Instruction: - # TODO should we just do the following because performance of adding and deleting IGates - # doesn't matter? - # (Reduce removes extra IGates). - # return PrimitiveOp(self.primitive.to_instruction(), coeff=self.coeff).reduce() - - return self.primitive.to_instruction() - - def to_pauli_op(self, massive: bool = False) -> "PauliOp": - return self diff --git a/qiskit/opflow/primitive_ops/pauli_sum_op.py b/qiskit/opflow/primitive_ops/pauli_sum_op.py deleted file mode 100644 index 37815effb61a..000000000000 --- a/qiskit/opflow/primitive_ops/pauli_sum_op.py +++ /dev/null @@ -1,464 +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. - -"""PauliSumOp Class""" - -from collections import defaultdict -from typing import Dict, List, Optional, Set, Tuple, Union, cast -import numpy as np -from scipy.sparse import spmatrix - -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp -from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector -from qiskit.quantum_info.operators.custom_iterator import CustomIterator -from qiskit.utils.deprecation import deprecate_func - - -class PauliSumOp(PrimitiveOp): - """Deprecated: Class for Operators backed by Terra's ``SparsePauliOp`` class.""" - - primitive: SparsePauliOp - - @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: SparsePauliOp, - coeff: Union[complex, ParameterExpression] = 1.0, - grouping_type: str = "None", - ) -> None: - """ - Args: - primitive: The SparsePauliOp which defines the behavior of the underlying function. - coeff: A coefficient multiplying the primitive. - grouping_type: The type of grouping. If None, the operator is not grouped. - - Raises: - TypeError: invalid parameters. - """ - if not isinstance(primitive, SparsePauliOp): - raise TypeError( - f"PauliSumOp can only be instantiated with SparsePauliOp, not {type(primitive)}" - ) - - super().__init__(primitive, coeff=coeff) - self._grouping_type = grouping_type - - def primitive_strings(self) -> Set[str]: - return {"SparsePauliOp"} - - @property - def grouping_type(self) -> str: - """ - Returns: Type of Grouping - """ - return self._grouping_type - - @property - def num_qubits(self) -> int: - return self.primitive.num_qubits - - @property - def coeffs(self): - """Return the Pauli coefficients.""" - return self.coeff * self.primitive.coeffs - - @property - def settings(self) -> Dict: - """Return operator settings.""" - data = super().settings - data.update({"grouping_type": self._grouping_type}) - return data - - def matrix_iter(self, sparse=False): - """Return a matrix representation iterator. - - This is a lazy iterator that converts each term in the PauliSumOp - into a matrix as it is used. To convert to a single matrix use the - :meth:`to_matrix` method. - - Args: - sparse (bool): optionally return sparse CSR matrices if True, - otherwise return Numpy array matrices - (Default: False) - - Returns: - MatrixIterator: matrix iterator object for the PauliSumOp. - """ - - class MatrixIterator(CustomIterator): - """Matrix representation iteration and item access.""" - - def __repr__(self): - return f"" - - def __getitem__(self, key): - sumopcoeff = self.obj.coeff * self.obj.primitive.coeffs[key] - return sumopcoeff * self.obj.primitive.paulis[key].to_matrix(sparse=sparse) - - return MatrixIterator(self) - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - f"Sum of operators with different numbers of qubits, {self.num_qubits} and " - f"{other.num_qubits}, is not well defined" - ) - - if ( - isinstance(other, PauliSumOp) - and not isinstance(self.coeff, ParameterExpression) - and not isinstance(other.coeff, ParameterExpression) - ): - return PauliSumOp(self.coeff * self.primitive + other.coeff * other.primitive, coeff=1) - - if ( - isinstance(other, PauliOp) - and not isinstance(self.coeff, ParameterExpression) - and not isinstance(other.coeff, ParameterExpression) - ): - return PauliSumOp( - self.coeff * self.primitive + other.coeff * SparsePauliOp(other.primitive) - ) - - return SummedOp([self, other]) - - def mul(self, scalar: Union[complex, ParameterExpression]) -> OperatorBase: - if isinstance(scalar, (int, float, complex)) and scalar != 0: - return PauliSumOp(scalar * self.primitive, coeff=self.coeff) - - return PauliSumOp(self.primitive, coeff=self.coeff * scalar) - - def adjoint(self) -> "PauliSumOp": - return PauliSumOp(self.primitive.adjoint(), coeff=self.coeff.conjugate()) - - def equals(self, other: OperatorBase) -> bool: - self_reduced, other_reduced = self.reduce(), other.reduce() - - if isinstance(other_reduced, PauliOp): - other_reduced = PauliSumOp( - SparsePauliOp(other_reduced.primitive, coeffs=[other_reduced.coeff]) - ) - - if not isinstance(other_reduced, PauliSumOp): - return False - - if isinstance(self_reduced.coeff, ParameterExpression) or isinstance( - other_reduced.coeff, ParameterExpression - ): - return self_reduced.coeff == other_reduced.coeff and self_reduced.primitive.equiv( - other_reduced.primitive - ) - return len(self_reduced) == len(other_reduced) and self_reduced.primitive.equiv( - other_reduced.primitive - ) - - def _expand_dim(self, num_qubits: int) -> "PauliSumOp": - return PauliSumOp( - self.primitive.tensor(SparsePauliOp(Pauli("I" * num_qubits))), - coeff=self.coeff, - ) - - def tensor(self, other: OperatorBase) -> Union["PauliSumOp", TensoredOp]: - if isinstance(other, PauliSumOp): - return PauliSumOp( - self.primitive.tensor(other.primitive), - coeff=self.coeff * other.coeff, - ) - if isinstance(other, PauliOp): - return PauliSumOp( - self.primitive.tensor(other.primitive), - coeff=self.coeff * other.coeff, - ) - - return TensoredOp([self, other]) - - def permute(self, permutation: List[int]) -> "PauliSumOp": - """Permutes the sequence of ``PauliSumOp``. - - Args: - permutation: A list defining where each Pauli should be permuted. The Pauli at index - j of the primitive should be permuted to position permutation[j]. - - Returns: - A new PauliSumOp representing the permuted operator. For operator (X ^ Y ^ Z) and - indices=[1,2,4], it returns (X ^ I ^ Y ^ Z ^ I). - - Raises: - OpflowError: if indices do not define a new index for each qubit. - """ - set_perm = set(permutation) - if len(set_perm) != len(permutation) or any(index < 0 for index in set_perm): - raise OpflowError(f"List {permutation} is not a permutation.") - - if len(permutation) != self.num_qubits: - raise OpflowError( - "List of indices to permute must have the same size as Pauli Operator" - ) - length = max(permutation) + 1 - - if length > self.num_qubits: - spop = self.primitive.tensor(SparsePauliOp(Pauli("I" * (length - self.num_qubits)))) - else: - spop = self.primitive.copy() - - permutation = [i for i in range(length) if i not in permutation] + permutation - permu_arr = np.arange(length)[np.argsort(permutation)] - spop.paulis.x = spop.paulis.x[:, permu_arr] - spop.paulis.z = spop.paulis.z[:, permu_arr] - return PauliSumOp(spop, self.coeff) - - def compose( - self, - other: OperatorBase, - permutation: Optional[List[int]] = None, - front: bool = False, - ) -> OperatorBase: - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self = cast(PauliSumOp, new_self) - - if front: - return other.compose(new_self) - # If self is identity, just return other. - if not np.any(np.logical_or(new_self.primitive.paulis.x, new_self.primitive.paulis.z)): - return other * new_self.coeff * sum(new_self.primitive.coeffs) - - # Both PauliSumOps - if isinstance(other, PauliSumOp): - return PauliSumOp( - new_self.primitive.dot(other.primitive), - coeff=new_self.coeff * other.coeff, - ) - if isinstance(other, PauliOp): - other_primitive = SparsePauliOp(other.primitive) - return PauliSumOp( - new_self.primitive.dot(other_primitive), - coeff=new_self.coeff * other.coeff, - ) - - # pylint: disable=cyclic-import - from ..state_fns.circuit_state_fn import CircuitStateFn - from .circuit_op import CircuitOp - - if isinstance(other, (CircuitOp, CircuitStateFn)): - pauli_op = cast(Union[PauliOp, SummedOp], new_self.to_pauli_op()) - return pauli_op.to_circuit_op().compose(other) - - return super(PauliSumOp, new_self).compose(other) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive) - if isinstance(self.coeff, ParameterExpression): - return (self.primitive.to_matrix(sparse=True)).toarray() * self.coeff - return (self.primitive.to_matrix(sparse=True) * self.coeff).toarray() - - def __str__(self) -> str: - def format_sign(x): - return x.real if np.isreal(x) else x - - def format_number(x): - x = format_sign(x) - if isinstance(x, (int, float)) and x < 0: - return f"- {-x}" - return f"+ {x}" - - indent = "" if self.coeff == 1 else " " - prim_list = self.primitive.to_list() - if prim_list: - first = prim_list[0] - if isinstance(first[1], (int, float)) and first[1] < 0: - main_string = indent + f"- {-first[1].real} * {first[0]}" - else: - main_string = indent + f"{format_sign(first[1])} * {first[0]}" - - main_string += "".join([f"\n{indent}{format_number(c)} * {p}" for p, c in prim_list[1:]]) - return f"{main_string}" if self.coeff == 1 else f"{self.coeff} * (\n{main_string}\n)" - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: - return self.to_matrix_op() - - # pylint: disable=cyclic-import - from ..list_ops.list_op import ListOp - from ..state_fns.circuit_state_fn import CircuitStateFn - from ..state_fns.dict_state_fn import DictStateFn - from ..state_fns.state_fn import StateFn - from .circuit_op import CircuitOp - - # For now, always do this. If it's not performant, we can be more granular. - if not isinstance(front, OperatorBase): - front = StateFn(front, is_measurement=False) - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - else: - - if self.num_qubits != front.num_qubits: - raise ValueError( - "eval does not support operands with differing numbers of qubits, " - "{} and {}, respectively.".format(self.num_qubits, front.num_qubits) - ) - - if isinstance(front, DictStateFn): - new_dict: Dict[str, int] = defaultdict(int) - corrected_x_bits = self.primitive.paulis.x[:, ::-1] - corrected_z_bits = self.primitive.paulis.z[:, ::-1] - coeffs = self.primitive.coeffs - for bstr, v in front.primitive.items(): - bitstr = np.fromiter(bstr, dtype=int).astype(bool) - new_b_str = np.logical_xor(bitstr, corrected_x_bits) - new_str = ["".join([str(b) for b in bs]) for bs in new_b_str.astype(int)] - z_factor = np.prod(1 - 2 * np.logical_and(bitstr, corrected_z_bits), axis=1) - y_factor = np.prod( - np.sqrt(1 - 2 * np.logical_and(corrected_x_bits, corrected_z_bits) + 0j), - axis=1, - ) - for i, n_str in enumerate(new_str): - new_dict[n_str] += v * z_factor[i] * y_factor[i] * coeffs[i] - return DictStateFn(new_dict, coeff=self.coeff * front.coeff) - - elif isinstance(front, StateFn) and front.is_measurement: - raise ValueError("Operator composed with a measurement is undefined.") - - # Composable types with PauliOp - elif isinstance(front, (PauliSumOp, PauliOp, CircuitOp, CircuitStateFn)): - return self.compose(front).eval() - - # Covers VectorStateFn and OperatorStateFn - front = cast(StateFn, front) - return self.to_matrix_op().eval(front.to_matrix_op()) - - def exp_i(self) -> OperatorBase: - """Return a ``CircuitOp`` equivalent to e^-iH for this operator H.""" - # TODO: optimize for some special cases - from ..evolutions.evolved_op import EvolvedOp - - return EvolvedOp(self) - - def to_instruction(self) -> Instruction: - return self.to_matrix_op().to_circuit().to_instruction() # type: ignore - - def to_pauli_op(self, massive: bool = False) -> Union[PauliOp, SummedOp]: - def to_native(x): - return x.item() if isinstance(x, np.generic) else x - - if len(self.primitive) == 1: - return PauliOp( - Pauli((self.primitive.paulis.z[0], self.primitive.paulis.x[0])), - to_native(np.real_if_close(self.primitive.coeffs[0])) * self.coeff, - ) - coeffs = np.real_if_close(self.primitive.coeffs) - return SummedOp( - [ - PauliOp(pauli, to_native(coeff)) - for pauli, coeff in zip(self.primitive.paulis, coeffs) - ], - coeff=self.coeff, - ) - - def __getitem__(self, offset: Union[int, slice]) -> "PauliSumOp": - """Allows array-indexing style access to the ``PauliSumOp``. - - Args: - offset: The index of ``PauliSumOp``. - - Returns: - The ``PauliSumOp`` at index ``offset``, - """ - return PauliSumOp(self.primitive[offset], self.coeff) - - def __iter__(self): - for i in range(len(self)): - yield self[i] - - def __len__(self) -> int: - """Length of ``SparsePauliOp``. - - Returns: - An int equal to the length of SparsePauliOp. - """ - return len(self.primitive) - - def reduce(self, atol: Optional[float] = None, rtol: Optional[float] = None) -> "PauliSumOp": - """Simplify the primitive ``SparsePauliOp``. - - Args: - atol: Absolute tolerance for checking if coefficients are zero (Default: 1e-8). - rtol: Relative tolerance for checking if coefficients are zero (Default: 1e-5). - - Returns: - The simplified ``PauliSumOp``. - """ - if isinstance(self.coeff, (int, float, complex)): - primitive = self.coeff * self.primitive - return PauliSumOp(primitive.simplify(atol=atol, rtol=rtol)) - return PauliSumOp(self.primitive.simplify(atol=atol, rtol=rtol), self.coeff) - - def to_spmatrix(self) -> spmatrix: - """Returns SciPy sparse matrix representation of the ``PauliSumOp``. - - Returns: - CSR sparse matrix representation of the ``PauliSumOp``. - - Raises: - ValueError: invalid parameters. - """ - return self.primitive.to_matrix(sparse=True) * self.coeff - - @classmethod - def from_list( - cls, - pauli_list: List[Tuple[str, Union[complex, ParameterExpression]]], - coeff: Union[complex, ParameterExpression] = 1.0, - dtype: type = complex, - ) -> "PauliSumOp": - """Construct from a pauli_list with the form [(pauli_str, coeffs)] - - Args: - pauli_list: A list of Tuple of pauli_str and coefficient. - coeff: A coefficient multiplying the primitive. - dtype: The dtype to use to construct the internal SparsePauliOp. - Defaults to ``complex``. - - Returns: - The PauliSumOp constructed from the pauli_list. - """ - return cls(SparsePauliOp.from_list(pauli_list, dtype=dtype), coeff=coeff) - - def is_zero(self) -> bool: - """ - Return this operator is zero operator or not. - """ - op = self.reduce() - primitive: SparsePauliOp = op.primitive - return op.coeff == 1 and len(op) == 1 and primitive.coeffs[0] == 0 - - def is_hermitian(self): - return np.isreal(self.coeffs).all() and np.all(self.primitive.paulis.phase == 0) diff --git a/qiskit/opflow/primitive_ops/primitive_op.py b/qiskit/opflow/primitive_ops/primitive_op.py deleted file mode 100644 index eb31a1fa461b..000000000000 --- a/qiskit/opflow/primitive_ops/primitive_op.py +++ /dev/null @@ -1,324 +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. - -"""PrimitiveOp Class""" - -from typing import Dict, List, Optional, Set, Union, cast - -import numpy as np -import scipy.linalg -from scipy.sparse import spmatrix - -from qiskit import QuantumCircuit -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.opflow.operator_base import OperatorBase -from qiskit.quantum_info import Operator, Pauli, SparsePauliOp, Statevector -from qiskit.utils.deprecation import deprecate_func - - -class PrimitiveOp(OperatorBase): - r""" - Deprecated: A class for representing basic Operators, backed by Operator primitives from - Terra. This class (and inheritors) primarily serves to allow the underlying - primitives to "flow" - i.e. interoperability and adherence to the Operator formalism - - while the core computational logic mostly remains in the underlying primitives. - For example, we would not produce an interface in Terra in which - ``QuantumCircuit1 + QuantumCircuit2`` equaled the Operator sum of the circuit - unitaries, rather than simply appending the circuits. However, within the Operator - flow summing the unitaries is the expected behavior. - - Note that all mathematical methods are not in-place, meaning that they return a - new object, but the underlying primitives are not copied. - - """ - - def __init_subclass__(cls): - cls.__new__ = lambda cls, *args, **kwargs: super().__new__(cls) - - @staticmethod - # pylint: disable=unused-argument - def __new__( - cls, - primitive: Union[ - Instruction, QuantumCircuit, List, np.ndarray, spmatrix, Operator, Pauli, SparsePauliOp - ], - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> "PrimitiveOp": - """A factory method to produce the correct type of PrimitiveOp subclass - based on the primitive passed in. Primitive and coeff arguments are passed into - subclass's init() as-is automatically by new(). - - Args: - primitive: The operator primitive being wrapped. - coeff: A coefficient multiplying the primitive. - - Returns: - The appropriate PrimitiveOp subclass for ``primitive``. - - Raises: - TypeError: Unsupported primitive type passed. - """ - # pylint: disable=cyclic-import - if isinstance(primitive, (Instruction, QuantumCircuit)): - from .circuit_op import CircuitOp - - return super().__new__(CircuitOp) - - if isinstance(primitive, (list, np.ndarray, spmatrix, Operator)): - from .matrix_op import MatrixOp - - return super().__new__(MatrixOp) - - if isinstance(primitive, Pauli): - from .pauli_op import PauliOp - - return super().__new__(PauliOp) - - if isinstance(primitive, SparsePauliOp): - from .pauli_sum_op import PauliSumOp - - return super().__new__(PauliSumOp) - - raise TypeError( - "Unsupported primitive type {} passed into PrimitiveOp " - "factory constructor".format(type(primitive)) - ) - - @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: Union[QuantumCircuit, Operator, Pauli, SparsePauliOp, OperatorBase], - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> None: - """ - Args: - primitive: The operator primitive being wrapped. - coeff: A coefficient multiplying the primitive. - """ - super().__init__() - self._primitive = primitive - self._coeff = coeff - - @property - def primitive(self) -> Union[QuantumCircuit, Operator, Pauli, SparsePauliOp, OperatorBase]: - """The primitive defining the underlying function of the Operator. - - Returns: - The primitive object. - """ - return self._primitive - - @property - def coeff(self) -> Union[complex, ParameterExpression]: - """ - The scalar coefficient multiplying the Operator. - - Returns: - The coefficient. - """ - return self._coeff - - @property - def num_qubits(self) -> int: - raise NotImplementedError - - @property - def settings(self) -> Dict: - """Return operator settings.""" - return {"primitive": self._primitive, "coeff": self._coeff} - - def primitive_strings(self) -> Set[str]: - raise NotImplementedError - - def add(self, other: OperatorBase) -> OperatorBase: - raise NotImplementedError - - def adjoint(self) -> OperatorBase: - raise NotImplementedError - - def equals(self, other: OperatorBase) -> bool: - raise NotImplementedError - - def mul(self, scalar: Union[complex, ParameterExpression]) -> OperatorBase: - if not isinstance(scalar, (int, float, complex, ParameterExpression)): - raise ValueError( - "Operators can only be scalar multiplied by float or complex, not " - "{} of type {}.".format(scalar, type(scalar)) - ) - # Need to return self.__class__ in case the object is one of the inherited OpPrimitives - return self.__class__(self.primitive, coeff=self.coeff * scalar) - - def tensor(self, other: OperatorBase) -> OperatorBase: - raise NotImplementedError - - def tensorpower(self, other: int) -> Union[OperatorBase, int]: - # Hack to make Z^(I^0) work as intended. - if other == 0: - return 1 - if not isinstance(other, int) or other < 0: - raise TypeError("Tensorpower can only take positive int arguments") - temp = PrimitiveOp(self.primitive, coeff=self.coeff) # type: OperatorBase - for _ in range(other - 1): - temp = temp.tensor(self) - return temp - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - # pylint: disable=cyclic-import - from ..list_ops.composed_op import ComposedOp - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - if isinstance(other, ComposedOp): - comp_with_first = new_self.compose(other.oplist[0]) - if not isinstance(comp_with_first, ComposedOp): - new_oplist = [comp_with_first] + other.oplist[1:] - return ComposedOp(new_oplist, coeff=other.coeff) - return ComposedOp([new_self] + other.oplist, coeff=other.coeff) - - return ComposedOp([new_self, other]) - - def _expand_dim(self, num_qubits: int) -> OperatorBase: - raise NotImplementedError - - def permute(self, permutation: List[int]) -> OperatorBase: - raise NotImplementedError - - def exp_i(self) -> OperatorBase: - """Return Operator exponentiation, equaling e^(-i * op)""" - # pylint: disable=cyclic-import - from ..evolutions.evolved_op import EvolvedOp - - return EvolvedOp(self) - - def log_i(self, massive: bool = False) -> OperatorBase: - """Return a ``MatrixOp`` equivalent to log(H)/-i for this operator H. This - function is the effective inverse of exp_i, equivalent to finding the Hermitian - Operator which produces self when exponentiated.""" - # pylint: disable=cyclic-import - from ..operator_globals import EVAL_SIG_DIGITS - from .matrix_op import MatrixOp - - return MatrixOp( - np.around( - scipy.linalg.logm(self.to_matrix(massive=massive)) / -1j, decimals=EVAL_SIG_DIGITS - ) - ) - - def __str__(self) -> str: - raise NotImplementedError - - def __repr__(self) -> str: - return f"{type(self).__name__}({repr(self.primitive)}, coeff={self.coeff})" - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - raise NotImplementedError - - @property - def parameters(self): - params = set() - if isinstance(self.primitive, (OperatorBase, QuantumCircuit)): - params.update(self.primitive.parameters) - if isinstance(self.coeff, ParameterExpression): - params.update(self.coeff.parameters) - return params - - def assign_parameters(self, param_dict: dict) -> OperatorBase: - param_value = self.coeff - if isinstance(self.coeff, ParameterExpression): - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - # pylint: disable=cyclic-import - from ..list_ops.list_op import ListOp - - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if self.coeff.parameters <= set(unrolled_dict.keys()): - binds = {param: unrolled_dict[param] for param in self.coeff.parameters} - param_value = complex(self.coeff.bind(binds)) - if abs(param_value.imag) == 0: - param_value = param_value.real - return self.__class__(self.primitive, coeff=param_value) - - # Nothing to collapse here. - def reduce(self) -> OperatorBase: - return self - - def to_matrix(self, massive: bool = False) -> np.ndarray: - raise NotImplementedError - - def to_matrix_op(self, massive: bool = False) -> OperatorBase: - """Returns a ``MatrixOp`` equivalent to this Operator.""" - coeff = self.coeff - op = self.copy() - op._coeff = 1 - prim_mat = op.to_matrix(massive=massive) - from .matrix_op import MatrixOp - - return MatrixOp(prim_mat, coeff=coeff) - - def to_instruction(self) -> Instruction: - """Returns an ``Instruction`` equivalent to this Operator.""" - raise NotImplementedError - - def to_circuit(self) -> QuantumCircuit: - """Returns a ``QuantumCircuit`` equivalent to this Operator.""" - qc = QuantumCircuit(self.num_qubits) - qc.append(self.to_instruction(), qargs=range(self.primitive.num_qubits)) - return qc.decompose() - - def to_circuit_op(self) -> OperatorBase: - """Returns a ``CircuitOp`` equivalent to this Operator.""" - from .circuit_op import CircuitOp - - if self.coeff == 0: - return CircuitOp(QuantumCircuit(self.num_qubits), coeff=0) - return CircuitOp(self.to_circuit(), coeff=self.coeff) - - def to_pauli_op(self, massive: bool = False) -> OperatorBase: - """Returns a sum of ``PauliOp`` s equivalent to this Operator.""" - # pylint: disable=cyclic-import - from .matrix_op import MatrixOp - - mat_op = cast(MatrixOp, self.to_matrix_op(massive=massive)) - sparse_pauli = SparsePauliOp.from_operator(mat_op.primitive) - if not sparse_pauli.to_list(): - from ..operator_globals import I - - return (I ^ self.num_qubits) * 0.0 - from .pauli_op import PauliOp - - if len(sparse_pauli) == 1: - label, coeff = sparse_pauli.to_list()[0] - coeff = coeff.real if np.isreal(coeff) else coeff - return PauliOp(Pauli(label), coeff * self.coeff) - - from ..list_ops.summed_op import SummedOp - - return SummedOp( - [ - PrimitiveOp( - Pauli(label), - coeff.real if coeff == coeff.real else coeff, - ) - for (label, coeff) in sparse_pauli.to_list() - ], - self.coeff, - ) diff --git a/qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py b/qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py deleted file mode 100644 index f603a9fbb364..000000000000 --- a/qiskit/opflow/primitive_ops/tapered_pauli_sum_op.py +++ /dev/null @@ -1,590 +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. - -"""TaperedPauliSumOp Class and Z2Symmetries""" - -import itertools -import logging -from copy import deepcopy -from typing import Dict, List, Optional, Union, cast - -import numpy as np - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.utils import commutator -from qiskit.quantum_info import Pauli, SparsePauliOp -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class TaperedPauliSumOp(PauliSumOp): - """Deprecated: Class for PauliSumOp after tapering""" - - @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: SparsePauliOp, - z2_symmetries: "Z2Symmetries", - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> None: - """ - Args: - primitive: The SparsePauliOp which defines the behavior of the underlying function. - z2_symmetries: Z2 symmetries which the Operator has. - coeff: A coefficient multiplying the primitive. - - Raises: - TypeError: invalid parameters. - """ - super().__init__(primitive, coeff) - if not isinstance(z2_symmetries, Z2Symmetries): - raise TypeError( - f"Argument parameter z2_symmetries must be Z2Symmetries, not {type(z2_symmetries)}" - ) - self._z2_symmetries = z2_symmetries - - @property - def z2_symmetries(self) -> "Z2Symmetries": - """ - Z2 symmetries which the Operator has. - - Returns: - The Z2 Symmetries. - """ - return self._z2_symmetries - - @property - def settings(self) -> Dict: - """Return operator settings.""" - return { - "primitive": self._primitive, - "z2_symmetries": self._z2_symmetries, - "coeff": self._coeff, - } - - def assign_parameters(self, param_dict: dict) -> OperatorBase: - pauli_sum = PauliSumOp(self.primitive, self.coeff) - return pauli_sum.assign_parameters(param_dict) - - -class Z2Symmetries: - """Deprecated: Z2 Symmetries""" - - @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, - symmetries: List[Pauli], - sq_paulis: List[Pauli], - sq_list: List[int], - tapering_values: Optional[List[int]] = None, - tol: float = 1e-14, - ): - """ - Args: - symmetries: the list of Pauli objects representing the Z_2 symmetries - sq_paulis: the list of single - qubit Pauli objects to construct the - Clifford operators - sq_list: the list of support of the single-qubit Pauli objects used to build - the Clifford operators - tapering_values: values determines the sector. - tol: Tolerance threshold for ignoring real and complex parts of a coefficient. - - Raises: - OpflowError: Invalid paulis - """ - if len(symmetries) != len(sq_paulis): - raise OpflowError( - "Number of Z2 symmetries has to be the same as number of single-qubit pauli x." - ) - - if len(sq_paulis) != len(sq_list): - raise OpflowError( - "Number of single-qubit pauli x has to be the same as length of single-qubit list." - ) - - if tapering_values is not None: - if len(sq_list) != len(tapering_values): - raise OpflowError( - "The length of single-qubit list has " - "to be the same as length of tapering values." - ) - - self._symmetries = symmetries - self._sq_paulis = sq_paulis - self._sq_list = sq_list - self._tapering_values = tapering_values - self._tol = tol - - @property - def tol(self): - """Tolerance threshold for ignoring real and complex parts of a coefficient.""" - return self._tol - - @tol.setter - def tol(self, value): - """Set the tolerance threshold for ignoring real and complex parts of a coefficient.""" - self._tol = value - - @property - def symmetries(self): - """return symmetries""" - return self._symmetries - - @property - def sq_paulis(self): - """returns sq paulis""" - return self._sq_paulis - - @property - def cliffords(self) -> List[PauliSumOp]: - """ - Get clifford operators, build based on symmetries and single-qubit X. - Returns: - a list of unitaries used to diagonalize the Hamiltonian. - """ - cliffords = [ - (PauliOp(pauli_symm) + PauliOp(sq_pauli)) / np.sqrt(2) - for pauli_symm, sq_pauli in zip(self._symmetries, self._sq_paulis) - ] - return cliffords - - @property - def sq_list(self): - """returns sq list""" - return self._sq_list - - @property - def tapering_values(self): - """returns tapering values""" - return self._tapering_values - - @tapering_values.setter - def tapering_values(self, new_value): - """set tapering values""" - self._tapering_values = new_value - - @property - def settings(self) -> Dict: - """Return operator settings.""" - return { - "symmetries": self._symmetries, - "sq_paulis": self._sq_paulis, - "sq_list": self._sq_list, - "tapering_values": self._tapering_values, - } - - def __str__(self): - ret = ["Z2 symmetries:"] - ret.append("Symmetries:") - for symmetry in self._symmetries: - ret.append(symmetry.to_label()) - ret.append("Single-Qubit Pauli X:") - for x in self._sq_paulis: - ret.append(x.to_label()) - ret.append("Cliffords:") - for c in self.cliffords: - ret.append(str(c)) - ret.append("Qubit index:") - ret.append(str(self._sq_list)) - ret.append("Tapering values:") - if self._tapering_values is None: - possible_values = [ - str(list(coeff)) for coeff in itertools.product([1, -1], repeat=len(self._sq_list)) - ] - possible_values = ", ".join(x for x in possible_values) - ret.append(" - Possible values: " + possible_values) - else: - ret.append(str(self._tapering_values)) - - ret = "\n".join(ret) - return ret - - def copy(self) -> "Z2Symmetries": - """ - Get a copy of self. - Returns: - copy - """ - return deepcopy(self) - - def is_empty(self) -> bool: - """ - Check the z2_symmetries is empty or not. - Returns: - Empty or not - """ - return self._symmetries == [] or self._sq_paulis == [] or self._sq_list == [] - - # pylint: disable=invalid-name - @classmethod - def find_Z2_symmetries(cls, operator: PauliSumOp) -> "Z2Symmetries": - """ - Finds Z2 Pauli-type symmetries of an Operator. - - Returns: - a z2_symmetries object contains symmetries, single-qubit X, single-qubit list. - """ - pauli_symmetries = [] - sq_paulis = [] - sq_list = [] - - stacked_paulis = [] - - if operator.is_zero(): - logger.info("Operator is empty.") - return cls([], [], [], None) - - for pauli in operator: - stacked_paulis.append( - np.concatenate( - (pauli.primitive.paulis.x[0], pauli.primitive.paulis.z[0]), axis=0 - ).astype(int) - ) - - stacked_matrix = np.array(np.stack(stacked_paulis)) - symmetries = _kernel_F2(stacked_matrix) - - if not symmetries: - logger.info("No symmetry is found.") - return cls([], [], [], None) - - stacked_symmetries = np.stack(symmetries) - symm_shape = stacked_symmetries.shape - - for row in range(symm_shape[0]): - - pauli_symmetries.append( - Pauli( - ( - stacked_symmetries[row, : symm_shape[1] // 2], - stacked_symmetries[row, symm_shape[1] // 2 :], - ) - ) - ) - - stacked_symm_del = np.delete(stacked_symmetries, row, axis=0) - for col in range(symm_shape[1] // 2): - # case symmetries other than one at (row) have Z or I on col qubit - Z_or_I = True - for symm_idx in range(symm_shape[0] - 1): - if not ( - stacked_symm_del[symm_idx, col] == 0 - and stacked_symm_del[symm_idx, col + symm_shape[1] // 2] in (0, 1) - ): - Z_or_I = False - if Z_or_I: - if ( - stacked_symmetries[row, col] == 1 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 0 - ) or ( - stacked_symmetries[row, col] == 1 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 1 - ): - sq_paulis.append( - Pauli((np.zeros(symm_shape[1] // 2), np.zeros(symm_shape[1] // 2))) - ) - sq_paulis[row].z[col] = False - sq_paulis[row].x[col] = True - sq_list.append(col) - break - - # case symmetries other than one at (row) have X or I on col qubit - X_or_I = True - for symm_idx in range(symm_shape[0] - 1): - if not ( - stacked_symm_del[symm_idx, col] in (0, 1) - and stacked_symm_del[symm_idx, col + symm_shape[1] // 2] == 0 - ): - X_or_I = False - if X_or_I: - if ( - stacked_symmetries[row, col] == 0 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 1 - ) or ( - stacked_symmetries[row, col] == 1 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 1 - ): - sq_paulis.append( - Pauli((np.zeros(symm_shape[1] // 2), np.zeros(symm_shape[1] // 2))) - ) - sq_paulis[row].z[col] = True - sq_paulis[row].x[col] = False - sq_list.append(col) - break - - # case symmetries other than one at (row) have Y or I on col qubit - Y_or_I = True - for symm_idx in range(symm_shape[0] - 1): - if not ( - ( - stacked_symm_del[symm_idx, col] == 1 - and stacked_symm_del[symm_idx, col + symm_shape[1] // 2] == 1 - ) - or ( - stacked_symm_del[symm_idx, col] == 0 - and stacked_symm_del[symm_idx, col + symm_shape[1] // 2] == 0 - ) - ): - Y_or_I = False - if Y_or_I: - if ( - stacked_symmetries[row, col] == 0 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 1 - ) or ( - stacked_symmetries[row, col] == 1 - and stacked_symmetries[row, col + symm_shape[1] // 2] == 0 - ): - sq_paulis.append( - Pauli((np.zeros(symm_shape[1] // 2), np.zeros(symm_shape[1] // 2))) - ) - sq_paulis[row].z[col] = True - sq_paulis[row].x[col] = True - sq_list.append(col) - break - - return cls(pauli_symmetries, sq_paulis, sq_list, None) - - def convert_clifford(self, operator: PauliSumOp) -> OperatorBase: - """This method operates the first part of the tapering. - It converts the operator by composing it with the clifford unitaries defined in the current - symmetry. - - Args: - operator: to-be-tapered operator - - Returns: - :class:`PauliSumOp` corresponding to the converted operator. - - Raises: - OpflowError: Z2 symmetries, single qubit pauli and single qubit list cannot be empty - - """ - - if not self._symmetries or not self._sq_paulis or not self._sq_list: - raise OpflowError( - "Z2 symmetries, single qubit pauli and single qubit list cannot be empty." - ) - - if not operator.is_zero(): - for clifford in self.cliffords: - operator = cast(PauliSumOp, clifford @ operator @ clifford) - operator = operator.reduce(atol=0) - - return operator - - def taper_clifford(self, operator: PauliSumOp) -> OperatorBase: - """This method operates the second part of the tapering. - This function assumes that the input operators have already been transformed using - :meth:`convert_clifford`. The redundant qubits due to the symmetries are dropped and - replaced by their two possible eigenvalues. - The `tapering_values` will be stored into the resulted operator for a record. - - Args: - operator: Partially tapered operator resulting from a call to :meth:`convert_clifford` - - Returns: - If tapering_values is None: [:class:`PauliSumOp`]; otherwise, :class:`PauliSumOp` - - Raises: - OpflowError: Z2 symmetries, single qubit pauli and single qubit list cannot be empty - - """ - - if not self._symmetries or not self._sq_paulis or not self._sq_list: - raise OpflowError( - "Z2 symmetries, single qubit pauli and single qubit list cannot be empty." - ) - # If the operator is zero then we can skip the following. We still need to taper the - # operator to reduce its size i.e. the number of qubits so for example 0*"IIII" could - # taper to 0*"II" when symmetries remove two qubits. - if self._tapering_values is None: - tapered_ops_list = [ - self._taper(operator, list(coeff)) - for coeff in itertools.product([1, -1], repeat=len(self._sq_list)) - ] - tapered_ops: OperatorBase = ListOp(tapered_ops_list) - else: - tapered_ops = self._taper(operator, self._tapering_values) - - return tapered_ops - - def taper(self, operator: PauliSumOp) -> OperatorBase: - """ - Taper an operator based on the z2_symmetries info and sector defined by `tapering_values`. - The `tapering_values` will be stored into the resulted operator for a record. - - The tapering is a two-step algorithm which first converts the operator into a - :class:`PauliSumOp` with same eigenvalues but where some qubits are only acted upon - with the Pauli operators I or X. - The number M of these redundant qubits is equal to the number M of identified symmetries. - - The second step of the reduction consists in replacing these qubits with the possible - eigenvalues of the corresponding Pauli X, giving 2^M new operators with M less qubits. - If an eigenvalue sector was previously identified for the solution, then this reduces to - 1 new operator with M less qubits. - - Args: - operator: the to-be-tapered operator - - Returns: - If tapering_values is None: [:class:`PauliSumOp`]; otherwise, :class:`PauliSumOp` - - Raises: - OpflowError: Z2 symmetries, single qubit pauli and single qubit list cannot be empty - - """ - - if not self._symmetries or not self._sq_paulis or not self._sq_list: - raise OpflowError( - "Z2 symmetries, single qubit pauli and single qubit list cannot be empty." - ) - - converted_ops = self.convert_clifford(operator) - tapered_ops = self.taper_clifford(converted_ops) - - return tapered_ops - - def _taper(self, op: PauliSumOp, curr_tapering_values: List[int]) -> OperatorBase: - pauli_list = [] - for pauli_term in op: - coeff_out = pauli_term.primitive.coeffs[0] - for idx, qubit_idx in enumerate(self._sq_list): - if ( - pauli_term.primitive.paulis.z[0, qubit_idx] - or pauli_term.primitive.paulis.x[0, qubit_idx] - ): - coeff_out = curr_tapering_values[idx] * coeff_out - z_temp = np.delete(pauli_term.primitive.paulis.z[0].copy(), np.asarray(self._sq_list)) - x_temp = np.delete(pauli_term.primitive.paulis.x[0].copy(), np.asarray(self._sq_list)) - pauli_list.append((Pauli((z_temp, x_temp)).to_label(), coeff_out)) - - spo = SparsePauliOp.from_list(pauli_list).simplify(atol=0.0) - spo = spo.chop(self.tol) - z2_symmetries = self.copy() - z2_symmetries.tapering_values = curr_tapering_values - - return TaperedPauliSumOp(spo, z2_symmetries) - - def consistent_tapering(self, operator: PauliSumOp) -> OperatorBase: - """ - Tapering the `operator` with the same manner of how this tapered operator - is created. i.e., using the same Cliffords and tapering values. - - Args: - operator: the to-be-tapered operator - - Returns: - The tapered operator - - Raises: - OpflowError: The given operator does not commute with the symmetry - """ - for symmetry in self._symmetries: - commutator_op = cast(PauliSumOp, commutator(operator, PauliOp(symmetry))) - if not commutator_op.is_zero(): - raise OpflowError( - "The given operator does not commute with the symmetry, can not taper it." - ) - - return self.taper(operator) - - def __eq__(self, other: object) -> bool: - """ - Overload `==` operation to evaluate equality between Z2Symmetries. - - Args: - other: The `Z2Symmetries` to compare to self. - - Returns: - A bool equal to the equality of self and other. - """ - if not isinstance(other, Z2Symmetries): - return False - - return ( - self.symmetries == other.symmetries - and self.sq_paulis == other.sq_paulis - and self.sq_list == other.sq_list - and self.tapering_values == other.tapering_values - ) - - -def _kernel_F2(matrix_in) -> List[np.ndarray]: # pylint: disable=invalid-name - """ - Computes the kernel of a binary matrix on the binary finite field - Args: - matrix_in (numpy.ndarray): binary matrix - Returns: - The list of kernel vectors - """ - size = matrix_in.shape - kernel = [] - matrix_in_id = np.vstack((matrix_in, np.identity(size[1]))) - matrix_in_id_ech = (_row_echelon_F2(matrix_in_id.transpose())).transpose() - - for col in range(size[1]): - if np.array_equal( - matrix_in_id_ech[0 : size[0], col], np.zeros(size[0]) - ) and not np.array_equal(matrix_in_id_ech[size[0] :, col], np.zeros(size[1])): - kernel.append(matrix_in_id_ech[size[0] :, col]) - - return kernel - - -def _row_echelon_F2(matrix_in) -> np.ndarray: # pylint: disable=invalid-name - """ - Computes the row Echelon form of a binary matrix on the binary finite field - Args: - matrix_in (numpy.ndarray): binary matrix - Returns: - Matrix_in in Echelon row form - """ - size = matrix_in.shape - - for i in range(size[0]): - pivot_index = 0 - for j in range(size[1]): - if matrix_in[i, j] == 1: - pivot_index = j - break - for k in range(size[0]): - if k != i and matrix_in[k, pivot_index] == 1: - matrix_in[k, :] = np.mod(matrix_in[k, :] + matrix_in[i, :], 2) - - matrix_out_temp = deepcopy(matrix_in) - indices = [] - matrix_out = np.zeros(size) - - for i in range(size[0] - 1): - if np.array_equal(matrix_out_temp[i, :], np.zeros(size[1])): - indices.append(i) - for row in np.sort(indices)[::-1]: - matrix_out_temp = np.delete(matrix_out_temp, (row), axis=0) - - matrix_out[0 : size[0] - len(indices), :] = matrix_out_temp - matrix_out = matrix_out.astype(int) - - return matrix_out diff --git a/qiskit/opflow/state_fns/__init__.py b/qiskit/opflow/state_fns/__init__.py deleted file mode 100644 index 69b7d960bc20..000000000000 --- a/qiskit/opflow/state_fns/__init__.py +++ /dev/null @@ -1,78 +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. - -""" -State Functions (:mod:`qiskit.opflow.state_fns`) -================================================ - -.. deprecated:: 0.24.0 - - The :mod:`qiskit.opflow` module is deprecated and will be removed no earlier - than 3 months after the release date. For code migration guidelines, - visit https://qisk.it/opflow_migration. - -State functions are defined to be complex functions over a single binary -string (as compared to an operator, which is defined as a function over two binary strings, -or a function taking a binary function to another binary function). This function may be -called by the eval() method. - -Measurements are defined to be functionals over StateFns, taking them to real values. -Generally, this real value is interpreted to represent the probability of some classical -state (binary string) being observed from a probabilistic or quantum system represented -by a StateFn. This leads to the equivalent definition, which is that a measurement m is -a function over binary strings producing StateFns, such that the probability of measuring -a given binary string b from a system with StateFn f is equal to the inner -product between f and m(b). - -Note: - All mathematical methods between StateFns are not in-place, meaning that they return a - new object, but the underlying primitives are not copied. - -Note: - State functions here are not restricted to wave functions, as there is - no requirement of normalization. - -.. currentmodule:: qiskit.opflow.state_fns - -State Functions ---------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - StateFn - CircuitStateFn - DictStateFn - VectorStateFn - SparseVectorStateFn - OperatorStateFn - CVaRMeasurement - -""" - -from .state_fn import StateFn -from .dict_state_fn import DictStateFn -from .operator_state_fn import OperatorStateFn -from .vector_state_fn import VectorStateFn -from .sparse_vector_state_fn import SparseVectorStateFn -from .circuit_state_fn import CircuitStateFn -from .cvar_measurement import CVaRMeasurement - -__all__ = [ - "StateFn", - "DictStateFn", - "VectorStateFn", - "CircuitStateFn", - "OperatorStateFn", - "CVaRMeasurement", -] diff --git a/qiskit/opflow/state_fns/circuit_state_fn.py b/qiskit/opflow/state_fns/circuit_state_fn.py deleted file mode 100644 index 6a4aa3a2b5e3..000000000000 --- a/qiskit/opflow/state_fns/circuit_state_fn.py +++ /dev/null @@ -1,404 +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. - -"""CircuitStateFn Class""" - - -from typing import Dict, List, Optional, Set, Union, cast - -import numpy as np - -from qiskit import BasicAer, ClassicalRegister, QuantumCircuit, transpile -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.circuit.exceptions import CircuitError -from qiskit.circuit.library import IGate, StatePreparation -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.composed_op import ComposedOp -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.circuit_op import CircuitOp -from qiskit.opflow.primitive_ops.matrix_op import MatrixOp -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.opflow.state_fns.vector_state_fn import VectorStateFn -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class CircuitStateFn(StateFn): - r""" - Deprecated: A class for state functions and measurements which are defined by the action of a - QuantumCircuit starting from \|0⟩, and stored using Terra's ``QuantumCircuit`` class. - """ - primitive: QuantumCircuit - - # 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__( - self, - primitive: Union[QuantumCircuit, Instruction] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - from_operator: bool = False, - ) -> None: - """ - Args: - primitive: The ``QuantumCircuit`` (or ``Instruction``, which will be converted) which - defines the behavior of the underlying function. - coeff: A coefficient multiplying the state function. - is_measurement: Whether the StateFn is a measurement operator. - from_operator: if True the StateFn is derived from OperatorStateFn. (Default: False) - - Raises: - TypeError: Unsupported primitive, or primitive has ClassicalRegisters. - """ - if isinstance(primitive, Instruction): - qc = QuantumCircuit(primitive.num_qubits) - qc.append(primitive, qargs=range(primitive.num_qubits)) - primitive = qc - - if not isinstance(primitive, QuantumCircuit): - raise TypeError( - "CircuitStateFn can only be instantiated " - "with QuantumCircuit, not {}".format(type(primitive)) - ) - - if len(primitive.clbits) != 0: - raise TypeError("CircuitOp does not support QuantumCircuits with ClassicalRegisters.") - - super().__init__(primitive, coeff=coeff, is_measurement=is_measurement) - - self.from_operator = from_operator - - @staticmethod - def from_dict(density_dict: dict) -> "CircuitStateFn": - """Construct the CircuitStateFn from a dict mapping strings to probability densities. - - Args: - density_dict: The dict representing the desired state. - - Returns: - The CircuitStateFn created from the dict. - """ - # If the dict is sparse (elements <= qubits), don't go - # building a statevector to pass to Qiskit's - # initializer, just create a sum. - if len(density_dict) <= len(list(density_dict.keys())[0]): - statefn_circuits = [] - for bstr, prob in density_dict.items(): - qc = QuantumCircuit(len(bstr)) - # NOTE: Reversing endianness!! - for (index, bit) in enumerate(reversed(bstr)): - if bit == "1": - qc.x(index) - sf_circuit = CircuitStateFn(qc, coeff=prob) - statefn_circuits += [sf_circuit] - if len(statefn_circuits) == 1: - return statefn_circuits[0] - else: - return cast(CircuitStateFn, SummedOp(cast(List[OperatorBase], statefn_circuits))) - else: - sf_dict = StateFn(density_dict) - return CircuitStateFn.from_vector(sf_dict.to_matrix()) - - @staticmethod - def from_vector(statevector: np.ndarray) -> "CircuitStateFn": - """Construct the CircuitStateFn from a vector representing the statevector. - - Args: - statevector: The statevector representing the desired state. - - Returns: - The CircuitStateFn created from the vector. - """ - normalization_coeff = np.linalg.norm(statevector) - normalized_sv = statevector / normalization_coeff - return CircuitStateFn(StatePreparation(normalized_sv), coeff=normalization_coeff) - - def primitive_strings(self) -> Set[str]: - return {"QuantumCircuit"} - - @property - def settings(self) -> Dict: - """Return settings.""" - data = super().settings - data["from_operator"] = self.from_operator - return data - - @property - def num_qubits(self) -> int: - return self.primitive.num_qubits - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over operators with different numbers of qubits, " - "{} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - if isinstance(other, CircuitStateFn) and self.primitive == other.primitive: - return CircuitStateFn(self.primitive, coeff=self.coeff + other.coeff) - - # Covers all else. - return SummedOp([self, other]) - - def adjoint(self) -> "CircuitStateFn": - try: - inverse = self.primitive.inverse() - except CircuitError as missing_inverse: - raise OpflowError( - "Failed to take the inverse of the underlying circuit, the circuit " - "is likely not unitary and can therefore not be inverted." - ) from missing_inverse - - return CircuitStateFn( - inverse, coeff=self.coeff.conjugate(), is_measurement=(not self.is_measurement) - ) - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - if not self.is_measurement and not front: - raise ValueError( - "Composition with a Statefunctions in the first operand is not defined." - ) - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - new_self.from_operator = self.from_operator - - if front: - return other.compose(new_self) - - if isinstance(other, (PauliOp, CircuitOp, MatrixOp)): - op_circuit_self = CircuitOp(self.primitive) - - # Avoid reimplementing compose logic - composed_op_circs = cast(CircuitOp, op_circuit_self.compose(other.to_circuit_op())) - - # Returning CircuitStateFn - return CircuitStateFn( - composed_op_circs.primitive, - is_measurement=self.is_measurement, - coeff=self.coeff * other.coeff, - from_operator=self.from_operator, - ) - - if isinstance(other, CircuitStateFn) and self.is_measurement: - # pylint: disable=cyclic-import - from ..operator_globals import Zero - - return self.compose(CircuitOp(other.primitive)).compose( - (Zero ^ self.num_qubits) * other.coeff - ) - - return ComposedOp([new_self, other]) - - def tensor(self, other: OperatorBase) -> Union["CircuitStateFn", TensoredOp]: - r""" - Return tensor product between self and other, overloaded by ``^``. - Note: You must be conscious of Qiskit's big-endian bit printing convention. - Meaning, Plus.tensor(Zero) - produces a \|+⟩ on qubit 0 and a \|0⟩ on qubit 1, or \|+⟩⨂\|0⟩, but would produce - a QuantumCircuit like: - - \|0⟩-- - \|+⟩-- - - Because Terra prints circuits and results with qubit 0 at the end of the string or circuit. - - Args: - other: The ``OperatorBase`` to tensor product with self. - - Returns: - An ``OperatorBase`` equivalent to the tensor product of self and other. - """ - if isinstance(other, CircuitStateFn) and other.is_measurement == self.is_measurement: - # Avoid reimplementing tensor, just use CircuitOp's - c_op_self = CircuitOp(self.primitive, self.coeff) - c_op_other = CircuitOp(other.primitive, other.coeff) - c_op = c_op_self.tensor(c_op_other) - if isinstance(c_op, CircuitOp): - return CircuitStateFn( - primitive=c_op.primitive, - coeff=c_op.coeff, - is_measurement=self.is_measurement, - ) - return TensoredOp([self, other]) - - def to_density_matrix(self, massive: bool = False) -> np.ndarray: - """ - Return numpy matrix of density operator, warn if more than 16 qubits to - force the user to set - massive=True if they want such a large matrix. Generally big methods like this - should require the use of a - converter, but in this case a convenience method for quick hacking and access - to classical tools is - appropriate. - """ - OperatorBase._check_massive("to_density_matrix", True, self.num_qubits, massive) - # Rely on VectorStateFn's logic here. - return VectorStateFn(self.to_matrix(massive=massive) * self.coeff).to_density_matrix() - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", False, self.num_qubits, massive) - - # Need to adjoint to get forward statevector and then reverse - if self.is_measurement: - return np.conj(self.adjoint().to_matrix(massive=massive)) - qc = self.to_circuit(meas=False) - statevector_backend = BasicAer.get_backend("statevector_simulator") - transpiled = transpile(qc, statevector_backend, optimization_level=0) - statevector = statevector_backend.run(transpiled).result().get_statevector() - from ..operator_globals import EVAL_SIG_DIGITS - - return np.round(statevector * self.coeff, decimals=EVAL_SIG_DIGITS) - - def __str__(self) -> str: - qc = cast(CircuitStateFn, self.reduce()).to_circuit() - prim_str = str(qc.draw(output="text")) - if self.coeff == 1.0: - return "{}(\n{}\n)".format( - "CircuitStateFn" if not self.is_measurement else "CircuitMeasurement", prim_str - ) - else: - return "{}(\n{}\n) * {}".format( - "CircuitStateFn" if not self.is_measurement else "CircuitMeasurement", - prim_str, - self.coeff, - ) - - def assign_parameters(self, param_dict: dict) -> Union["CircuitStateFn", ListOp]: - param_value = self.coeff - qc = self.primitive - if isinstance(self.coeff, ParameterExpression) or self.primitive.parameters: - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if isinstance(self.coeff, ParameterExpression) and self.coeff.parameters <= set( - unrolled_dict.keys() - ): - param_instersection = set(unrolled_dict.keys()) & self.coeff.parameters - binds = {param: unrolled_dict[param] for param in param_instersection} - param_value = float(self.coeff.bind(binds)) - # & is set intersection, check if any parameters in unrolled are present in circuit - # This is different from bind_parameters in Terra because they check for set equality - if set(unrolled_dict.keys()) & self.primitive.parameters: - # Only bind the params found in the circuit - param_instersection = set(unrolled_dict.keys()) & self.primitive.parameters - binds = {param: unrolled_dict[param] for param in param_instersection} - qc = self.to_circuit().assign_parameters(binds) - return self.__class__(qc, coeff=param_value, is_measurement=self.is_measurement) - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: - vector_state_fn = self.to_matrix_op().eval() - return vector_state_fn - - if not self.is_measurement and isinstance(front, OperatorBase): - raise ValueError( - "Cannot compute overlap with StateFn or Operator if not Measurement. Try taking " - "sf.adjoint() first to convert to measurement." - ) - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - # Composable with circuit - if isinstance(front, (PauliOp, CircuitOp, MatrixOp, CircuitStateFn)): - new_front = self.compose(front) - return new_front.eval() - - return self.to_matrix_op().eval(front) - - def to_circuit(self, meas: bool = False) -> QuantumCircuit: - """Return QuantumCircuit representing StateFn""" - if meas: - meas_qc = self.primitive.copy() - meas_qc.add_register(ClassicalRegister(self.num_qubits)) - meas_qc.measure(qubit=range(self.num_qubits), cbit=range(self.num_qubits)) - return meas_qc - else: - return self.primitive - - def to_circuit_op(self) -> OperatorBase: - """Return ``StateFnCircuit`` corresponding to this StateFn.""" - return self - - def to_instruction(self): - """Return Instruction corresponding to primitive.""" - return self.primitive.to_instruction() - - # TODO specify backend? - def sample( - self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False - ) -> dict: - """ - Sample the state function as a normalized probability distribution. Returns dict of - bitstrings in order of probability, with values being probability. - """ - OperatorBase._check_massive("sample", False, self.num_qubits, massive) - qc = self.to_circuit(meas=True) - qasm_backend = BasicAer.get_backend("qasm_simulator") - transpiled = transpile(qc, qasm_backend, optimization_level=0) - counts = qasm_backend.run(transpiled, shots=shots).result().get_counts() - if reverse_endianness: - scaled_dict = {bstr[::-1]: (prob / shots) for (bstr, prob) in counts.items()} - else: - scaled_dict = {bstr: (prob / shots) for (bstr, prob) in counts.items()} - return dict(sorted(scaled_dict.items(), key=lambda x: x[1], reverse=True)) - - # Warning - modifying primitive!! - def reduce(self) -> "CircuitStateFn": - if self.primitive.data is not None: - # Need to do this from the end because we're deleting items! - for i in reversed(range(len(self.primitive.data))): - gate = self.primitive.data[i].operation - # Check if Identity or empty instruction (need to check that type is exactly - # Instruction because some gates have lazy gate.definition population) - # pylint: disable=unidiomatic-typecheck - if isinstance(gate, IGate) or ( - type(gate) == Instruction and gate.definition.data == [] - ): - del self.primitive.data[i] - return self - - def _expand_dim(self, num_qubits: int) -> "CircuitStateFn": - # this is equivalent to self.tensor(identity_operator), but optimized for better performance - # just like in tensor method, qiskit endianness is reversed here - return self.permute(list(range(num_qubits, num_qubits + self.num_qubits))) - - def permute(self, permutation: List[int]) -> "CircuitStateFn": - r""" - Permute the qubits of the circuit. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j of the circuit should be permuted to position permutation[j]. - - Returns: - A new CircuitStateFn containing the permuted circuit. - """ - new_qc = QuantumCircuit(max(permutation) + 1).compose(self.primitive, qubits=permutation) - return CircuitStateFn(new_qc, coeff=self.coeff, is_measurement=self.is_measurement) diff --git a/qiskit/opflow/state_fns/cvar_measurement.py b/qiskit/opflow/state_fns/cvar_measurement.py deleted file mode 100644 index 858d3b87f671..000000000000 --- a/qiskit/opflow/state_fns/cvar_measurement.py +++ /dev/null @@ -1,386 +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. - -"""CVaRMeasurement class.""" - - -from typing import Callable, Optional, Tuple, Union, cast, Dict - -import numpy as np - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops import ListOp, SummedOp, TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops import PauliOp, PauliSumOp -from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn -from qiskit.opflow.state_fns.dict_state_fn import DictStateFn -from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.opflow.state_fns.vector_state_fn import VectorStateFn -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class CVaRMeasurement(OperatorStateFn): - r"""Deprecated: A specialized measurement class to compute CVaR expectation values. - See https://arxiv.org/pdf/1907.04769.pdf for further details. - - Used in :class:`~qiskit.opflow.CVaRExpectation`, see there for more details. - """ - - primitive: OperatorBase - - # TODO allow normalization somehow? - @deprecate_func( - since="0.24.0", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: OperatorBase = None, - alpha: float = 1.0, - coeff: Union[complex, ParameterExpression] = 1.0, - ) -> None: - """ - Args: - primitive: The ``OperatorBase`` which defines the diagonal operator - measurement. - coeff: A coefficient by which to multiply the state function - alpha: A real-valued parameter between 0 and 1 which specifies the - fraction of observed samples to include when computing the - objective value. alpha = 1 corresponds to a standard observable - expectation value. alpha = 0 corresponds to only using the single - sample with the lowest energy. alpha = 0.5 corresponds to ranking each - observation by lowest energy and using the best - - Raises: - ValueError: TODO remove that this raises an error - ValueError: If alpha is not in [0, 1]. - OpflowError: If the primitive is not diagonal. - """ - if primitive is None: - raise ValueError - - if not 0 <= alpha <= 1: - raise ValueError("The parameter alpha must be in [0, 1].") - self._alpha = alpha - - if not _check_is_diagonal(primitive): - raise OpflowError( - "Input operator to CVaRMeasurement must be diagonal, but is not:", str(primitive) - ) - - super().__init__(primitive, coeff=coeff, is_measurement=True) - - @property - def alpha(self) -> float: - """A real-valued parameter between 0 and 1 which specifies the - fraction of observed samples to include when computing the - objective value. alpha = 1 corresponds to a standard observable - expectation value. alpha = 0 corresponds to only using the single - sample with the lowest energy. alpha = 0.5 corresponds to ranking each - observation by lowest energy and using the best half. - - Returns: - The parameter alpha which was given at initialization - """ - return self._alpha - - @property - def settings(self) -> Dict: - """Return settings.""" - return {"primitive": self._primitive, "coeff": self._coeff, "alpha": self._alpha} - - def add(self, other: OperatorBase) -> SummedOp: - return SummedOp([self, other]) - - def adjoint(self): - """The adjoint of a CVaRMeasurement is not defined. - - Returns: - Does not return anything, raises an error. - - Raises: - OpflowError: The adjoint of a CVaRMeasurement is not defined. - """ - raise OpflowError("Adjoint of a CVaR measurement not defined") - - def mul(self, scalar: Union[complex, ParameterExpression]) -> "CVaRMeasurement": - if not isinstance(scalar, (int, float, complex, ParameterExpression)): - raise ValueError( - "Operators can only be scalar multiplied by float or complex, not " - "{} of type {}.".format(scalar, type(scalar)) - ) - - return self.__class__(self.primitive, coeff=self.coeff * scalar, alpha=self._alpha) - - def tensor(self, other: OperatorBase) -> Union["OperatorStateFn", TensoredOp]: - if isinstance(other, OperatorStateFn): - return OperatorStateFn( - self.primitive.tensor(other.primitive), coeff=self.coeff * other.coeff - ) - return TensoredOp([self, other]) - - def to_density_matrix(self, massive: bool = False): - """Not defined.""" - raise NotImplementedError - - def to_matrix_op(self, massive: bool = False): - """Not defined.""" - raise NotImplementedError - - def to_matrix(self, massive: bool = False): - """Not defined.""" - raise NotImplementedError - - def to_circuit_op(self): - """Not defined.""" - raise NotImplementedError - - def __str__(self) -> str: - return f"CVaRMeasurement({str(self.primitive)}) * {self.coeff}" - - def eval( - self, front: Union[str, dict, np.ndarray, OperatorBase, Statevector] = None - ) -> complex: - r""" - Given the energies of each sampled measurement outcome (H_i) as well as the - sampling probability of each measurement outcome (p_i, we can compute the - CVaR as H_j + 1/α*(sum_i complex: - r""" - Given the energies of each sampled measurement outcome (H_i) as well as the - sampling probability of each measurement outcome (p_i, we can compute the - variance of the CVaR estimator as - H_j^2 + 1/α * (sum_i], where H is the diagonal observable and bi - corresponds to measurement outcome i. Given this, E[X^2] = E[^2] - - Args: - front: A StateFn or primitive which specifies the results of evaluating - a quantum state. - - Returns: - The Var[CVaR] of the diagonal observable specified by self.primitive - and the sampled quantum state described by the inputs - (energies, probabilities). For index j (described above), the CVaR - is computed as H_j^2 + 1/α*(sum_i Tuple[list, list]: - r""" - In order to compute the CVaR of an observable expectation, we require - the energies of each sampled measurement outcome as well as the sampling - probability of each measurement outcome. Note that the counts for each - measurement outcome will also suffice (and this is often how the CVaR - is presented). - - Args: - front: A StateFn or a primitive which defines a StateFn. - This input holds the results of a sampled/simulated circuit. - - Returns: - Two lists of equal length. `energies` contains the energy of each - unique measurement outcome computed against the diagonal observable - stored in self.primitive. `probabilities` contains the corresponding - sampling probability for each measurement outcome in `energies`. - - Raises: - ValueError: front isn't a DictStateFn or VectorStateFn - """ - if isinstance(front, CircuitStateFn): - front = cast(StateFn, front.eval()) - - # Standardize the inputs to a dict - if isinstance(front, DictStateFn): - data = front.primitive - elif isinstance(front, VectorStateFn): - vec = front.primitive.data - # Determine how many bits are needed - key_len = int(np.ceil(np.log2(len(vec)))) - # Convert the vector primitive into a dict. The formatting here ensures - # that the proper number of leading `0` characters are added. - data = {format(index, "0" + str(key_len) + "b"): val for index, val in enumerate(vec)} - else: - raise ValueError("Unsupported input to CVaRMeasurement.eval:", type(front)) - - obs = self.primitive - outcomes = list(data.items()) - # add energy evaluation - for i, outcome in enumerate(outcomes): - key = outcome[0] - outcomes[i] += (obs.eval(key).adjoint().eval(key),) # type: ignore - - # Sort each observation based on it's energy - outcomes = sorted(outcomes, key=lambda x: x[2]) # type: ignore - - # Here probabilities are the (root) probabilities of - # observing each state. energies are the expectation - # values of each state with the provided Hamiltonian. - _, root_probabilities, energies = zip(*outcomes) - - # Square the dict values - # (since CircuitSampler takes the root...) - probabilities = [p_i * np.conj(p_i) for p_i in root_probabilities] - return list(energies), probabilities - - def compute_cvar(self, energies: list, probabilities: list) -> complex: - r""" - Given the energies of each sampled measurement outcome (H_i) as well as the - sampling probability of each measurement outcome (p_i, we can compute the - CVaR. Note that the sampling probabilities serve as an alternative to knowing - the counts of each observation and that the input energies are assumed to be - sorted in increasing order. - - Consider the outcome with index j, such that only some of the samples with - measurement outcome j will be used in computing CVaR. The CVaR calculation - can then be separated into two parts. First we sum each of the energies for - outcomes i < j, weighted by the probability of observing that outcome (i.e - the normalized counts). Second, we add the energy for outcome j, weighted by - the difference (α - \sum_i alpha: - break - - h_j = energies[j] - cvar = alpha * h_j - - if alpha == 0 or j == 0: - return self.coeff * h_j - - energies = energies[:j] - probabilities = probabilities[:j] - # Let H_i be the energy associated with outcome i - # and let the outcomes be sorted by ascending energy. - # Let p_i be the probability of observing outcome i. - # CVaR = H_j + 1/α*(sum_i OperatorBase: - r""" - Apply the convert_fn to the internal primitive if the primitive is an Operator (as in - the case of ``OperatorStateFn``). Otherwise do nothing. Used by converters. - - Args: - convert_fn: The function to apply to the internal OperatorBase. - coeff: A coefficient to multiply by after applying convert_fn. - If it is None, self.coeff is used instead. - - Returns: - The converted StateFn. - """ - if coeff is None: - coeff = self.coeff - - if isinstance(self.primitive, OperatorBase): - return self.__class__(convert_fn(self.primitive), coeff=coeff, alpha=self._alpha) - return self - - def sample(self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False): - raise NotImplementedError - - -def _check_is_diagonal(operator: OperatorBase) -> bool: - """Check whether ``operator`` is diagonal. - - Args: - operator: The operator to check for diagonality. - - Returns: - True, if the operator is diagonal, False otherwise. - - Raises: - OpflowError: If the operator is not diagonal. - """ - if isinstance(operator, PauliOp): - # every X component must be False - return not np.any(operator.primitive.x) - - # For sums (PauliSumOp and SummedOp), we cover the case of sums of diagonal paulis, but don't - # raise since there might be summand canceling the non-diagonal parts. That case is checked - # in the inefficient matrix check at the bottom. - if isinstance(operator, PauliSumOp): - if not np.any(operator.primitive.paulis.x): - return True - - elif isinstance(operator, SummedOp): - if all(isinstance(op, PauliOp) and not np.any(op.primitive.x) for op in operator.oplist): - return True - - elif isinstance(operator, ListOp): - return all(operator.traverse(_check_is_diagonal)) - - # cannot efficiently check if a operator is diagonal, converting to matrix - matrix = operator.to_matrix() - return np.all(matrix == np.diag(np.diagonal(matrix))) diff --git a/qiskit/opflow/state_fns/dict_state_fn.py b/qiskit/opflow/state_fns/dict_state_fn.py deleted file mode 100644 index a1fb52acb486..000000000000 --- a/qiskit/opflow/state_fns/dict_state_fn.py +++ /dev/null @@ -1,346 +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. - -"""DictStateFn Class""" - -import itertools -import warnings -from typing import Dict, List, Optional, Set, Union, cast - -import numpy as np -from scipy import sparse - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.exceptions import OpflowError -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.opflow.state_fns.vector_state_fn import VectorStateFn -from qiskit.quantum_info import Statevector -from qiskit.result import Result -from qiskit.utils import algorithm_globals -from qiskit.utils.deprecation import deprecate_func - - -class DictStateFn(StateFn): - """Deprecated: A class for state functions and measurements which are defined by a lookup table, - stored in a dict. - """ - - primitive: Dict[str, complex] - - # 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__( - self, - primitive: Union[str, dict, Result] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - from_operator: bool = False, - ) -> None: - """ - Args: - primitive: The dict, single bitstring (if defining a basis sate), or Qiskit - Result, which defines the behavior of the underlying function. - coeff: A coefficient by which to multiply the state function. - is_measurement: Whether the StateFn is a measurement operator. - from_operator: if True the StateFn is derived from OperatorStateFn. (Default: False) - - Raises: - TypeError: invalid parameters. - """ - # If the initial density is a string, treat this as a density dict - # with only a single basis state. - if isinstance(primitive, str): - primitive = {primitive: 1} - - # NOTE: - # 1) This is not the same as passing in the counts dict directly, as this will - # convert the shot numbers to - # probabilities, whereas passing in the counts dict will not. - # 2) This will extract counts for both shot and statevector simulations. - # To use the statevector, - # simply pass in the statevector. - # 3) This will only extract the first result. - if isinstance(primitive, Result): - counts = primitive.get_counts() - # NOTE: Need to square root to take correct Pauli measurements! - primitive = { - bstr: (shots / sum(counts.values())) ** 0.5 for (bstr, shots) in counts.items() - } - - if not isinstance(primitive, dict): - raise TypeError( - "DictStateFn can only be instantiated with dict, " - "string, or Qiskit Result, not {}".format(type(primitive)) - ) - - super().__init__(primitive, coeff=coeff, is_measurement=is_measurement) - self.from_operator = from_operator - - def primitive_strings(self) -> Set[str]: - return {"Dict"} - - @property - def num_qubits(self) -> int: - return len(next(iter(self.primitive))) - - @property - def settings(self) -> Dict: - """Return settings.""" - data = super().settings - data["from_operator"] = self.from_operator - return data - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over statefns with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - # Right now doesn't make sense to add a StateFn to a Measurement - if isinstance(other, DictStateFn) and self.is_measurement == other.is_measurement: - # TODO add compatibility with vector and Operator? - if self.primitive == other.primitive: - return DictStateFn( - self.primitive, - coeff=self.coeff + other.coeff, - is_measurement=self.is_measurement, - ) - else: - new_dict = { - b: (v * self.coeff) + (other.primitive.get(b, 0) * other.coeff) - for (b, v) in self.primitive.items() - } - new_dict.update( - { - b: v * other.coeff - for (b, v) in other.primitive.items() - if b not in self.primitive - } - ) - return DictStateFn(new_dict, is_measurement=self._is_measurement) - # pylint: disable=cyclic-import - from ..list_ops.summed_op import SummedOp - - return SummedOp([self, other]) - - def adjoint(self) -> "DictStateFn": - return DictStateFn( - {b: np.conj(v) for (b, v) in self.primitive.items()}, - coeff=self.coeff.conjugate(), - is_measurement=(not self.is_measurement), - ) - - def permute(self, permutation: List[int]) -> "DictStateFn": - new_num_qubits = max(permutation) + 1 - if self.num_qubits != len(permutation): - raise OpflowError("New index must be defined for each qubit of the operator.") - - # helper function to permute the key - def perm(key): - list_key = ["0"] * new_num_qubits - for i, k in enumerate(permutation): - list_key[k] = key[i] - return "".join(list_key) - - new_dict = {perm(key): value for key, value in self.primitive.items()} - return DictStateFn(new_dict, coeff=self.coeff, is_measurement=self.is_measurement) - - def _expand_dim(self, num_qubits: int) -> "DictStateFn": - pad = "0" * num_qubits - new_dict = {key + pad: value for key, value in self.primitive.items()} - return DictStateFn(new_dict, coeff=self.coeff, is_measurement=self.is_measurement) - - def tensor(self, other: OperatorBase) -> OperatorBase: - # Both dicts - if isinstance(other, DictStateFn): - new_dict = { - k1 + k2: v1 * v2 - for ( - ( - k1, - v1, - ), - (k2, v2), - ) in itertools.product(self.primitive.items(), other.primitive.items()) - } - return StateFn( - new_dict, coeff=self.coeff * other.coeff, is_measurement=self.is_measurement - ) - # pylint: disable=cyclic-import - from ..list_ops.tensored_op import TensoredOp - - return TensoredOp([self, other]) - - def to_density_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_density_matrix", True, self.num_qubits, massive) - states = int(2**self.num_qubits) - return self.to_matrix(massive=massive) * np.eye(states) * self.coeff - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", False, self.num_qubits, massive) - states = int(2**self.num_qubits) - probs = np.zeros(states) + 0.0j - for k, v in self.primitive.items(): - probs[int(k, 2)] = v - vec = probs * self.coeff - - # Reshape for measurements so np.dot still works for composition. - return vec if not self.is_measurement else vec.reshape(1, -1) - - def to_spmatrix(self) -> sparse.spmatrix: - """Same as to_matrix, but returns csr sparse matrix. - - Returns: - CSR sparse matrix representation of the State function. - - Raises: - ValueError: invalid parameters. - """ - - indices = [int(v, 2) for v in self.primitive.keys()] - vals = np.array(list(self.primitive.values())) * self.coeff - spvec = sparse.csr_matrix( - (vals, (np.zeros(len(indices), dtype=int), indices)), shape=(1, 2**self.num_qubits) - ) - return spvec if not self.is_measurement else spvec.transpose() - - def to_spmatrix_op(self) -> OperatorBase: - """Convert this state function to a ``SparseVectorStateFn``.""" - from .sparse_vector_state_fn import SparseVectorStateFn - - return SparseVectorStateFn(self.to_spmatrix(), self.coeff, self.is_measurement) - - def to_circuit_op(self) -> OperatorBase: - """Convert this state function to a ``CircuitStateFn``.""" - from .circuit_state_fn import CircuitStateFn - - csfn = CircuitStateFn.from_dict(self.primitive) * self.coeff - return csfn.adjoint() if self.is_measurement else csfn - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return "{}({})".format( - "DictStateFn" if not self.is_measurement else "DictMeasurement", prim_str - ) - else: - return "{}({}) * {}".format( - "DictStateFn" if not self.is_measurement else "DictMeasurement", - prim_str, - self.coeff, - ) - - # pylint: disable=too-many-return-statements - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: - sparse_vector_state_fn = self.to_spmatrix_op().eval() - return sparse_vector_state_fn - - if not self.is_measurement and isinstance(front, OperatorBase): - raise ValueError( - "Cannot compute overlap with StateFn or Operator if not Measurement. Try taking " - "sf.adjoint() first to convert to measurement." - ) - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - # For now, always do this. If it's not performant, we can be more granular. - if not isinstance(front, OperatorBase): - front = StateFn(front) - - # pylint: disable=cyclic-import - from ..operator_globals import EVAL_SIG_DIGITS - - # If the primitive is a lookup of bitstrings, - # we define all missing strings to have a function value of - # zero. - if isinstance(front, DictStateFn): - # If self is come from operator, it should be expanded as - # = . - front_coeff = ( - front.coeff * front.coeff.conjugate() if self.from_operator else front.coeff - ) - return np.round( - cast( - float, - sum(v * front.primitive.get(b, 0) for (b, v) in self.primitive.items()) - * self.coeff - * front_coeff, - ), - decimals=EVAL_SIG_DIGITS, - ) - - # All remaining possibilities only apply when self.is_measurement is True - - if isinstance(front, VectorStateFn): - # TODO does it need to be this way for measurement? - # return sum([v * front.primitive.data[int(b, 2)] * - # np.conj(front.primitive.data[int(b, 2)]) - return np.round( - cast( - float, - sum(v * front.primitive.data[int(b, 2)] for (b, v) in self.primitive.items()) - * self.coeff, - ), - decimals=EVAL_SIG_DIGITS, - ) - - from .circuit_state_fn import CircuitStateFn - - if isinstance(front, CircuitStateFn): - # Don't reimplement logic from CircuitStateFn - self_adjoint = cast(DictStateFn, self.adjoint()) - return np.conj(front.adjoint().eval(self_adjoint.primitive)) * self.coeff - - from .operator_state_fn import OperatorStateFn - - if isinstance(front, OperatorStateFn): - return cast(Union[OperatorBase, complex], front.adjoint().eval(self.adjoint())) - - # All other OperatorBases go here - self_adjoint = cast(DictStateFn, self.adjoint()) - adjointed_eval = cast(OperatorBase, front.adjoint().eval(self_adjoint.primitive)) - return adjointed_eval.adjoint() * self.coeff - - def sample( - self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False - ) -> Dict[str, float]: - probs = np.square(np.abs(np.array(list(self.primitive.values())))) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - unique, counts = np.unique( - algorithm_globals.random.choice( - list(self.primitive.keys()), size=shots, p=(probs / sum(probs)) - ), - return_counts=True, - ) - counts = dict(zip(unique, counts)) - if reverse_endianness: - scaled_dict = {bstr[::-1]: (prob / shots) for (bstr, prob) in counts.items()} - else: - scaled_dict = {bstr: (prob / shots) for (bstr, prob) in counts.items()} - return dict(sorted(scaled_dict.items(), key=lambda x: x[1], reverse=True)) diff --git a/qiskit/opflow/state_fns/operator_state_fn.py b/qiskit/opflow/state_fns/operator_state_fn.py deleted file mode 100644 index 08f1b3c766b0..000000000000 --- a/qiskit/opflow/state_fns/operator_state_fn.py +++ /dev/null @@ -1,260 +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. - -"""OperatorStateFn Class""" - -from typing import List, Optional, Set, Union, cast - -import numpy as np - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.primitive_ops.matrix_op import MatrixOp -from qiskit.opflow.primitive_ops.pauli_sum_op import PauliSumOp -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn -from qiskit.quantum_info import Statevector -from qiskit.utils.deprecation import deprecate_func - - -class OperatorStateFn(StateFn): - r""" - Deprecated: A class for state functions and measurements which are defined by a density Operator, - stored using an ``OperatorBase``. - """ - primitive: OperatorBase - - # 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__( - self, - primitive: OperatorBase, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - ) -> None: - """ - Args: - primitive: The ``OperatorBase`` which defines the behavior of the underlying State - function. - coeff: A coefficient by which to multiply the state function - is_measurement: Whether the StateFn is a measurement operator - """ - - super().__init__(primitive, coeff=coeff, is_measurement=is_measurement) - - def primitive_strings(self) -> Set[str]: - return self.primitive.primitive_strings() - - @property - def num_qubits(self) -> int: - return self.primitive.num_qubits - - def add(self, other: OperatorBase) -> Union["OperatorStateFn", SummedOp]: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over statefns with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - # Right now doesn't make sense to add a StateFn to a Measurement - if isinstance(other, OperatorStateFn) and self.is_measurement == other.is_measurement: - if isinstance(other.primitive, OperatorBase) and self.primitive == other.primitive: - return OperatorStateFn( - self.primitive, - coeff=self.coeff + other.coeff, - is_measurement=self.is_measurement, - ) - # Covers Statevector and custom. - elif isinstance(other, OperatorStateFn): - # Also assumes scalar multiplication is available - return OperatorStateFn( - (self.coeff * self.primitive).add(other.primitive * other.coeff), - is_measurement=self._is_measurement, - ) - - return SummedOp([self, other]) - - def adjoint(self) -> "OperatorStateFn": - return OperatorStateFn( - self.primitive.adjoint(), - coeff=self.coeff.conjugate(), - is_measurement=(not self.is_measurement), - ) - - def _expand_dim(self, num_qubits: int) -> "OperatorStateFn": - return OperatorStateFn( - self.primitive._expand_dim(num_qubits), - coeff=self.coeff, - is_measurement=self.is_measurement, - ) - - def permute(self, permutation: List[int]) -> "OperatorStateFn": - return OperatorStateFn( - self.primitive.permute(permutation), - coeff=self.coeff, - is_measurement=self.is_measurement, - ) - - def tensor(self, other: OperatorBase) -> Union["OperatorStateFn", TensoredOp]: - if isinstance(other, OperatorStateFn): - return OperatorStateFn( - self.primitive.tensor(other.primitive), - coeff=self.coeff * other.coeff, - is_measurement=self.is_measurement, - ) - - return TensoredOp([self, other]) - - def to_density_matrix(self, massive: bool = False) -> np.ndarray: - """Return numpy matrix of density operator, warn if more than 16 qubits - to force the user to set - massive=True if they want such a large matrix. Generally big methods like - this should require the use of a - converter, but in this case a convenience method for quick hacking and - access to classical tools is - appropriate.""" - OperatorBase._check_massive("to_density_matrix", True, self.num_qubits, massive) - return self.primitive.to_matrix() * self.coeff - - def to_matrix_op(self, massive: bool = False) -> "OperatorStateFn": - """Return a MatrixOp for this operator.""" - return OperatorStateFn( - self.primitive.to_matrix_op(massive=massive) * self.coeff, - is_measurement=self.is_measurement, - ) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - r""" - Note: this does not return a density matrix, it returns a classical matrix - containing the quantum or classical vector representing the evaluation of the state - function on each binary basis state. Do not assume this is is a normalized quantum or - classical probability vector. If we allowed this to return a density matrix, - then we would need to change the definition of composition to be ~Op @ StateFn @ Op for - those cases, whereas by this methodology we can ensure that composition always means Op - @ StateFn. - - Return numpy vector of state vector, warn if more than 16 qubits to force the user to set - massive=True if they want such a large vector. - - Args: - massive: Whether to allow large conversions, e.g. creating a matrix representing - over 16 qubits. - - Returns: - np.ndarray: Vector of state vector - - Raises: - ValueError: Invalid parameters. - """ - OperatorBase._check_massive("to_matrix", False, self.num_qubits, massive) - # Operator - return diagonal (real values, not complex), - # not rank 1 decomposition (statevector)! - mat = self.primitive.to_matrix(massive=massive) - # TODO change to weighted sum of eigenvectors' StateFns? - - # ListOp primitives can return lists of matrices (or trees for nested ListOps), - # so we need to recurse over the - # possible tree. - def diag_over_tree(op): - if isinstance(op, list): - return [diag_over_tree(o) for o in op] - else: - vec = np.diag(op) * self.coeff - # Reshape for measurements so np.dot still works for composition. - return vec if not self.is_measurement else vec.reshape(1, -1) - - return diag_over_tree(mat) - - def to_circuit_op(self): - r"""Return ``StateFnCircuit`` corresponding to this StateFn. Ignore for now because this is - undefined. TODO maybe call to_pauli_op and diagonalize here, but that could be very - inefficient, e.g. splitting one Stabilizer measurement into hundreds of 1 qubit Paulis.""" - raise NotImplementedError - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return "{}({})".format( - "OperatorStateFn" if not self.is_measurement else "OperatorMeasurement", prim_str - ) - else: - return "{}({}) * {}".format( - "OperatorStateFn" if not self.is_measurement else "OperatorMeasurement", - prim_str, - self.coeff, - ) - - def eval( - self, front: Optional[Union[str, dict, np.ndarray, OperatorBase, Statevector]] = None - ) -> Union[OperatorBase, complex]: - if front is None: - matrix = cast(MatrixOp, self.primitive.to_matrix_op()).primitive.data - # pylint: disable=cyclic-import - from .vector_state_fn import VectorStateFn - - return VectorStateFn(matrix[0, :]) - - if not self.is_measurement and isinstance(front, OperatorBase): - raise ValueError( - "Cannot compute overlap with StateFn or Operator if not Measurement. Try taking " - "sf.adjoint() first to convert to measurement." - ) - - if not isinstance(front, OperatorBase): - front = StateFn(front) - - if isinstance(self.primitive, ListOp) and self.primitive.distributive: - evals = [ - OperatorStateFn(op, is_measurement=self.is_measurement).eval(front) - for op in self.primitive.oplist - ] - result = self.primitive.combo_fn(evals) - if isinstance(result, list): - multiplied = self.primitive.coeff * self.coeff * np.array(result) - return multiplied.tolist() - return result * self.coeff * self.primitive.coeff - - # pylint: disable=cyclic-import - from .vector_state_fn import VectorStateFn - - if isinstance(self.primitive, PauliSumOp) and isinstance(front, VectorStateFn): - return ( - front.primitive.expectation_value(self.primitive.primitive) - * self.coeff - * front.coeff - ) - - # Need an ListOp-specific carve-out here to make sure measurement over a ListOp doesn't - # produce two-dimensional ListOp from composing from both sides of primitive. - # Can't use isinstance because this would include subclasses. - # pylint: disable=unidiomatic-typecheck - if isinstance(front, ListOp) and type(front) == ListOp: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - # If we evaluate against a circuit, evaluate it to a vector so we - # make sure to only do the expensive circuit simulation once - if isinstance(front, CircuitStateFn): - front = front.eval() - - return front.adjoint().eval(cast(OperatorBase, self.primitive.eval(front))) * self.coeff - - def sample(self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False): - raise NotImplementedError diff --git a/qiskit/opflow/state_fns/sparse_vector_state_fn.py b/qiskit/opflow/state_fns/sparse_vector_state_fn.py deleted file mode 100644 index b26c6dff9df1..000000000000 --- a/qiskit/opflow/state_fns/sparse_vector_state_fn.py +++ /dev/null @@ -1,234 +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. - -"""SparseVectorStateFn class.""" - - -from typing import Dict, Optional, Set, Union - -import numpy as np -import scipy - -from qiskit.circuit import ParameterExpression -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.opflow.state_fns.vector_state_fn import VectorStateFn -from qiskit.quantum_info import Statevector -from qiskit.utils import algorithm_globals -from qiskit.utils.deprecation import deprecate_func - - -class SparseVectorStateFn(StateFn): - """Deprecated: A class for sparse state functions and measurements in vector representation. - - This class uses ``scipy.sparse.spmatrix`` for the internal representation. - """ - - primitive: scipy.sparse.spmatrix - - # TODO allow normalization somehow? - @deprecate_func( - since="0.24.0", - additional_msg="For code migration guidelines, visit https://qisk.it/opflow_migration.", - ) - def __init__( - self, - primitive: scipy.sparse.spmatrix, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - ) -> None: - """ - Args: - primitive: The underlying sparse vector. - coeff: A coefficient multiplying the state function. - is_measurement: Whether the StateFn is a measurement operator - - Raises: - ValueError: If the primitive is not a column vector. - ValueError: If the number of elements in the primitive is not a power of 2. - - """ - if primitive.shape[0] != 1: - raise ValueError("The primitive must be a row vector of shape (x, 1).") - - # check if the primitive is a statevector of 2^n elements - self._num_qubits = int(np.log2(primitive.shape[1])) - if np.log2(primitive.shape[1]) != self._num_qubits: - raise ValueError("The number of vector elements must be a power of 2.") - - super().__init__(primitive, coeff=coeff, is_measurement=is_measurement) - - def primitive_strings(self) -> Set[str]: - return {"SparseVector"} - - @property - def num_qubits(self) -> int: - return self._num_qubits - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over statefns with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - # Right now doesn't make sense to add a StateFn to a Measurement - if isinstance(other, SparseVectorStateFn) and self.is_measurement == other.is_measurement: - # Covers Statevector and custom. - added = self.coeff * self.primitive + other.coeff * other.primitive - return SparseVectorStateFn(added, is_measurement=self._is_measurement) - - return SummedOp([self, other]) - - def adjoint(self) -> "SparseVectorStateFn": - return SparseVectorStateFn( - self.primitive.conjugate(), - coeff=self.coeff.conjugate(), - is_measurement=(not self.is_measurement), - ) - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, SparseVectorStateFn) or not self.coeff == other.coeff: - return False - - if self.primitive.shape != other.primitive.shape: - return False - - if self.primitive.count_nonzero() != other.primitive.count_nonzero(): - return False - - # equal if no elements are different (using != for efficiency) - return (self.primitive != other.primitive).nnz == 0 - - def to_dict_fn(self) -> StateFn: - """Convert this state function to a ``DictStateFn``. - - Returns: - A new DictStateFn equivalent to ``self``. - """ - from .dict_state_fn import DictStateFn - - num_qubits = self.num_qubits - dok = self.primitive.todok() - new_dict = {format(i[1], "b").zfill(num_qubits): v for i, v in dok.items()} - return DictStateFn(new_dict, coeff=self.coeff, is_measurement=self.is_measurement) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", False, self.num_qubits, massive) - vec = self.primitive.toarray() * self.coeff - return vec if not self.is_measurement else vec.reshape(1, -1) - - def to_matrix_op(self, massive: bool = False) -> OperatorBase: - return VectorStateFn(self.to_matrix()) - - def to_spmatrix(self) -> OperatorBase: - return self - - def to_circuit_op(self) -> OperatorBase: - """Convert this state function to a ``CircuitStateFn``.""" - # pylint: disable=cyclic-import - from .circuit_state_fn import CircuitStateFn - - csfn = CircuitStateFn.from_vector(self.primitive) * self.coeff - return csfn.adjoint() if self.is_measurement else csfn - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return "{}({})".format( - "SparseVectorStateFn" if not self.is_measurement else "MeasurementSparseVector", - prim_str, - ) - else: - return "{}({}) * {}".format( - "SparseVectorStateFn" if not self.is_measurement else "SparseMeasurementVector", - prim_str, - self.coeff, - ) - - # pylint: disable=too-many-return-statements - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, Statevector, OperatorBase] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: - return self - - if not self.is_measurement and isinstance(front, OperatorBase): - raise ValueError( - "Cannot compute overlap with StateFn or Operator if not Measurement. " - "Try taking sf.adjoint() first to convert to measurement." - ) - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - if not isinstance(front, OperatorBase): - front = StateFn(front) - - # pylint: disable=cyclic-import - from ..operator_globals import EVAL_SIG_DIGITS - from .operator_state_fn import OperatorStateFn - from .circuit_state_fn import CircuitStateFn - from .dict_state_fn import DictStateFn - - if isinstance(front, DictStateFn): - return np.round( - sum( - v * self.primitive.data[int(b, 2)] * front.coeff - for (b, v) in front.primitive.items() - ) - * self.coeff, - decimals=EVAL_SIG_DIGITS, - ) - - if isinstance(front, VectorStateFn): - # Need to extract the element or np.array([1]) is returned. - return np.round( - np.dot(self.to_matrix(), front.to_matrix())[0], decimals=EVAL_SIG_DIGITS - ) - - if isinstance(front, CircuitStateFn): - # Don't reimplement logic from CircuitStateFn - return np.conj(front.adjoint().eval(self.adjoint().primitive)) * self.coeff - - if isinstance(front, OperatorStateFn): - return front.adjoint().eval(self.primitive) * self.coeff - - return front.adjoint().eval(self.adjoint().primitive).adjoint() * self.coeff # type: ignore - - def sample( - self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False - ) -> dict: - as_dict = self.to_dict_fn().primitive - all_states = sum(as_dict.keys()) - deterministic_counts = {key: value / all_states for key, value in as_dict.items()} - # Don't need to square because probabilities_dict already does. - probs = np.array(list(deterministic_counts.values())) - unique, counts = np.unique( - algorithm_globals.random.choice( - list(deterministic_counts.keys()), size=shots, p=(probs / sum(probs)) - ), - return_counts=True, - ) - counts = dict(zip(unique, counts)) - if reverse_endianness: - scaled_dict = {bstr[::-1]: (prob / shots) for (bstr, prob) in counts.items()} - else: - scaled_dict = {bstr: (prob / shots) for (bstr, prob) in counts.items()} - return dict(sorted(scaled_dict.items(), key=lambda x: x[1], reverse=True)) diff --git a/qiskit/opflow/state_fns/state_fn.py b/qiskit/opflow/state_fns/state_fn.py deleted file mode 100644 index 6f6575db4105..000000000000 --- a/qiskit/opflow/state_fns/state_fn.py +++ /dev/null @@ -1,460 +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. - -"""StateFn Class""" - -from typing import Callable, Dict, List, Optional, Set, Tuple, Union - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Instruction, ParameterExpression -from qiskit.opflow.operator_base import OperatorBase -from qiskit.quantum_info import Statevector -from qiskit.result import Result -from qiskit.utils.deprecation import deprecate_func - - -class StateFn(OperatorBase): - r""" - Deprecated: A class for representing state functions and measurements. - - State functions are defined to be complex functions over a single binary string (as - compared to an operator, which is defined as a function over two binary strings, or a - function taking a binary function to another binary function). This function may be - called by the eval() method. - - Measurements are defined to be functionals over StateFns, taking them to real values. - Generally, this real value is interpreted to represent the probability of some classical - state (binary string) being observed from a probabilistic or quantum system represented - by a StateFn. This leads to the equivalent definition, which is that a measurement m is - a function over binary strings producing StateFns, such that the probability of measuring - a given binary string b from a system with StateFn f is equal to the inner - product between f and m(b). - - NOTE: State functions here are not restricted to wave functions, as there is - no requirement of normalization. - """ - - def __init_subclass__(cls): - cls.__new__ = lambda cls, *args, **kwargs: super().__new__(cls) - - @staticmethod - # pylint: disable=unused-argument - def __new__( - cls, - primitive: Union[ - str, - dict, - Result, - list, - np.ndarray, - Statevector, - QuantumCircuit, - Instruction, - OperatorBase, - ] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - ) -> "StateFn": - """A factory method to produce the correct type of StateFn subclass - based on the primitive passed in. Primitive, coeff, and is_measurement arguments - are passed into subclass's init() as-is automatically by new(). - - Args: - primitive: The primitive which defines the behavior of the underlying State function. - coeff: A coefficient by which the state function is multiplied. - is_measurement: Whether the StateFn is a measurement operator - - Returns: - The appropriate StateFn subclass for ``primitive``. - - Raises: - TypeError: Unsupported primitive type passed. - """ - - # Prevents infinite recursion when subclasses are created - if cls.__name__ != StateFn.__name__: - return super().__new__(cls) - - # pylint: disable=cyclic-import - if isinstance(primitive, (str, dict, Result)): - from .dict_state_fn import DictStateFn - - return DictStateFn.__new__(DictStateFn) - - if isinstance(primitive, (list, np.ndarray, Statevector)): - from .vector_state_fn import VectorStateFn - - return VectorStateFn.__new__(VectorStateFn) - - if isinstance(primitive, (QuantumCircuit, Instruction)): - from .circuit_state_fn import CircuitStateFn - - return CircuitStateFn.__new__(CircuitStateFn) - - if isinstance(primitive, OperatorBase): - from .operator_state_fn import OperatorStateFn - - return OperatorStateFn.__new__(OperatorStateFn) - - raise TypeError( - "Unsupported primitive type {} passed into StateFn " - "factory constructor".format(type(primitive)) - ) - - # 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__( - self, - primitive: Union[ - str, - dict, - Result, - list, - np.ndarray, - Statevector, - QuantumCircuit, - Instruction, - OperatorBase, - ] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - ) -> None: - """ - Args: - primitive: The primitive which defines the behavior of the underlying State function. - coeff: A coefficient by which the state function is multiplied. - is_measurement: Whether the StateFn is a measurement operator - """ - super().__init__() - self._primitive = primitive - self._is_measurement = is_measurement - self._coeff = coeff - - @property - def primitive(self): - """The primitive which defines the behavior of the underlying State function.""" - return self._primitive - - @property - def coeff(self) -> Union[complex, ParameterExpression]: - """A coefficient by which the state function is multiplied.""" - return self._coeff - - @property - def is_measurement(self) -> bool: - """Whether the StateFn object is a measurement Operator.""" - return self._is_measurement - - @property - def settings(self) -> Dict: - """Return settings.""" - return { - "primitive": self._primitive, - "coeff": self._coeff, - "is_measurement": self._is_measurement, - } - - def primitive_strings(self) -> Set[str]: - raise NotImplementedError - - @property - def num_qubits(self) -> int: - raise NotImplementedError - - def add(self, other: OperatorBase) -> OperatorBase: - raise NotImplementedError - - def adjoint(self) -> OperatorBase: - raise NotImplementedError - - def _expand_dim(self, num_qubits: int) -> "StateFn": - raise NotImplementedError - - def permute(self, permutation: List[int]) -> OperatorBase: - """Permute the qubits of the state function. - - Args: - permutation: A list defining where each qubit should be permuted. The qubit at index - j of the circuit should be permuted to position permutation[j]. - - Returns: - A new StateFn containing the permuted primitive. - """ - raise NotImplementedError - - def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, type(self)) or not self.coeff == other.coeff: - return False - - return self.primitive == other.primitive - # Will return NotImplementedError if not supported - - def mul(self, scalar: Union[complex, ParameterExpression]) -> OperatorBase: - if not isinstance(scalar, (int, float, complex, ParameterExpression)): - raise ValueError( - "Operators can only be scalar multiplied by float or complex, not " - "{} of type {}.".format(scalar, type(scalar)) - ) - - if hasattr(self, "from_operator"): - return self.__class__( - self.primitive, - coeff=self.coeff * scalar, - is_measurement=self.is_measurement, - from_operator=self.from_operator, - ) - else: - return self.__class__( - self.primitive, coeff=self.coeff * scalar, is_measurement=self.is_measurement - ) - - def tensor(self, other: OperatorBase) -> OperatorBase: - r""" - Return tensor product between self and other, overloaded by ``^``. - Note: You must be conscious of Qiskit's big-endian bit printing - convention. Meaning, Plus.tensor(Zero) - produces a \|+⟩ on qubit 0 and a \|0⟩ on qubit 1, or \|+⟩⨂\|0⟩, but - would produce a QuantumCircuit like - - \|0⟩-- - \|+⟩-- - - Because Terra prints circuits and results with qubit 0 - at the end of the string or circuit. - - Args: - other: The ``OperatorBase`` to tensor product with self. - - Returns: - An ``OperatorBase`` equivalent to the tensor product of self and other. - """ - raise NotImplementedError - - def tensorpower(self, other: int) -> Union[OperatorBase, int]: - if not isinstance(other, int) or other <= 0: - raise TypeError("Tensorpower can only take positive int arguments") - temp = StateFn( - self.primitive, coeff=self.coeff, is_measurement=self.is_measurement - ) # type: OperatorBase - for _ in range(other - 1): - temp = temp.tensor(self) - return temp - - def _expand_shorter_operator_and_permute( - self, other: OperatorBase, permutation: Optional[List[int]] = None - ) -> Tuple[OperatorBase, OperatorBase]: - # pylint: disable=cyclic-import - from ..operator_globals import Zero - - if self == StateFn({"0": 1}, is_measurement=True): - # Zero is special - we'll expand it to the correct qubit number. - return StateFn("0" * other.num_qubits, is_measurement=True), other - elif other == Zero: - # Zero is special - we'll expand it to the correct qubit number. - return self, StateFn("0" * self.num_qubits) - - return super()._expand_shorter_operator_and_permute(other, permutation) - - def to_matrix(self, massive: bool = False) -> np.ndarray: - raise NotImplementedError - - def to_density_matrix(self, massive: bool = False) -> np.ndarray: - """Return matrix representing product of StateFn evaluated on pairs of basis states. - Overridden by child classes. - - Args: - massive: Whether to allow large conversions, e.g. creating a matrix representing - over 16 qubits. - - Returns: - The NumPy array representing the density matrix of the State function. - - Raises: - ValueError: If massive is set to False, and exponentially large computation is needed. - """ - raise NotImplementedError - - def compose( - self, other: OperatorBase, permutation: Optional[List[int]] = None, front: bool = False - ) -> OperatorBase: - r""" - Composition (Linear algebra-style: A@B(x) = A(B(x))) is not well defined for states - in the binary function model, but is well defined for measurements. - - Args: - other: The Operator to compose with self. - permutation: ``List[int]`` which defines permutation on other operator. - front: If front==True, return ``other.compose(self)``. - - Returns: - An Operator equivalent to the function composition of self and other. - - Raises: - ValueError: If self is not a measurement, it cannot be composed from the right. - """ - # TODO maybe allow outers later to produce density operators or projectors, but not yet. - if not self.is_measurement and not front: - raise ValueError( - "Composition with a Statefunction in the first operand is not defined." - ) - - new_self, other = self._expand_shorter_operator_and_permute(other, permutation) - - if front: - return other.compose(self) - # TODO maybe include some reduction here in the subclasses - vector and Op, op and Op, etc. - from ..primitive_ops.circuit_op import CircuitOp - - if self.primitive == {"0" * self.num_qubits: 1.0} and isinstance(other, CircuitOp): - # Returning CircuitStateFn - return StateFn( - other.primitive, is_measurement=self.is_measurement, coeff=self.coeff * other.coeff - ) - - from ..list_ops.composed_op import ComposedOp - - if isinstance(other, ComposedOp): - return ComposedOp([new_self] + other.oplist, coeff=new_self.coeff * other.coeff) - - return ComposedOp([new_self, other]) - - def power(self, exponent: int) -> OperatorBase: - """Compose with Self Multiple Times, undefined for StateFns. - - Args: - exponent: The number of times to compose self with self. - - Raises: - ValueError: This function is not defined for StateFns. - """ - raise ValueError("Composition power over Statefunctions or Measurements is not defined.") - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return "{}({})".format( - "StateFunction" if not self.is_measurement else "Measurement", self.coeff - ) - else: - return "{}({}) * {}".format( - "StateFunction" if not self.is_measurement else "Measurement", self.coeff, prim_str - ) - - def __repr__(self) -> str: - return "{}({}, coeff={}, is_measurement={})".format( - self.__class__.__name__, repr(self.primitive), self.coeff, self.is_measurement - ) - - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, OperatorBase, Statevector] - ] = None, - ) -> Union[OperatorBase, complex]: - raise NotImplementedError - - @property - def parameters(self): - params = set() - if isinstance(self.primitive, (OperatorBase, QuantumCircuit)): - params.update(self.primitive.parameters) - if isinstance(self.coeff, ParameterExpression): - params.update(self.coeff.parameters) - return params - - def assign_parameters(self, param_dict: dict) -> OperatorBase: - param_value = self.coeff - if isinstance(self.coeff, ParameterExpression): - unrolled_dict = self._unroll_param_dict(param_dict) - if isinstance(unrolled_dict, list): - from ..list_ops.list_op import ListOp - - return ListOp([self.assign_parameters(param_dict) for param_dict in unrolled_dict]) - if self.coeff.parameters <= set(unrolled_dict.keys()): - binds = {param: unrolled_dict[param] for param in self.coeff.parameters} - param_value = float(self.coeff.bind(binds)) - return self.traverse(lambda x: x.assign_parameters(param_dict), coeff=param_value) - - # Try collapsing primitives where possible. Nothing to collapse here. - def reduce(self) -> OperatorBase: - return self - - def traverse( - self, convert_fn: Callable, coeff: Optional[Union[complex, ParameterExpression]] = None - ) -> OperatorBase: - r""" - Apply the convert_fn to the internal primitive if the primitive is an Operator (as in - the case of ``OperatorStateFn``). Otherwise do nothing. Used by converters. - - Args: - convert_fn: The function to apply to the internal OperatorBase. - coeff: A coefficient to multiply by after applying convert_fn. - If it is None, self.coeff is used instead. - - Returns: - The converted StateFn. - """ - if coeff is None: - coeff = self.coeff - - if isinstance(self.primitive, OperatorBase): - return StateFn( - convert_fn(self.primitive), coeff=coeff, is_measurement=self.is_measurement - ) - else: - return self - - def to_matrix_op(self, massive: bool = False) -> OperatorBase: - """Return a ``VectorStateFn`` for this ``StateFn``. - - Args: - massive: Whether to allow large conversions, e.g. creating a matrix representing - over 16 qubits. - - Returns: - A VectorStateFn equivalent to self. - """ - # pylint: disable=cyclic-import - from .vector_state_fn import VectorStateFn - - return VectorStateFn(self.to_matrix(massive=massive), is_measurement=self.is_measurement) - - def to_circuit_op(self) -> OperatorBase: - """Returns a ``CircuitOp`` equivalent to this Operator.""" - raise NotImplementedError - - # TODO to_dict_op - - def sample( - self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False - ) -> Dict[str, float]: - """Sample the state function as a normalized probability distribution. Returns dict of - bitstrings in order of probability, with values being probability. - - Args: - shots: The number of samples to take to approximate the State function. - massive: Whether to allow large conversions, e.g. creating a matrix representing - over 16 qubits. - reverse_endianness: Whether to reverse the endianness of the bitstrings in the return - dict to match Terra's big-endianness. - - Returns: - A dict containing pairs sampled strings from the State function and sampling - frequency divided by shots. - """ - raise NotImplementedError diff --git a/qiskit/opflow/state_fns/vector_state_fn.py b/qiskit/opflow/state_fns/vector_state_fn.py deleted file mode 100644 index 067070a4f05c..000000000000 --- a/qiskit/opflow/state_fns/vector_state_fn.py +++ /dev/null @@ -1,260 +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. - -"""VectorStateFn Class""" - -import warnings -from typing import Dict, List, Optional, Set, Union, cast - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import ParameterExpression -from qiskit.opflow.list_ops.list_op import ListOp -from qiskit.opflow.list_ops.summed_op import SummedOp -from qiskit.opflow.list_ops.tensored_op import TensoredOp -from qiskit.opflow.operator_base import OperatorBase -from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.quantum_info import Statevector -from qiskit.utils import algorithm_globals, arithmetic -from qiskit.utils.deprecation import deprecate_func - - -class VectorStateFn(StateFn): - """Deprecated: A class for state functions and measurements which are defined in vector - representation, and stored using Terra's ``Statevector`` class. - """ - - primitive: Statevector - - # 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__( - self, - primitive: Union[list, np.ndarray, Statevector] = None, - coeff: Union[complex, ParameterExpression] = 1.0, - is_measurement: bool = False, - ) -> None: - """ - Args: - primitive: The ``Statevector``, NumPy array, or list, which defines the behavior of - the underlying function. - coeff: A coefficient multiplying the state function. - is_measurement: Whether the StateFn is a measurement operator - """ - # Lists and Numpy arrays representing statevectors are stored - # in Statevector objects for easier handling. - if isinstance(primitive, (np.ndarray, list)): - primitive = Statevector(primitive) - - super().__init__(primitive, coeff=coeff, is_measurement=is_measurement) - - def primitive_strings(self) -> Set[str]: - return {"Vector"} - - @property - def num_qubits(self) -> int: - return len(self.primitive.dims()) - - def add(self, other: OperatorBase) -> OperatorBase: - if not self.num_qubits == other.num_qubits: - raise ValueError( - "Sum over statefns with different numbers of qubits, {} and {}, is not well " - "defined".format(self.num_qubits, other.num_qubits) - ) - - # Right now doesn't make sense to add a StateFn to a Measurement - if isinstance(other, VectorStateFn) and self.is_measurement == other.is_measurement: - # Covers Statevector and custom. - return VectorStateFn( - (self.coeff * self.primitive) + (other.primitive * other.coeff), - is_measurement=self._is_measurement, - ) - return SummedOp([self, other]) - - def adjoint(self) -> "VectorStateFn": - return VectorStateFn( - self.primitive.conjugate(), - coeff=self.coeff.conjugate(), - is_measurement=(not self.is_measurement), - ) - - def permute(self, permutation: List[int]) -> "VectorStateFn": - new_self = self - new_num_qubits = max(permutation) + 1 - - if self.num_qubits != len(permutation): - # raise OpflowError("New index must be defined for each qubit of the operator.") - pass - if self.num_qubits < new_num_qubits: - # pad the operator with identities - new_self = self._expand_dim(new_num_qubits - self.num_qubits) - qc = QuantumCircuit(new_num_qubits) - - # extend the permutation indices to match the size of the new matrix - permutation = ( - list(filter(lambda x: x not in permutation, range(new_num_qubits))) + permutation - ) - - # decompose permutation into sequence of transpositions - transpositions = arithmetic.transpositions(permutation) - for trans in transpositions: - qc.swap(trans[0], trans[1]) - - from ..primitive_ops.circuit_op import CircuitOp - - matrix = CircuitOp(qc).to_matrix() - vector = new_self.primitive.data - new_vector = cast(np.ndarray, matrix.dot(vector)) - return VectorStateFn( - primitive=new_vector, coeff=self.coeff, is_measurement=self.is_measurement - ) - - def to_dict_fn(self) -> StateFn: - """Creates the equivalent state function of type DictStateFn. - - Returns: - A new DictStateFn equivalent to ``self``. - """ - from .dict_state_fn import DictStateFn - - num_qubits = self.num_qubits - new_dict = {format(i, "b").zfill(num_qubits): v for i, v in enumerate(self.primitive.data)} - return DictStateFn(new_dict, coeff=self.coeff, is_measurement=self.is_measurement) - - def _expand_dim(self, num_qubits: int) -> "VectorStateFn": - primitive = np.zeros(2**num_qubits, dtype=complex) - return VectorStateFn( - self.primitive.tensor(primitive), coeff=self.coeff, is_measurement=self.is_measurement - ) - - def tensor(self, other: OperatorBase) -> OperatorBase: - if isinstance(other, VectorStateFn): - return StateFn( - self.primitive.tensor(other.primitive), - coeff=self.coeff * other.coeff, - is_measurement=self.is_measurement, - ) - return TensoredOp([self, other]) - - def to_density_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_density_matrix", True, self.num_qubits, massive) - return self.primitive.to_operator().data * self.coeff - - def to_matrix(self, massive: bool = False) -> np.ndarray: - OperatorBase._check_massive("to_matrix", False, self.num_qubits, massive) - vec = self.primitive.data * self.coeff - return vec if not self.is_measurement else vec.reshape(1, -1) - - def to_matrix_op(self, massive: bool = False) -> OperatorBase: - return self - - def to_circuit_op(self) -> OperatorBase: - """Return ``StateFnCircuit`` corresponding to this StateFn.""" - # pylint: disable=cyclic-import - from .circuit_state_fn import CircuitStateFn - - csfn = CircuitStateFn.from_vector(self.primitive.data) * self.coeff - return csfn.adjoint() if self.is_measurement else csfn - - def __str__(self) -> str: - prim_str = str(self.primitive) - if self.coeff == 1.0: - return "{}({})".format( - "VectorStateFn" if not self.is_measurement else "MeasurementVector", prim_str - ) - else: - return "{}({}) * {}".format( - "VectorStateFn" if not self.is_measurement else "MeasurementVector", - prim_str, - self.coeff, - ) - - # pylint: disable=too-many-return-statements - def eval( - self, - front: Optional[ - Union[str, Dict[str, complex], np.ndarray, Statevector, OperatorBase] - ] = None, - ) -> Union[OperatorBase, complex]: - if front is None: # this object is already a VectorStateFn - return self - - if not self.is_measurement and isinstance(front, OperatorBase): - raise ValueError( - "Cannot compute overlap with StateFn or Operator if not Measurement. Try taking " - "sf.adjoint() first to convert to measurement." - ) - - if isinstance(front, ListOp) and front.distributive: - return front.combo_fn( - [self.eval(front.coeff * front_elem) for front_elem in front.oplist] - ) - - if not isinstance(front, OperatorBase): - front = StateFn(front) - - # pylint: disable=cyclic-import - from ..operator_globals import EVAL_SIG_DIGITS - from .operator_state_fn import OperatorStateFn - from .circuit_state_fn import CircuitStateFn - from .dict_state_fn import DictStateFn - - if isinstance(front, DictStateFn): - return np.round( - sum( - v * self.primitive.data[int(b, 2)] * front.coeff - for (b, v) in front.primitive.items() - ) - * self.coeff, - decimals=EVAL_SIG_DIGITS, - ) - - if isinstance(front, VectorStateFn): - # Need to extract the element or np.array([1]) is returned. - return np.round( - np.dot(self.to_matrix(), front.to_matrix())[0], decimals=EVAL_SIG_DIGITS - ) - - if isinstance(front, CircuitStateFn): - # Don't reimplement logic from CircuitStateFn - return np.conj(front.adjoint().eval(self.adjoint().primitive)) * self.coeff - - if isinstance(front, OperatorStateFn): - return front.adjoint().eval(self.primitive) * self.coeff - - return front.adjoint().eval(self.adjoint().primitive).adjoint() * self.coeff # type: ignore - - def sample( - self, shots: int = 1024, massive: bool = False, reverse_endianness: bool = False - ) -> dict: - deterministic_counts = self.primitive.probabilities_dict() - # Don't need to square because probabilities_dict already does. - probs = np.array(list(deterministic_counts.values())) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - unique, counts = np.unique( - algorithm_globals.random.choice( - list(deterministic_counts.keys()), size=shots, p=(probs / sum(probs)) - ), - return_counts=True, - ) - counts = dict(zip(unique, counts)) - if reverse_endianness: - scaled_dict = {bstr[::-1]: (prob / shots) for (bstr, prob) in counts.items()} - else: - scaled_dict = {bstr: (prob / shots) for (bstr, prob) in counts.items()} - return dict(sorted(scaled_dict.items(), key=lambda x: x[1], reverse=True)) diff --git a/qiskit/opflow/utils.py b/qiskit/opflow/utils.py deleted file mode 100644 index e766d4f57e32..000000000000 --- a/qiskit/opflow/utils.py +++ /dev/null @@ -1,119 +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. - -"""Utility functions for OperatorFlow""" - -from qiskit.opflow.operator_base import OperatorBase -from qiskit.utils.deprecation import deprecate_func - - -@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: - r""" - Deprecated: Compute commutator of `op_a` and `op_b`. - - .. math:: - - AB - BA. - - Args: - op_a: Operator A - op_b: Operator B - Returns: - OperatorBase: the commutator - """ - return (op_a @ op_b - op_b @ op_a).reduce() - - -@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: - r""" - Deprecated: Compute anti-commutator of `op_a` and `op_b`. - - .. math:: - - AB + BA. - - Args: - op_a: Operator A - op_b: Operator B - Returns: - OperatorBase: the anti-commutator - """ - return (op_a @ op_b + op_b @ op_a).reduce() - - -@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( - op_a: OperatorBase, - op_b: OperatorBase, - op_c: OperatorBase, - sign: bool = False, -) -> OperatorBase: - r""" - Deprecated: Compute symmetric double commutator of `op_a`, `op_b` and `op_c`. - See McWeeny chapter 13.6 Equation of motion methods (page 479) - - If `sign` is `False`, it returns - - .. math:: - - [[A, B], C]/2 + [A, [B, C]]/2 - = (2ABC + 2CBA - BAC - CAB - ACB - BCA)/2. - - If `sign` is `True`, it returns - - .. math:: - \lbrace[A, B], C\rbrace/2 + \lbrace A, [B, C]\rbrace/2 - = (2ABC - 2CBA - BAC + CAB - ACB + BCA)/2. - - Args: - op_a: Operator A - op_b: Operator B - op_c: Operator C - sign: False anti-commutes, True commutes - Returns: - OperatorBase: the double commutator - """ - sign_num = 1 if sign else -1 - - op_ab = op_a @ op_b - op_ba = op_b @ op_a - op_ac = op_a @ op_c - op_ca = op_c @ op_a - - op_abc = op_ab @ op_c - op_cba = op_c @ op_ba - op_bac = op_ba @ op_c - op_cab = op_c @ op_ab - op_acb = op_ac @ op_b - op_bca = op_b @ op_ca - - res = ( - op_abc - - sign_num * op_cba - + 0.5 * (-op_bac + sign_num * op_cab - op_acb + sign_num * op_bca) - ) - - return res.reduce() diff --git a/qiskit/primitives/backend_estimator.py b/qiskit/primitives/backend_estimator.py index 8f62e4797c2f..eb97a3b95259 100644 --- a/qiskit/primitives/backend_estimator.py +++ b/qiskit/primitives/backend_estimator.py @@ -15,7 +15,6 @@ from __future__ import annotations -import typing from collections.abc import Sequence from itertools import accumulate @@ -41,9 +40,6 @@ from .primitive_job import PrimitiveJob from .utils import _circuit_key, _observable_key, init_observable -if typing.TYPE_CHECKING: - from qiskit.opflow import PauliSumOp - def _run_circuits( circuits: QuantumCircuit | list[QuantumCircuit], @@ -268,7 +264,7 @@ def _call( def _run( self, circuits: tuple[QuantumCircuit, ...], - observables: tuple[BaseOperator | PauliSumOp, ...], + observables: tuple[BaseOperator, ...], parameter_values: tuple[tuple[float, ...], ...], **run_options, ): diff --git a/qiskit/primitives/base/base_estimator.py b/qiskit/primitives/base/base_estimator.py index b789863a812d..93b497f02049 100644 --- a/qiskit/primitives/base/base_estimator.py +++ b/qiskit/primitives/base/base_estimator.py @@ -85,7 +85,6 @@ 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 @@ -97,9 +96,6 @@ from .base_primitive import BasePrimitive from . import validation -if typing.TYPE_CHECKING: - from qiskit.opflow import PauliSumOp - T = TypeVar("T", bound=Job) @@ -149,7 +145,7 @@ def __getattr__(self, name: str) -> any: def run( self, circuits: Sequence[QuantumCircuit] | QuantumCircuit, - observables: Sequence[BaseOperator | PauliSumOp | str] | BaseOperator | PauliSumOp | str, + observables: Sequence[BaseOperator | str] | BaseOperator | str, parameter_values: Sequence[Sequence[float]] | Sequence[float] | float | None = None, **run_options, ) -> T: @@ -218,14 +214,14 @@ def _run( @staticmethod @deprecate_func(since="0.46.0") def _validate_observables( - observables: Sequence[BaseOperator | PauliSumOp | str] | BaseOperator | PauliSumOp | str, + observables: Sequence[BaseOperator | str] | BaseOperator | str, ) -> tuple[SparsePauliOp, ...]: return validation._validate_observables(observables) @staticmethod @deprecate_func(since="0.46.0") def _cross_validate_circuits_observables( - circuits: tuple[QuantumCircuit, ...], observables: tuple[BaseOperator | PauliSumOp, ...] + circuits: tuple[QuantumCircuit, ...], observables: tuple[BaseOperator, ...] ) -> None: return validation._cross_validate_circuits_observables(circuits, observables) diff --git a/qiskit/primitives/estimator.py b/qiskit/primitives/estimator.py index 69f36e1723f1..9edfe35d7eb2 100644 --- a/qiskit/primitives/estimator.py +++ b/qiskit/primitives/estimator.py @@ -17,7 +17,6 @@ from collections.abc import Sequence from typing import Any -import typing import numpy as np @@ -35,9 +34,6 @@ init_observable, ) -if typing.TYPE_CHECKING: - from qiskit.opflow import PauliSumOp - class Estimator(BaseEstimator[PrimitiveJob[EstimatorResult]]): """ @@ -130,7 +126,7 @@ def _call( def _run( self, circuits: tuple[QuantumCircuit, ...], - observables: tuple[BaseOperator | PauliSumOp, ...], + observables: tuple[BaseOperator, ...], parameter_values: tuple[tuple[float, ...], ...], **run_options, ): diff --git a/qiskit/primitives/utils.py b/qiskit/primitives/utils.py index c2f915c8f0da..75d25abbbe30 100644 --- a/qiskit/primitives/utils.py +++ b/qiskit/primitives/utils.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 @@ -15,22 +15,17 @@ from __future__ import annotations import warnings -import sys -import typing from collections.abc import Iterable import numpy as np -from qiskit.circuit import Instruction, ParameterExpression, QuantumCircuit +from qiskit.circuit import Instruction, QuantumCircuit from qiskit.circuit.bit import Bit from qiskit.circuit.library.data_preparation import Initialize 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 -if typing.TYPE_CHECKING: - from qiskit.opflow import PauliSumOp - def init_circuit(state: QuantumCircuit | Statevector) -> QuantumCircuit: """Initialize state by converting the input to a quantum circuit. @@ -50,7 +45,7 @@ def init_circuit(state: QuantumCircuit | Statevector) -> QuantumCircuit: return qc -def init_observable(observable: BaseOperator | PauliSumOp | str) -> SparsePauliOp: +def init_observable(observable: BaseOperator | str) -> SparsePauliOp: """Initialize observable by converting the input to a :class:`~qiskit.quantum_info.SparsePauliOp`. Args: @@ -59,27 +54,10 @@ def init_observable(observable: BaseOperator | PauliSumOp | str) -> SparsePauliO Returns: The observable as :class:`~qiskit.quantum_info.SparsePauliOp`. - Raises: - TypeError: If the observable is a :class:`~qiskit.opflow.PauliSumOp` and has a parameterized - coefficient. """ - # This dance is to avoid importing the deprecated `qiskit.opflow` if the user hasn't already - # done so. They can't hold a `qiskit.opflow.PauliSumOp` if `qiskit.opflow` hasn't been - # imported, and we don't want unrelated Qiskit library code to be responsible for the first - # import, so the deprecation warnings will show. - if "qiskit.opflow" in sys.modules: - pauli_sum_check = sys.modules["qiskit.opflow"].PauliSumOp - else: - pauli_sum_check = () if isinstance(observable, SparsePauliOp): return observable - elif isinstance(observable, pauli_sum_check): - if isinstance(observable.coeff, ParameterExpression): - raise TypeError( - f"Observable must have numerical coefficient, not {type(observable.coeff)}." - ) - return observable.coeff * observable.primitive elif isinstance(observable, BaseOperator) and not isinstance(observable, BasePauli): return SparsePauliOp.from_operator(observable) else: diff --git a/qiskit/providers/fake_provider/fake_backend_v2.py b/qiskit/providers/fake_provider/fake_backend_v2.py index 23b0c58dadce..96cd6dff9d37 100644 --- a/qiskit/providers/fake_provider/fake_backend_v2.py +++ b/qiskit/providers/fake_provider/fake_backend_v2.py @@ -42,7 +42,7 @@ def __init__(self): None, name="FakeV2", description="A fake BackendV2 example", - online_date=datetime.datetime.utcnow(), + online_date=datetime.datetime.now(datetime.timezone.utc), backend_version="0.0.1", ) self._qubit_properties = [ @@ -116,7 +116,7 @@ def __init__(self, bidirectional=True): None, name="Fake5QV2", description="A fake BackendV2 example", - online_date=datetime.datetime.utcnow(), + online_date=datetime.datetime.now(datetime.timezone.utc), backend_version="0.0.1", ) qubit_properties = [ @@ -188,7 +188,7 @@ def __init__(self): None, name="FakeSimpleV2", description="A fake simple BackendV2 example", - online_date=datetime.datetime.utcnow(), + online_date=datetime.datetime.now(datetime.timezone.utc), backend_version="0.0.1", ) self._lam = Parameter("lambda") diff --git a/qiskit/providers/fake_provider/fake_mumbai_v2.py b/qiskit/providers/fake_provider/fake_mumbai_v2.py index 1a5f1c24d4a3..8474319dfa62 100644 --- a/qiskit/providers/fake_provider/fake_mumbai_v2.py +++ b/qiskit/providers/fake_provider/fake_mumbai_v2.py @@ -39,7 +39,7 @@ def __init__(self): super().__init__( name="FakeMumbaiFractionalCX", description="A fake BackendV2 example based on IBM Mumbai", - online_date=datetime.datetime.utcnow(), + online_date=datetime.datetime.now(datetime.timezone.utc), backend_version="0.0.1", ) dt = 0.2222222222222222e-9 diff --git a/qiskit/pulse/instructions/instruction.py b/qiskit/pulse/instructions/instruction.py index 559b4fb38dc7..18e724241f97 100644 --- a/qiskit/pulse/instructions/instruction.py +++ b/qiskit/pulse/instructions/instruction.py @@ -22,14 +22,11 @@ sched += Delay(duration, channel) # Delay is a specific subclass of Instruction """ from abc import ABC, abstractmethod -from typing import Callable, Iterable, List, Optional, Set, Tuple +from typing import Iterable, List, Optional, Set, Tuple from qiskit.circuit import Parameter from qiskit.pulse.channels import Channel from qiskit.pulse.exceptions import PulseError -from qiskit.utils import optionals as _optionals - -from qiskit.utils.deprecation import deprecate_func # pylint: disable=bad-docstring-quotes @@ -222,76 +219,6 @@ def is_parameterized(self) -> bool: """Return True iff the instruction is parameterized.""" return any(self.parameters) - @deprecate_func( - additional_msg=( - "No direct alternative is being provided to drawing individual pulses. But, " - "instructions can be visualized as part of a complete schedule using " - "``qiskit.visualization.pulse_drawer``." - ), - since="0.23.0", - package_name="qiskit-terra", - ) - @_optionals.HAS_MATPLOTLIB.require_in_call - def draw( - self, - dt: float = 1, - style=None, - filename: Optional[str] = None, - interp_method: Optional[Callable] = None, - scale: float = 1, - plot_all: bool = False, - plot_range: Optional[Tuple[float]] = None, - interactive: bool = False, - table: bool = True, - label: bool = False, - framechange: bool = True, - channels: Optional[List[Channel]] = None, - ): - """Plot the instruction. - - Args: - dt: Time interval of samples - style (Optional[SchedStyle]): A style sheet to configure plot appearance - filename: Name required to save pulse image - interp_method: A function for interpolation - scale: Relative visual scaling of waveform amplitudes - plot_all: Plot empty channels - plot_range: A tuple of time range to plot - interactive: When set true show the circuit in a new window - (this depends on the matplotlib backend being used supporting this) - table: Draw event table for supported instructions - label: Label individual instructions - framechange: Add framechange indicators - channels: A list of channel names to plot - - Returns: - matplotlib.figure: A matplotlib figure object of the pulse schedule - """ - # pylint: disable=cyclic-import - from qiskit.visualization.pulse.matplotlib import ScheduleDrawer - from qiskit.visualization.utils import matplotlib_close_if_inline - - drawer = ScheduleDrawer(style=style) - image = drawer.draw( - self, - dt=dt, - interp_method=interp_method, - scale=scale, - plot_range=plot_range, - plot_all=plot_all, - table=table, - label=label, - framechange=framechange, - channels=channels, - ) - if filename: - image.savefig(filename, dpi=drawer.style.dpi, bbox_inches="tight") - - matplotlib_close_if_inline(image) - if image and interactive: - image.show() - return image - def __eq__(self, other: "Instruction") -> bool: """Check if this Instruction is equal to the `other` instruction. diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py index 9bc18c9c7f13..a943bb1acf60 100644 --- a/qiskit/pulse/library/symbolic_pulses.py +++ b/qiskit/pulse/library/symbolic_pulses.py @@ -23,19 +23,14 @@ from copy import deepcopy import numpy as np +import symengine as sym from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType from qiskit.pulse.exceptions import PulseError from qiskit.pulse.library.pulse import Pulse from qiskit.pulse.library.waveform import Waveform -from qiskit.utils import optionals as _optional from qiskit.utils.deprecation import deprecate_arg -if _optional.HAS_SYMENGINE: - import symengine as sym -else: - import sympy as sym - def _lifted_gaussian( t: sym.Symbol, @@ -183,34 +178,31 @@ def __set__(self, instance, value): continue params.append(p) - if _optional.HAS_SYMENGINE: - try: - lamb = sym.lambdify(params, [value], real=False) - - def _wrapped_lamb(*args): - if isinstance(args[0], np.ndarray): - # When the args[0] is a vector ("t"), tile other arguments args[1:] - # to prevent evaluation from looping over each element in t. - t = args[0] - args = np.hstack( - ( - t.reshape(t.size, 1), - np.tile(args[1:], t.size).reshape(t.size, len(args) - 1), - ) + try: + lamb = sym.lambdify(params, [value], real=False) + + def _wrapped_lamb(*args): + if isinstance(args[0], np.ndarray): + # When the args[0] is a vector ("t"), tile other arguments args[1:] + # to prevent evaluation from looping over each element in t. + t = args[0] + args = np.hstack( + ( + t.reshape(t.size, 1), + np.tile(args[1:], t.size).reshape(t.size, len(args) - 1), ) - return lamb(args) - - func = _wrapped_lamb - except RuntimeError: - # Currently symengine doesn't support complex_double version for - # several functions such as comparison operator and piecewise. - # If expression contains these function, it fall back to sympy lambdify. - # See https://github.com/symengine/symengine.py/issues/406 for details. - import sympy - - func = sympy.lambdify(params, value) - else: - func = sym.lambdify(params, value) + ) + return lamb(args) + + func = _wrapped_lamb + except RuntimeError: + # Currently symengine doesn't support complex_double version for + # several functions such as comparison operator and piecewise. + # If expression contains these function, it fall back to sympy lambdify. + # See https://github.com/symengine/symengine.py/issues/406 for details. + import sympy + + func = sympy.lambdify(params, value) self.lambda_funcs[key] = func diff --git a/qiskit/qasm/__init__.py b/qiskit/qasm/__init__.py deleted file mode 100644 index 322d230343c6..000000000000 --- a/qiskit/qasm/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -========================= -Qasm (:mod:`qiskit.qasm`) -========================= - -.. currentmodule:: qiskit.qasm - -QASM Routines -============= - -.. autoclass:: Qasm - - -Pygments -======== - -.. autoclass:: OpenQASMLexer - :class-doc-from: class - -.. autoclass:: QasmHTMLStyle - :class-doc-from: class - -.. autoclass:: QasmTerminalStyle - :class-doc-from: class -""" - -from numpy import pi - -from qiskit.utils.optionals import HAS_PYGMENTS - -from .qasm import Qasm -from .exceptions import QasmError - - -def __getattr__(name): - if name in ("OpenQASMLexer", "QasmHTMLStyle", "QasmTerminalStyle"): - import qiskit.qasm.pygments - - return getattr(qiskit.qasm.pygments, name) - - raise AttributeError(f"module '{__name__}' has no attribute '{name}'") diff --git a/qiskit/qasm/exceptions.py b/qiskit/qasm/exceptions.py deleted file mode 100644 index 7bc40db35b8b..000000000000 --- a/qiskit/qasm/exceptions.py +++ /dev/null @@ -1,16 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of 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 while handling OpenQASM 2.0.""" - -# Re-export from the new place to ensure that old code continues to work. -from qiskit.qasm2.exceptions import QASM2Error as QasmError # pylint: disable=unused-import diff --git a/qiskit/qasm/node/__init__.py b/qiskit/qasm/node/__init__.py deleted file mode 100644 index 09e24db01bf5..000000000000 --- a/qiskit/qasm/node/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""OpenQASM 2 nodes.""" - -from .barrier import Barrier -from .binaryop import BinaryOp -from .binaryoperator import BinaryOperator -from .cnot import Cnot -from .creg import Creg -from .customunitary import CustomUnitary -from .expressionlist import ExpressionList -from .external import External -from .gate import Gate -from .gatebody import GateBody -from .id import Id -from .idlist import IdList -from .if_ import If -from .indexedid import IndexedId -from .intnode import Int -from .format import Format -from .measure import Measure -from .opaque import Opaque -from .prefix import Prefix -from .primarylist import PrimaryList -from .program import Program -from .qreg import Qreg -from .real import Real -from .reset import Reset -from .unaryoperator import UnaryOperator -from .universalunitary import UniversalUnitary -from .nodeexception import NodeException diff --git a/qiskit/qasm/node/barrier.py b/qiskit/qasm/node/barrier.py deleted file mode 100644 index afa16f2f9222..000000000000 --- a/qiskit/qasm/node/barrier.py +++ /dev/null @@ -1,30 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM barrier statement.""" - -from .node import Node - - -class Barrier(Node): - """Node for an OPENQASM barrier statement. - - children[0] is a primarylist node. - """ - - def __init__(self, children): - """Create the barrier node.""" - super().__init__("barrier", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "barrier " + self.children[0].qasm() + ";" diff --git a/qiskit/qasm/node/binaryop.py b/qiskit/qasm/node/binaryop.py deleted file mode 100644 index 45d4de4364d0..000000000000 --- a/qiskit/qasm/node/binaryop.py +++ /dev/null @@ -1,59 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM binary operation expression.""" - -from qiskit.exceptions import MissingOptionalLibraryError -from .node import Node - - -class BinaryOp(Node): - """Node for an OPENQASM binary operation expression. - - children[0] is the operation, as a binary operator node. - children[1] is the left expression. - children[2] is the right expression. - """ - - def __init__(self, children): - """Create the binaryop node.""" - super().__init__("binop", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return ( - "(" + self.children[1].qasm() + self.children[0].value + self.children[2].qasm() + ")" - ) - - def latex(self): - """Return the corresponding math mode latex string.""" - try: - from pylatexenc.latexencode import utf8tolatex - except ImportError as ex: - raise MissingOptionalLibraryError( - "pylatexenc", "latex-from-qasm exporter", "pip install pylatexenc" - ) from ex - return utf8tolatex(self.sym()) - - def real(self): - """Return the correspond floating point number.""" - operation = self.children[0].operation() - lhs = self.children[1].real() - rhs = self.children[2].real() - return operation(lhs, rhs) - - def sym(self, nested_scope=None): - """Return the correspond symbolic number.""" - operation = self.children[0].operation() - lhs = self.children[1].sym(nested_scope) - rhs = self.children[2].sym(nested_scope) - return operation(lhs, rhs) diff --git a/qiskit/qasm/node/binaryoperator.py b/qiskit/qasm/node/binaryoperator.py deleted file mode 100644 index 57e7d883c547..000000000000 --- a/qiskit/qasm/node/binaryoperator.py +++ /dev/null @@ -1,52 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM binary operator.""" - -import operator - -from .node import Node -from .nodeexception import NodeException - - -VALID_OPERATORS = { - "+": operator.add, - "-": operator.sub, - "*": operator.mul, - "/": operator.truediv, - "^": operator.pow, -} - - -class BinaryOperator(Node): - """Node for an OPENQASM binary operator. - - This node has no children. The data is in the value field. - """ - - def __init__(self, operation): - """Create the operator node.""" - super().__init__("operator", None, None) - self.value = operation - - def operation(self): - """ - Return the operator as a function f(left, right). - """ - try: - return VALID_OPERATORS[self.value] - except KeyError as ex: - raise NodeException(f"internal error: undefined operator '{self.value}'") from ex - - def qasm(self): - """Return the OpenQASM 2 representation.""" - return self.value diff --git a/qiskit/qasm/node/cnot.py b/qiskit/qasm/node/cnot.py deleted file mode 100644 index 3034d0ca8e32..000000000000 --- a/qiskit/qasm/node/cnot.py +++ /dev/null @@ -1,31 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM CNOT statement.""" - -from .node import Node - - -class Cnot(Node): - """Node for an OPENQASM CNOT statement. - - children[0], children[1] are id nodes if CX is inside a gate body, - otherwise they are primary nodes. - """ - - def __init__(self, children): - """Create the cnot node.""" - super().__init__("cnot", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "CX " + self.children[0].qasm() + "," + self.children[1].qasm() + ";" diff --git a/qiskit/qasm/node/creg.py b/qiskit/qasm/node/creg.py deleted file mode 100644 index 6a6bac6cb400..000000000000 --- a/qiskit/qasm/node/creg.py +++ /dev/null @@ -1,45 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM creg statement.""" -from .node import Node - - -class Creg(Node): - """Node for an OPENQASM creg statement. - - children[0] is an indexedid node. - """ - - def __init__(self, children): - """Create the creg node.""" - super().__init__("creg", children, None) - # This is the indexed id, the full "id[n]" object - self.id = children[0] # pylint: disable=invalid-name - # Name of the creg - self.name = self.id.name - # Source line number - self.line = self.id.line - # Source file name - self.file = self.id.file - # Size of the register - self.index = self.id.index - - def to_string(self, indent): - """Print the node data, with indent.""" - ind = indent * " " - print(ind, "creg") - self.children[0].to_string(indent + 3) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "creg " + self.id.qasm() + ";" diff --git a/qiskit/qasm/node/customunitary.py b/qiskit/qasm/node/customunitary.py deleted file mode 100644 index 393cf160f3af..000000000000 --- a/qiskit/qasm/node/customunitary.py +++ /dev/null @@ -1,49 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM custom gate statement.""" -from .node import Node - - -class CustomUnitary(Node): - """Node for an OPENQASM custom gate statement. - - children[0] is an id node. - children[1] is an exp_list (if len==3) or primary_list. - children[2], if present, is a primary_list. - - Has properties: - .id = id node - .name = gate name string - .arguments = None or exp_list node - .bitlist = primary_list node - """ - - def __init__(self, children): - """Create the custom gate node.""" - super().__init__("custom_unitary", children, None) - self.id = children[0] # pylint: disable=invalid-name - self.name = self.id.name - if len(children) == 3: - self.arguments = children[1] - self.bitlist = children[2] - else: - self.arguments = None - self.bitlist = children[1] - - def qasm(self): - """Return the corresponding OPENQASM string.""" - string = self.name - if self.arguments is not None: - string += "(" + self.arguments.qasm() + ")" - string += " " + self.bitlist.qasm() + ";" - return string diff --git a/qiskit/qasm/node/expressionlist.py b/qiskit/qasm/node/expressionlist.py deleted file mode 100644 index bef4f7b2e268..000000000000 --- a/qiskit/qasm/node/expressionlist.py +++ /dev/null @@ -1,33 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM expression list.""" -from .node import Node - - -class ExpressionList(Node): - """Node for an OPENQASM expression list. - - children are expression nodes. - """ - - def __init__(self, children): - """Create the expression list node.""" - super().__init__("expression_list", children, None) - - def size(self): - """Return the number of expressions.""" - return len(self.children) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return ",".join([self.children[j].qasm() for j in range(self.size())]) diff --git a/qiskit/qasm/node/external.py b/qiskit/qasm/node/external.py deleted file mode 100644 index 2aecbf26bb62..000000000000 --- a/qiskit/qasm/node/external.py +++ /dev/null @@ -1,87 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM external function.""" - -import numpy as np - -from qiskit.exceptions import MissingOptionalLibraryError -from .node import Node -from .nodeexception import NodeException - - -class External(Node): - """Node for an OPENQASM external function. - - children[0] is an id node with the name of the function. - children[1] is an expression node. - """ - - def __init__(self, children): - """Create the external node.""" - super().__init__("external", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return self.children[0].qasm() + "(" + self.children[1].qasm() + ")" - - def latex(self): - """Return the corresponding math mode latex string.""" - try: - from pylatexenc.latexencode import utf8tolatex - except ImportError as ex: - raise MissingOptionalLibraryError( - "pylatexenc", "latex-from-qasm exporter", "pip install pylatexenc" - ) from ex - return utf8tolatex(self.sym()) - - def real(self, nested_scope=None): - """Return the correspond floating point number.""" - op = self.children[0].name - expr = self.children[1] - dispatch = { - "sin": np.sin, - "cos": np.cos, - "tan": np.tan, - "asin": np.arcsin, - "acos": np.arccos, - "atan": np.arctan, - "exp": np.exp, - "ln": np.log, - "sqrt": np.sqrt, - } - if op in dispatch: - arg = expr.real(nested_scope) - return dispatch[op](arg) - else: - raise NodeException("internal error: undefined external") - - def sym(self, nested_scope=None): - """Return the corresponding symbolic expression.""" - op = self.children[0].name - expr = self.children[1] - dispatch = { - "sin": np.sin, - "cos": np.cos, - "tan": np.tan, - "asin": np.arcsin, - "acos": np.arccos, - "atan": np.arctan, - "exp": np.exp, - "ln": np.log, - "sqrt": np.sqrt, - } - if op in dispatch: - arg = expr.sym(nested_scope) - return dispatch[op](arg) - else: - raise NodeException("internal error: undefined external") diff --git a/qiskit/qasm/node/format.py b/qiskit/qasm/node/format.py deleted file mode 100644 index 3ced9ca08a6a..000000000000 --- a/qiskit/qasm/node/format.py +++ /dev/null @@ -1,37 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM file identifier/version statement.""" - -import re - -from .node import Node - - -class Format(Node): - """Node for an OPENQASM file identifier/version statement.""" - - def __init__(self, value): - """Create the version node.""" - super().__init__("format", None, None) - parts = re.match(r"(\w+)\s+(\d+)(\.(\d+))?", value) - self.language = parts.group(1) - self.majorversion = parts.group(2) - self.minorversion = parts.group(4) if parts.group(4) is not None else "0" - - def version(self): - """Return the version.""" - return f"{self.majorversion}.{self.minorversion}" - - def qasm(self): - """Return the corresponding format string.""" - return f"{self.language} {self.version()};" diff --git a/qiskit/qasm/node/gate.py b/qiskit/qasm/node/gate.py deleted file mode 100644 index 122bd4f935df..000000000000 --- a/qiskit/qasm/node/gate.py +++ /dev/null @@ -1,62 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM gate definition.""" -from .node import Node - - -class Gate(Node): - """Node for an OPENQASM gate definition. - - children[0] is an id node. - If len(children) is 3, children[1] is an idlist node, - and children[2] is a gatebody node. - Otherwise, children[1] is an expressionlist node, - children[2] is an idlist node, and children[3] is a gatebody node. - """ - - def __init__(self, children): - """Create the gate node.""" - super().__init__("gate", children, None) - self.id = children[0] # pylint: disable=invalid-name - # The next three fields are required by the symbtab - self.name = self.id.name - self.line = self.id.line - self.file = self.id.file - - if len(children) == 3: - self.arguments = None - self.bitlist = children[1] - self.body = children[2] - else: - self.arguments = children[1] - self.bitlist = children[2] - self.body = children[3] - - def n_args(self): - """Return the number of parameter expressions.""" - if self.arguments: - return self.arguments.size() - return 0 - - def n_bits(self): - """Return the number of qubit arguments.""" - return self.bitlist.size() - - def qasm(self): - """Return the corresponding OPENQASM string.""" - string = "gate " + self.name - if self.arguments is not None: - string += "(" + self.arguments.qasm() + ")" - string += " " + self.bitlist.qasm() + "\n" - string += "{\n" + self.body.qasm() + "}" - return string diff --git a/qiskit/qasm/node/gatebody.py b/qiskit/qasm/node/gatebody.py deleted file mode 100644 index a7c591b549b8..000000000000 --- a/qiskit/qasm/node/gatebody.py +++ /dev/null @@ -1,41 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM custom gate body.""" -from .node import Node - - -class GateBody(Node): - """Node for an OPENQASM custom gate body. - - children is a list of gate operation nodes. - These are one of barrier, custom_unitary, U, or CX. - """ - - def __init__(self, children): - """Create the gatebody node.""" - super().__init__("gate_body", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - string = "" - for children in self.children: - string += " " + children.qasm() + "\n" - return string - - def calls(self): - """Return a list of custom gate names in this gate body.""" - lst = [] - for children in self.children: - if children.type == "custom_unitary": - lst.append(children.name) - return lst diff --git a/qiskit/qasm/node/id.py b/qiskit/qasm/node/id.py deleted file mode 100644 index 041ea452a4bf..000000000000 --- a/qiskit/qasm/node/id.py +++ /dev/null @@ -1,78 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM id.""" - -from .node import Node -from .nodeexception import NodeException - - -class Id(Node): - """Node for an OPENQASM id. - - The node has no children but has fields name, line, and file. - There is a flag is_bit that is set when XXXXX to help with scoping. - """ - - def __init__(self, id, line, file): - """Create the id node.""" - # pylint: disable=redefined-builtin - super().__init__("id", None, None) - self.name = id - self.line = line - self.file = file - # To help with scoping rules, so we know the id is a bit, - # this flag is set to True when the id appears in a gate declaration - self.is_bit = False - - def to_string(self, indent): - """Print the node with indent.""" - ind = indent * " " - print(ind, "id", self.name) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return self.name - - def latex(self, nested_scope=None): - """Return the correspond math mode latex string.""" - if not nested_scope: - return "\textrm{" + self.name + "}" - else: - if self.name not in nested_scope[-1]: - raise NodeException( - "Expected local parameter name: ", - "name=%s, " % self.name, - "line=%s, " % self.line, - "file=%s" % self.file, - ) - - return nested_scope[-1][self.name].latex(nested_scope[0:-1]) - - def sym(self, nested_scope=None): - """Return the correspond symbolic number.""" - if not nested_scope or self.name not in nested_scope[-1]: - raise NodeException( - "Expected local parameter name: ", - f"name={self.name}, line={self.line}, file={self.file}", - ) - return nested_scope[-1][self.name].sym(nested_scope[0:-1]) - - def real(self, nested_scope=None): - """Return the correspond floating point number.""" - if not nested_scope or self.name not in nested_scope[-1]: - raise NodeException( - "Expected local parameter name: ", - f"name={self.name}, line={self.line}, file={self.file}", - ) - - return nested_scope[-1][self.name].real(nested_scope[0:-1]) diff --git a/qiskit/qasm/node/idlist.py b/qiskit/qasm/node/idlist.py deleted file mode 100644 index 889fd887f8df..000000000000 --- a/qiskit/qasm/node/idlist.py +++ /dev/null @@ -1,33 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM idlist.""" -from .node import Node - - -class IdList(Node): - """Node for an OPENQASM idlist. - - children is a list of id nodes. - """ - - def __init__(self, children): - """Create the idlist node.""" - super().__init__("id_list", children, None) - - def size(self): - """Return the length of the list.""" - return len(self.children) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return ",".join([self.children[j].qasm() for j in range(self.size())]) diff --git a/qiskit/qasm/node/if_.py b/qiskit/qasm/node/if_.py deleted file mode 100644 index c056b078fe01..000000000000 --- a/qiskit/qasm/node/if_.py +++ /dev/null @@ -1,39 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM if statement.""" -from .node import Node - - -class If(Node): - """Node for an OPENQASM if statement. - - children[0] is an id node. - children[1] is an integer node. - children[2] is quantum operation node, including U, CX, custom_unitary, - measure, reset, (and BUG: barrier, if). - """ - - def __init__(self, children): - """Create the if node.""" - super().__init__("if", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return ( - "if(" - + self.children[0].qasm() - + "==" - + str(self.children[1].value) - + ") " - + self.children[2].qasm() - ) diff --git a/qiskit/qasm/node/indexedid.py b/qiskit/qasm/node/indexedid.py deleted file mode 100644 index ea6aa93444d6..000000000000 --- a/qiskit/qasm/node/indexedid.py +++ /dev/null @@ -1,41 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM indexed id.""" - -from .node import Node - - -class IndexedId(Node): - """Node for an OPENQASM indexed id. - - children[0] is an id node. - children[1] is an Int node. - """ - - def __init__(self, children): - """Create the indexed id node.""" - super().__init__("indexed_id", children, None) - self.id = children[0] # pylint: disable=invalid-name - self.name = self.id.name - self.line = self.id.line - self.file = self.id.file - self.index = children[1].value - - def to_string(self, indent): - """Print with indent.""" - ind = indent * " " - print(ind, "indexed_id", self.name, self.index) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return self.name + "[%d]" % self.index diff --git a/qiskit/qasm/node/intnode.py b/qiskit/qasm/node/intnode.py deleted file mode 100644 index 2b61e670c06b..000000000000 --- a/qiskit/qasm/node/intnode.py +++ /dev/null @@ -1,51 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM integer.""" - -from .node import Node - - -class Int(Node): - """Node for an OPENQASM integer. - - This node has no children. The data is in the value field. - """ - - def __init__(self, id): - """Create the integer node.""" - # pylint: disable=redefined-builtin - super().__init__("int", None, None) - self.value = id - - def to_string(self, indent): - """Print with indent.""" - ind = indent * " " - print(ind, "int", self.value) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "%d" % self.value - - def latex(self): - """Return the corresponding math mode latex string.""" - return "%d" % self.value - - def sym(self, nested_scope=None): - """Return the correspond symbolic number.""" - del nested_scope - return float(self.value) - - def real(self, nested_scope=None): - """Return the correspond floating point number.""" - del nested_scope # ignored - return float(self.value) diff --git a/qiskit/qasm/node/measure.py b/qiskit/qasm/node/measure.py deleted file mode 100644 index c2045fba54c8..000000000000 --- a/qiskit/qasm/node/measure.py +++ /dev/null @@ -1,30 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM measure statement.""" -from .node import Node - - -class Measure(Node): - """Node for an OPENQASM measure statement. - - children[0] is a primary node (id or indexedid) - children[1] is a primary node (id or indexedid) - """ - - def __init__(self, children): - """Create the measure node.""" - super().__init__("measure", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "measure " + self.children[0].qasm() + " -> " + self.children[1].qasm() + ";" diff --git a/qiskit/qasm/node/node.py b/qiskit/qasm/node/node.py deleted file mode 100644 index 6f64dfb1343f..000000000000 --- a/qiskit/qasm/node/node.py +++ /dev/null @@ -1,59 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of 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 node object for the OPENQASM syntax tree.""" - - -class Node: - """Base node object for the OPENQASM syntax tree.""" - - def __init__(self, type, children=None, root=None): - """Construct a new node object.""" - # pylint: disable=redefined-builtin - self.type = type - if children: - self.children = children - else: - self.children = [] - self.root = root - # True if this node is an expression node, False otherwise - self.expression = False - - def is_expression(self): - """Return True if this is an expression node.""" - return self.expression - - def add_child(self, node): - """Add a child node.""" - self.children.append(node) - - def to_string(self, indent): - """Print with indent.""" - ind = indent * " " - if self.root: - print(ind, self.type, "---", self.root) - else: - print(ind, self.type) - indent = indent + 3 - ind = indent * " " - for children in self.children: - if children is None: - print("OOPS! type of parent is", type(self)) - print(self.children) - if isinstance(children, str): - print(ind, children) - elif isinstance(children, int): - print(ind, str(children)) - elif isinstance(children, float): - print(ind, str(children)) - else: - children.to_string(indent) diff --git a/qiskit/qasm/node/nodeexception.py b/qiskit/qasm/node/nodeexception.py deleted file mode 100644 index d351f499c929..000000000000 --- a/qiskit/qasm/node/nodeexception.py +++ /dev/null @@ -1,26 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of 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 while interpreting nodes.""" - - -class NodeException(Exception): - """Base class for errors raised while interpreting nodes.""" - - def __init__(self, *msg): - """Set the error message.""" - super().__init__(*msg) - self.msg = " ".join(msg) - - def __str__(self): - """Return the message.""" - return repr(self.msg) diff --git a/qiskit/qasm/node/opaque.py b/qiskit/qasm/node/opaque.py deleted file mode 100644 index 866ee711840d..000000000000 --- a/qiskit/qasm/node/opaque.py +++ /dev/null @@ -1,58 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM opaque gate declaration.""" - -from .node import Node - - -class Opaque(Node): - """Node for an OPENQASM opaque gate declaration. - - children[0] is an id node. - If len(children) is 3, children[1] is an expressionlist node, - and children[2] is an idlist node. - Otherwise, children[1] is an idlist node. - """ - - def __init__(self, children): - """Create the opaque gate node.""" - super().__init__("opaque", children, None) - self.id = children[0] # pylint: disable=invalid-name - # The next three fields are required by the symbtab - self.name = self.id.name - self.line = self.id.line - self.file = self.id.file - if len(children) == 3: - self.arguments = children[1] - self.bitlist = children[2] - else: - self.arguments = None - self.bitlist = children[1] - - def n_args(self): - """Return the number of parameter expressions.""" - if self.arguments: - return self.arguments.size() - return 0 - - def n_bits(self): - """Return the number of qubit arguments.""" - return self.bitlist.size() - - def qasm(self): - """Return the corresponding OPENQASM string.""" - string = "opaque %s" % self.name - if self.arguments is not None: - string += "(" + self.arguments.qasm() + ")" - string += " " + self.bitlist.qasm() + ";" - return string diff --git a/qiskit/qasm/node/prefix.py b/qiskit/qasm/node/prefix.py deleted file mode 100644 index c144e2b5e280..000000000000 --- a/qiskit/qasm/node/prefix.py +++ /dev/null @@ -1,54 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM prefix expression.""" - -from qiskit.exceptions import MissingOptionalLibraryError -from .node import Node - - -class Prefix(Node): - """Node for an OPENQASM prefix expression. - - children[0] is a unary operator node. - children[1] is an expression node. - """ - - def __init__(self, children): - """Create the prefix node.""" - super().__init__("prefix", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return self.children[0].value + "(" + self.children[1].qasm() + ")" - - def latex(self): - """Return the corresponding math mode latex string.""" - try: - from pylatexenc.latexencode import utf8tolatex - except ImportError as ex: - raise MissingOptionalLibraryError( - "pylatexenc", "latex-from-qasm exporter", "pip install pylatexenc" - ) from ex - return utf8tolatex(self.sym()) - - def real(self): - """Return the correspond floating point number.""" - operation = self.children[0].operation() - expr = self.children[1].real() - return operation(expr) - - def sym(self, nested_scope=None): - """Return the correspond symbolic number.""" - operation = self.children[0].operation() - expr = self.children[1].sym(nested_scope) - return operation(expr) diff --git a/qiskit/qasm/node/primarylist.py b/qiskit/qasm/node/primarylist.py deleted file mode 100644 index 20e20f7b30d3..000000000000 --- a/qiskit/qasm/node/primarylist.py +++ /dev/null @@ -1,33 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM primarylist.""" -from .node import Node - - -class PrimaryList(Node): - """Node for an OPENQASM primarylist. - - children is a list of primary nodes. Primary nodes are indexedid or id. - """ - - def __init__(self, children): - """Create the primarylist node.""" - super().__init__("primary_list", children, None) - - def size(self): - """Return the size of the list.""" - return len(self.children) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return ",".join([self.children[j].qasm() for j in range(self.size())]) diff --git a/qiskit/qasm/node/program.py b/qiskit/qasm/node/program.py deleted file mode 100644 index 4475cb12f3d2..000000000000 --- a/qiskit/qasm/node/program.py +++ /dev/null @@ -1,32 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM program.""" -from .node import Node - - -class Program(Node): - """Node for an OPENQASM program. - - children is a list of nodes (statements). - """ - - def __init__(self, children): - """Create the program node.""" - super().__init__("program", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - string = "" - for children in self.children: - string += children.qasm() + "\n" - return string diff --git a/qiskit/qasm/node/qreg.py b/qiskit/qasm/node/qreg.py deleted file mode 100644 index fb384bd342e3..000000000000 --- a/qiskit/qasm/node/qreg.py +++ /dev/null @@ -1,45 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM qreg statement.""" -from .node import Node - - -class Qreg(Node): - """Node for an OPENQASM qreg statement. - - children[0] is an indexedid node. - """ - - def __init__(self, children): - """Create the qreg node.""" - super().__init__("qreg", children, None) - # This is the indexed id, the full "id[n]" object - self.id = children[0] # pylint: disable=invalid-name - # Name of the qreg - self.name = self.id.name - # Source line number - self.line = self.id.line - # Source file name - self.file = self.id.file - # Size of the register - self.index = self.id.index - - def to_string(self, indent): - """Print the node data, with indent.""" - ind = indent * " " - print(ind, "qreg") - self.children[0].to_string(indent + 3) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "qreg " + self.id.qasm() + ";" diff --git a/qiskit/qasm/node/real.py b/qiskit/qasm/node/real.py deleted file mode 100644 index f967b7582502..000000000000 --- a/qiskit/qasm/node/real.py +++ /dev/null @@ -1,63 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM real number.""" - -import numpy as np - -from qiskit.exceptions import MissingOptionalLibraryError -from .node import Node - - -class Real(Node): - """Node for an OPENQASM real number. - - This node has no children. The data is in the value field. - """ - - def __init__(self, id): - """Create the real node.""" - # pylint: disable=redefined-builtin - super().__init__("real", None, None) - self.value = id - - def to_string(self, indent): - """Print with indent.""" - ind = indent * " " - print(ind, "real", self.value) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - if self.value == np.pi: - return "pi" - - return str(np.round(float(self.value))) - - def latex(self): - """Return the corresponding math mode latex string.""" - try: - from pylatexenc.latexencode import utf8tolatex - except ImportError as ex: - raise MissingOptionalLibraryError( - "pylatexenc", "latex-from-qasm exporter", "pip install pylatexenc" - ) from ex - return utf8tolatex(self.value) - - def sym(self, nested_scope=None): - """Return the correspond symbolic number.""" - del nested_scope # unused - return float(self.value) - - def real(self, nested_scope=None): - """Return the correspond floating point number.""" - del nested_scope # unused - return float(self.value.evalf()) diff --git a/qiskit/qasm/node/reset.py b/qiskit/qasm/node/reset.py deleted file mode 100644 index 29ccae931d96..000000000000 --- a/qiskit/qasm/node/reset.py +++ /dev/null @@ -1,29 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM reset statement.""" -from .node import Node - - -class Reset(Node): - """Node for an OPENQASM reset statement. - - children[0] is a primary node (id or indexedid) - """ - - def __init__(self, children): - """Create the reset node.""" - super().__init__("reset", children, None) - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "reset " + self.children[0].qasm() + ";" diff --git a/qiskit/qasm/node/unaryoperator.py b/qiskit/qasm/node/unaryoperator.py deleted file mode 100644 index a81e9f0737b1..000000000000 --- a/qiskit/qasm/node/unaryoperator.py +++ /dev/null @@ -1,49 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OpenQASM 2 unary operator.""" - -import operator - -from .node import Node -from .nodeexception import NodeException - - -VALID_OPERATORS = { - "+": operator.pos, - "-": operator.neg, -} - - -class UnaryOperator(Node): - """Node for an OpenQASM 2 unary operator. - - This node has no children. The data is in the value field. - """ - - def __init__(self, operation): - """Create the operator node.""" - super().__init__("unary_operator", None, None) - self.value = operation - - def operation(self): - """ - Return the operator as a function f(left, right). - """ - try: - return VALID_OPERATORS[self.value] - except KeyError as ex: - raise NodeException(f"internal error: undefined prefix '{self.value}'") from ex - - def qasm(self): - """Return OpenQASM 2 representation.""" - return self.value diff --git a/qiskit/qasm/node/universalunitary.py b/qiskit/qasm/node/universalunitary.py deleted file mode 100644 index e00303821273..000000000000 --- a/qiskit/qasm/node/universalunitary.py +++ /dev/null @@ -1,32 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Node for an OPENQASM U statement.""" -from .node import Node - - -class UniversalUnitary(Node): - """Node for an OPENQASM U statement. - - children[0] is an expressionlist node. - children[1] is a primary node (id or indexedid). - """ - - def __init__(self, children): - """Create the U node.""" - super().__init__("universal_unitary", children) - self.arguments = children[0] - self.bitlist = children[1] - - def qasm(self): - """Return the corresponding OPENQASM string.""" - return "U(" + self.children[0].qasm() + ") " + self.children[1].qasm() + ";" diff --git a/qiskit/qasm/pygments/__init__.py b/qiskit/qasm/pygments/__init__.py deleted file mode 100644 index 686bf5e7800d..000000000000 --- a/qiskit/qasm/pygments/__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. - -""" -================================================= -Qasm Pygments tools (:mod:`qiskit.qasm.pygments`) -================================================= - -.. currentmodule:: qiskit.qasm.pygments - -.. autosummary:: - :toctree: ../stubs/ - - OpenQASMLexer - QasmTerminalStyle - QasmHTMLStyle -""" - -# pylint: disable=wrong-import-position - -from qiskit.utils.optionals import HAS_PYGMENTS - -HAS_PYGMENTS.require_now("built-in OpenQASM 2 syntax highlighting") - -from .lexer import OpenQASMLexer, QasmTerminalStyle, QasmHTMLStyle diff --git a/qiskit/qasm/pygments/lexer.py b/qiskit/qasm/pygments/lexer.py deleted file mode 100644 index cba2163bb0d7..000000000000 --- a/qiskit/qasm/pygments/lexer.py +++ /dev/null @@ -1,133 +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. -"""Pygments tools for Qasm. -""" - -from pygments.lexer import RegexLexer -from pygments.token import Comment, String, Keyword, Name, Number, Text -from pygments.style import Style - - -class QasmTerminalStyle(Style): - """A style for OpenQasm in a Terminal env (e.g. Jupyter print).""" - - styles = { - String: "ansibrightred", - Number: "ansibrightcyan", - Keyword.Reserved: "ansibrightgreen", - Keyword.Declaration: "ansibrightgreen", - Keyword.Type: "ansibrightmagenta", - Name.Builtin: "ansibrightblue", - Name.Function: "ansibrightyellow", - } - - -class QasmHTMLStyle(Style): - """A style for OpenQasm in a HTML env (e.g. Jupyter widget).""" - - styles = { - String: "ansired", - Number: "ansicyan", - Keyword.Reserved: "ansigreen", - Keyword.Declaration: "ansigreen", - Keyword.Type: "ansimagenta", - Name.Builtin: "ansiblue", - Name.Function: "ansiyellow", - } - - -class OpenQASMLexer(RegexLexer): - """A pygments lexer for OpenQasm.""" - - name = "OpenQASM" - aliases = ["qasm"] - filenames = ["*.qasm"] - - gates = [ - "id", - "cx", - "x", - "y", - "z", - "s", - "sdg", - "h", - "t", - "tdg", - "ccx", - "c3x", - "c4x", - "c3sqrtx", - "rx", - "ry", - "rz", - "cz", - "cy", - "ch", - "swap", - "cswap", - "crx", - "cry", - "crz", - "cu1", - "cu3", - "rxx", - "rzz", - "rccx", - "rc3x", - "u1", - "u2", - "u3", - ] - - tokens = { - "root": [ - (r"\n", Text), - (r"[^\S\n]+", Text), - (r"//\n", Comment), - (r"//.*?$", Comment.Single), - # Keywords - (r"(OPENQASM|include)\b", Keyword.Reserved, "keywords"), - (r"(qreg|creg)\b", Keyword.Declaration), - # Treat 'if' special - (r"(if)\b", Keyword.Reserved, "if_keywords"), - # Constants - (r"(pi)\b", Name.Constant), - # Special - (r"(barrier|measure|reset)\b", Name.Builtin, "params"), - # Gates (Types) - ("(" + "|".join(gates) + r")\b", Keyword.Type, "params"), - (r"[unitary\d+]", Keyword.Type), - # Functions - (r"(gate)\b", Name.Function, "gate"), - # Generic text - (r"[a-zA-Z_][a-zA-Z0-9_]*", Text, "index"), - ], - "keywords": [ - (r'\s*("([^"]|"")*")', String, "#push"), - (r"\d+", Number, "#push"), - (r".*\(", Text, "params"), - ], - "if_keywords": [ - (r"[a-zA-Z0-9_]*", String, "#pop"), - (r"\d+", Number, "#push"), - (r".*\(", Text, "params"), - ], - "params": [ - (r"[a-zA-Z_][a-zA-Z0-9_]*", Text, "#push"), - (r"\d+", Number, "#push"), - (r"(\d+\.\d*|\d*\.\d+)([eEf][+-]?[0-9]+)?", Number, "#push"), - (r"\)", Text), - ], - "gate": [(r"[unitary\d+]", Keyword.Type, "#push"), (r"p\d+", Text, "#push")], - "index": [(r"\d+", Number, "#pop")], - } diff --git a/qiskit/qasm/qasm.py b/qiskit/qasm/qasm.py deleted file mode 100644 index ded52b32d3fc..000000000000 --- a/qiskit/qasm/qasm.py +++ /dev/null @@ -1,53 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -OPENQASM circuit object. -""" -from .exceptions import QasmError -from .qasmparser import QasmParser - - -class Qasm: - """OPENQASM circuit object.""" - - def __init__(self, filename=None, data=None): - """Create an OPENQASM circuit object.""" - if filename is None and data is None: - raise QasmError("Missing input file and/or data") - if filename is not None and data is not None: - raise QasmError("File and data must not both be specified initializing OpenQASM 2") - self._filename = filename - self._data = data - - def return_filename(self): - """Return the filename.""" - return self._filename - - def generate_tokens(self): - """Returns a generator of the tokens.""" - if self._filename: - with open(self._filename) as ifile: - self._data = ifile.read() - - with QasmParser(self._filename) as qasm_p: - return qasm_p.read_tokens() - - def parse(self): - """Parse the data.""" - if self._filename: - with open(self._filename) as ifile: - self._data = ifile.read() - - with QasmParser(self._filename) as qasm_p: - qasm_p.parse_debug(False) - return qasm_p.parse(self._data) diff --git a/qiskit/qasm/qasmlexer.py b/qiskit/qasm/qasmlexer.py deleted file mode 100644 index 7766d81c2eec..000000000000 --- a/qiskit/qasm/qasmlexer.py +++ /dev/null @@ -1,203 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -OPENQASM Lexer. - -This is a wrapper around the PLY lexer to support the "include" statement -by creating a stack of lexers. -""" - -import os - -import numpy as np -from ply import lex - -from . import node -from .exceptions import QasmError - -CORE_LIBS_PATH = os.path.join(os.path.dirname(__file__), "libs") -CORE_LIBS = os.listdir(CORE_LIBS_PATH) - - -class QasmLexer: - """OPENQASM Lexer. - - This is a wrapper around the PLY lexer to support the "include" statement - by creating a stack of lexers. - """ - - # pylint: disable=invalid-name,missing-function-docstring - # pylint: disable=attribute-defined-outside-init,bad-docstring-quotes - - def __mklexer__(self, filename): - """Create a PLY lexer.""" - self.lexer = lex.lex(module=self, debug=False) - self.filename = filename - self.lineno = 1 - - if filename: - with open(filename) as ifile: - self.data = ifile.read() - self.lexer.input(self.data) - - def __init__(self, filename): - """Create the OPENQASM lexer.""" - self.__mklexer__(filename) - self.stack = [] - - def input(self, data): - """Set the input text data.""" - self.data = data - self.lexer.input(data) - - def token(self): - """Return the next token.""" - ret = self.lexer.token() - return ret - - def pop(self): - """Pop a PLY lexer off the stack.""" - self.lexer = self.stack.pop() - self.filename = self.lexer.qasm_file - self.lineno = self.lexer.qasm_line - - def push(self, filename): - """Push a PLY lexer on the stack to parse filename.""" - self.lexer.qasm_file = self.filename - self.lexer.qasm_line = self.lineno - self.stack.append(self.lexer) - self.__mklexer__(filename) - - # ---- Beginning of the PLY lexer ---- - literals = r'=()[]{};<>,.+-/*^"' - reserved = { - "barrier": "BARRIER", - "creg": "CREG", - "gate": "GATE", - "if": "IF", - "measure": "MEASURE", - "opaque": "OPAQUE", - "qreg": "QREG", - "pi": "PI", - "reset": "RESET", - } - tokens = [ - "NNINTEGER", - "REAL", - "CX", - "U", - "FORMAT", - "ASSIGN", - "MATCHES", - "ID", - "STRING", - ] + list(reserved.values()) - - def t_REAL(self, t): - r"(([0-9]+|([0-9]+)?\.[0-9]+|[0-9]+\.)[eE][+-]?[0-9]+)|(([0-9]+)?\.[0-9]+|[0-9]+\.)" - if np.iscomplex(t): - return t.real - else: - return t - - def t_NNINTEGER(self, t): - r"[1-9]+[0-9]*|0" - t.value = int(t.value) - return t - - def t_ASSIGN(self, t): - "->" - return t - - def t_MATCHES(self, t): - "==" - return t - - def t_STRING(self, t): - r"\"([^\\\"]|\\.)*\"" # fmt: skip - return t - - def t_INCLUDE(self, _): - "include" - # Now eat up the next two tokens which must be - # 1 - the name of the include file, and - # 2 - a terminating semicolon - # - # Then push the current lexer onto the stack, create a new one from - # the include file, and push it onto the stack. - # - # When we hit eof (the t_eof) rule, we pop. - next_token = self.lexer.token() - lineno = next_token.lineno - if isinstance(next_token.value, str): - incfile = next_token.value.strip('"') - else: - raise QasmError("Invalid include: must be a quoted string.") - - if incfile in CORE_LIBS: - incfile = os.path.join(CORE_LIBS_PATH, incfile) - - next_token = self.lexer.token() - if next_token is None or next_token.value != ";": - raise QasmError('Invalid syntax, missing ";" at line', str(lineno)) - - if not os.path.exists(incfile): - raise QasmError( - "Include file %s cannot be found, line %s, file %s" - % (incfile, str(next_token.lineno), self.filename) - ) - self.push(incfile) - return self.lexer.token() - - def t_FORMAT(self, t): - r"OPENQASM\s+[0-9]+(\.[0-9]+)?" - return t - - def t_COMMENT(self, _): - r"//.*" - pass - - def t_CX(self, t): - "CX" - return t - - def t_U(self, t): - "U" - return t - - def t_ID(self, t): - r"[a-z][a-zA-Z0-9_]*" - - t.type = self.reserved.get(t.value, "ID") - if t.type == "ID": - t.value = node.Id(t.value, self.lineno, self.filename) - return t - - def t_newline(self, t): - r"\n+" - self.lineno += len(t.value) - t.lexer.lineno = self.lineno - - def t_eof(self, _): - if self.stack: - self.pop() - return self.lexer.token() - return None - - t_ignore = " \t\r" - - def t_error(self, t): - raise QasmError( - "Unable to match any token rule, got -->%s<-- " - "Check your OPENQASM source and any include statements." % t.value[0] - ) diff --git a/qiskit/qasm/qasmparser.py b/qiskit/qasm/qasmparser.py deleted file mode 100644 index f5c2dc2c1fa0..000000000000 --- a/qiskit/qasm/qasmparser.py +++ /dev/null @@ -1,1156 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""OpenQASM parser.""" - -import os -import shutil -import tempfile - -import numpy as np -from ply import yacc - -from . import node -from .exceptions import QasmError -from .qasmlexer import QasmLexer - - -class QasmParser: - """OPENQASM Parser.""" - - # pylint: disable=missing-function-docstring,invalid-name - - def __init__(self, filename): - """Create the parser.""" - if filename is None: - filename = "" - self.lexer = QasmLexer(filename) - self.tokens = self.lexer.tokens - self.parse_dir = tempfile.mkdtemp(prefix="qiskit") - self.precedence = ( - ("left", "+", "-"), - ("left", "*", "/"), - ("left", "negative", "positive"), - ("right", "^"), - ) - # For yacc, also, write_tables = Bool and optimize = Bool - self.parser = yacc.yacc(module=self, debug=False, outputdir=self.parse_dir) - self.qasm = None - self.parse_deb = False - self.global_symtab = {} # global symtab - self.current_symtab = self.global_symtab # top of symbol stack - self.symbols = [] # symbol stack - self.external_functions = ["sin", "cos", "tan", "exp", "ln", "sqrt", "acos", "atan", "asin"] - - def __enter__(self): - return self - - def __exit__(self, *args): - if os.path.exists(self.parse_dir): - shutil.rmtree(self.parse_dir) - - def update_symtab(self, obj): - """Update a node in the symbol table. - - Everything in the symtab must be a node with these attributes: - name - the string name of the object - type - the string type of the object - line - the source line where the type was first found - file - the source file where the type was first found - """ - if obj.name in self.current_symtab: - prev = self.current_symtab[obj.name] - raise QasmError( - "Duplicate declaration for", - obj.type + " '" + obj.name + "' at line", - str(obj.line) + ", file", - obj.file + ".\nPrevious occurrence at line", - str(prev.line) + ", file", - prev.file, - ) - self.current_symtab[obj.name] = obj - - def verify_declared_bit(self, obj): - """Verify a qubit id against the gate prototype.""" - # We are verifying gate args against the formal parameters of a - # gate prototype. - if obj.name not in self.current_symtab: - raise QasmError( - "Cannot find symbol '" + obj.name + "' in argument list for gate, line", - str(obj.line), - "file", - obj.file, - ) - - # This insures the thing is from the bitlist and not from the - # argument list. - sym = self.current_symtab[obj.name] - if not (sym.type == "id" and sym.is_bit): - raise QasmError("Bit", obj.name, "is not declared as a bit in the gate.") - - def verify_bit_list(self, obj): - """Verify each qubit in a list of ids.""" - # We expect the object to be a bitlist or an idlist, we don't care. - # We will iterate it and ensure everything in it is declared as a bit, - # and throw if not. - for children in obj.children: - self.verify_declared_bit(children) - - def verify_exp_list(self, obj): - """Verify each expression in a list.""" - # A tad harder. This is a list of expressions each of which could be - # the head of a tree. We need to recursively walk each of these and - # ensure that any Id elements resolve to the current stack. - # - # I believe we only have to look at the current symtab. - if obj.children is not None: - for children in obj.children: - if isinstance(children, node.Id): - if children.name in self.external_functions: - continue - - if children.name not in self.current_symtab: - raise QasmError( - "Argument '" - + children.name - + "' in expression cannot be " - + "found, line", - str(children.line), - "file", - children.file, - ) - else: - if hasattr(children, "children"): - self.verify_exp_list(children) - - def verify_as_gate(self, obj, bitlist, arglist=None): - """Verify a user defined gate call.""" - if obj.name not in self.global_symtab: - raise QasmError( - "Cannot find gate definition for '" + obj.name + "', line", - str(obj.line), - "file", - obj.file, - ) - g_sym = self.global_symtab[obj.name] - if g_sym.type not in ("gate", "opaque"): - raise QasmError( - "'" - + obj.name - + "' is used as a gate " - + "or opaque call but the symbol is neither;" - + " it is a '" - + g_sym.type - + "' line", - str(obj.line), - "file", - obj.file, - ) - - if g_sym.n_bits() != bitlist.size(): - raise QasmError( - "Gate or opaque call to '" + obj.name + "' uses", - str(bitlist.size()), - "qubits but is declared for", - str(g_sym.n_bits()), - "qubits", - "line", - str(obj.line), - "file", - obj.file, - ) - - if arglist: - if g_sym.n_args() != arglist.size(): - raise QasmError( - "Gate or opaque call to '" + obj.name + "' uses", - str(arglist.size()), - "qubits but is declared for", - str(g_sym.n_args()), - "qubits", - "line", - str(obj.line), - "file", - obj.file, - ) - else: - if g_sym.n_args() > 0: - raise QasmError( - "Gate or opaque call to '" - + obj.name - + "' has no arguments but is declared for", - str(g_sym.n_args()), - "qubits", - "line", - str(obj.line), - "file", - obj.file, - ) - - def verify_reg(self, obj, object_type): - """Verify a register.""" - # How to verify: - # types must match - # indexes must be checked - if obj.name not in self.global_symtab: - raise QasmError( - "Cannot find definition for", - object_type, - "'" + obj.name + "'", - "at line", - str(obj.line), - "file", - obj.file, - ) - - g_sym = self.global_symtab[obj.name] - - if g_sym.type != object_type: - raise QasmError( - "Type for '" - + g_sym.name - + "' should be '" - + object_type - + "' but was found to be '" - + g_sym.type - + "'", - "line", - str(obj.line), - "file", - obj.file, - ) - - if obj.type == "indexed_id": - bound = g_sym.index - ndx = obj.index - if ndx < 0 or ndx >= bound: - raise QasmError( - "Register index for '" + g_sym.name + "' out of bounds. Index is", - str(ndx), - "bound is 0 <= index <", - str(bound), - "at line", - str(obj.line), - "file", - obj.file, - ) - - def verify_reg_list(self, obj, object_type): - """Verify a list of registers.""" - # We expect the object to be a bitlist or an idlist, we don't care. - # We will iterate it and ensure everything in it is declared as a bit, - # and throw if not. - for children in obj.children: - self.verify_reg(children, object_type) - - def id_tuple_list(self, id_node): - """Return a list of (name, index) tuples for this id node.""" - if id_node.type != "id": - raise QasmError("internal error, id_tuple_list") - bit_list = [] - try: - g_sym = self.current_symtab[id_node.name] - except KeyError: - g_sym = self.global_symtab[id_node.name] - if g_sym.type in ("qreg", "creg"): - # Return list of (name, idx) for reg ids - for idx in range(g_sym.index): - bit_list.append((id_node.name, idx)) - else: - # Return (name, -1) for other ids - bit_list.append((id_node.name, -1)) - return bit_list - - def verify_distinct(self, list_of_nodes): - """Check that objects in list_of_nodes represent distinct (qu)bits. - - list_of_nodes is a list containing nodes of type id, indexed_id, - primary_list, or id_list. We assume these are all the same type - 'qreg' or 'creg'. - This method raises an exception if list_of_nodes refers to the - same object more than once. - """ - bit_list = [] - line_number = -1 - filename = "" - for node_ in list_of_nodes: - # id node: add all bits in register or (name, -1) for id - if node_.type == "id": - bit_list.extend(self.id_tuple_list(node_)) - line_number = node_.line - filename = node_.file - # indexed_id: add the bit - elif node_.type == "indexed_id": - bit_list.append((node_.name, node_.index)) - line_number = node_.line - filename = node_.file - # primary_list: for each id or indexed_id child, add - elif node_.type == "primary_list": - for child in node_.children: - if child.type == "id": - bit_list.extend(self.id_tuple_list(child)) - else: - bit_list.append((child.name, child.index)) - line_number = child.line - filename = child.file - # id_list: for each id, add - elif node_.type == "id_list": - for child in node_.children: - bit_list.extend(self.id_tuple_list(child)) - line_number = child.line - filename = child.file - else: - raise QasmError("internal error, verify_distinct") - if len(bit_list) != len(set(bit_list)): - raise QasmError("duplicate identifiers at line %d file %s" % (line_number, filename)) - - def pop_scope(self): - """Return to the previous scope.""" - self.current_symtab = self.symbols.pop() - - def push_scope(self): - """Enter a new scope.""" - self.symbols.append(self.current_symtab) - self.current_symtab = {} - - # ---- Begin the PLY parser ---- - start = "main" - - def p_main(self, program): - """ - main : program - """ - self.qasm = program[1] - - # ---------------------------------------- - # program : statement - # | program statement - # ---------------------------------------- - def p_program_0(self, program): - """ - program : statement - """ - program[0] = node.Program([program[1]]) - - def p_program_1(self, program): - """ - program : program statement - """ - program[0] = program[1] - program[0].add_child(program[2]) - - # ---------------------------------------- - # statement : decl - # | quantum_op ';' - # | format ';' - # ---------------------------------------- - def p_statement(self, program): - """ - statement : decl - | quantum_op ';' - | format ';' - | ignore - | quantum_op error - | format error - """ - if len(program) > 2: - if program[2] != ";": - raise QasmError( - "Missing ';' at end of statement; " + "received", str(program[2].value) - ) - program[0] = program[1] - - def p_format(self, program): - """ - format : FORMAT - """ - version = node.Format(program[1]) - if (version.majorversion != "2") or (version.minorversion != "0"): - provided_version = f"{version.majorversion}.{version.minorversion}" - raise QasmError( - f"Invalid version: '{provided_version}'. This module supports OpenQASM 2.0 only." - ) - program[0] = version - - # ---------------------------------------- - # id : ID - # ---------------------------------------- - def p_id(self, program): - """ - id : ID - """ - program[0] = program[1] - - def p_id_e(self, program): - """ - id : error - """ - raise QasmError("Expected an ID, received '" + str(program[1].value) + "'") - - # ---------------------------------------- - # indexed_id : ID [ int ] - # ---------------------------------------- - def p_indexed_id(self, program): - """ - indexed_id : id '[' NNINTEGER ']' - | id '[' NNINTEGER error - | id '[' error - """ - if len(program) == 4: - raise QasmError("Expecting an integer index; received", str(program[3].value)) - if program[4] != "]": - raise QasmError("Missing ']' in indexed ID; received", str(program[4].value)) - program[0] = node.IndexedId([program[1], node.Int(program[3])]) - - # ---------------------------------------- - # primary : id - # | indexed_id - # ---------------------------------------- - def p_primary(self, program): - """ - primary : id - | indexed_id - """ - program[0] = program[1] - - # ---------------------------------------- - # id_list : id - # | id_list ',' id - # ---------------------------------------- - def p_id_list_0(self, program): - """ - id_list : id - """ - program[0] = node.IdList([program[1]]) - - def p_id_list_1(self, program): - """ - id_list : id_list ',' id - """ - program[0] = program[1] - program[0].add_child(program[3]) - - # ---------------------------------------- - # gate_id_list : id - # | gate_id_list ',' id - # ---------------------------------------- - def p_gate_id_list_0(self, program): - """ - gate_id_list : id - """ - program[0] = node.IdList([program[1]]) - self.update_symtab(program[1]) - - def p_gate_id_list_1(self, program): - """ - gate_id_list : gate_id_list ',' id - """ - program[0] = program[1] - program[0].add_child(program[3]) - self.update_symtab(program[3]) - - # ---------------------------------------- - # bit_list : bit - # | bit_list ',' bit - # ---------------------------------------- - def p_bit_list_0(self, program): - """ - bit_list : id - """ - program[0] = node.IdList([program[1]]) - program[1].is_bit = True - self.update_symtab(program[1]) - - def p_bit_list_1(self, program): - """ - bit_list : bit_list ',' id - """ - program[0] = program[1] - program[0].add_child(program[3]) - program[3].is_bit = True - self.update_symtab(program[3]) - - # ---------------------------------------- - # primary_list : primary - # | primary_list ',' primary - # ---------------------------------------- - def p_primary_list_0(self, program): - """ - primary_list : primary - """ - program[0] = node.PrimaryList([program[1]]) - - def p_primary_list_1(self, program): - """ - primary_list : primary_list ',' primary - """ - program[0] = program[1] - program[1].add_child(program[3]) - - # ---------------------------------------- - # decl : qreg_decl - # | creg_decl - # | gate_decl - # ---------------------------------------- - def p_decl(self, program): - """ - decl : qreg_decl ';' - | creg_decl ';' - | qreg_decl error - | creg_decl error - | gate_decl - """ - if len(program) > 2: - if program[2] != ";": - raise QasmError( - "Missing ';' in qreg or creg declaration." - " Instead received '" + program[2].value + "'" - ) - program[0] = program[1] - - # ---------------------------------------- - # qreg_decl : QREG indexed_id - # ---------------------------------------- - def p_qreg_decl(self, program): - """ - qreg_decl : QREG indexed_id - """ - program[0] = node.Qreg([program[2]]) - if program[2].name in self.external_functions: - raise QasmError( - "QREG names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - if program[2].index == 0: - raise QasmError("QREG size must be positive") - self.update_symtab(program[0]) - - def p_qreg_decl_e(self, program): - """ - qreg_decl : QREG error - """ - raise QasmError( - "Expecting indexed id (ID[int]) in QREG" + " declaration; received", program[2].value - ) - - # ---------------------------------------- - # creg_decl : QREG indexed_id - # ---------------------------------------- - def p_creg_decl(self, program): - """ - creg_decl : CREG indexed_id - """ - program[0] = node.Creg([program[2]]) - if program[2].name in self.external_functions: - raise QasmError( - "CREG names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - if program[2].index == 0: - raise QasmError("CREG size must be positive") - self.update_symtab(program[0]) - - def p_creg_decl_e(self, program): - """ - creg_decl : CREG error - """ - raise QasmError( - "Expecting indexed id (ID[int]) in CREG" + " declaration; received", program[2].value - ) - - # Gate_body will throw if there are errors, so we don't need to cover - # that here. Same with the id_lists - if they are not legal, we die - # before we get here - # - # ---------------------------------------- - # gate_decl : GATE id gate_scope bit_list gate_body - # | GATE id gate_scope '(' ')' bit_list gate_body - # | GATE id gate_scope '(' gate_id_list ')' bit_list gate_body - # - # ---------------------------------------- - def p_gate_decl_0(self, program): - """ - gate_decl : GATE id gate_scope bit_list gate_body - """ - program[0] = node.Gate([program[2], program[4], program[5]]) - if program[2].name in self.external_functions: - raise QasmError( - "GATE names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - self.pop_scope() - self.update_symtab(program[0]) - - def p_gate_decl_1(self, program): - """ - gate_decl : GATE id gate_scope '(' ')' bit_list gate_body - """ - program[0] = node.Gate([program[2], program[6], program[7]]) - if program[2].name in self.external_functions: - raise QasmError( - "GATE names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - self.pop_scope() - self.update_symtab(program[0]) - - def p_gate_decl_2(self, program): - """ - gate_decl : GATE id gate_scope '(' gate_id_list ')' bit_list gate_body - """ - program[0] = node.Gate([program[2], program[5], program[7], program[8]]) - if program[2].name in self.external_functions: - raise QasmError( - "GATE names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - self.pop_scope() - self.update_symtab(program[0]) - - def p_gate_scope(self, _): - """ - gate_scope : - """ - self.push_scope() - - # ---------------------------------------- - # gate_body : '{' gate_op_list '}' - # | '{' '}' - # - # | '{' gate_op_list error - # | '{' error - # - # Error handling: gete_op will throw if there's a problem so we won't - # get here with in the gate_op_list - # ---------------------------------------- - def p_gate_body_0(self, program): - """ - gate_body : '{' '}' - """ - if program[2] != "}": - raise QasmError( - "Missing '}' in gate definition; received'" + str(program[2].value) + "'" - ) - program[0] = node.GateBody(None) - - def p_gate_body_1(self, program): - """ - gate_body : '{' gate_op_list '}' - """ - program[0] = node.GateBody(program[2]) - - # ---------------------------------------- - # gate_op_list : gate_op - # | gate_op_ist gate_op - # - # Error handling: gete_op will throw if there's a problem so we won't - # get here with errors - # ---------------------------------------- - def p_gate_op_list_0(self, program): - """ - gate_op_list : gate_op - """ - program[0] = [program[1]] - - def p_gate_op_list_1(self, program): - """ - gate_op_list : gate_op_list gate_op - """ - program[0] = program[1] - program[0].append(program[2]) - - # ---------------------------------------- - # These are for use outside of gate_bodies and allow - # indexed ids everywhere. - # - # unitary_op : U '(' exp_list ')' primary - # | CX primary ',' primary - # | id primary_list - # | id '(' ')' primary_list - # | id '(' exp_list ')' primary_list - # - # Note that it might not be unitary - this is the mechanism that - # is also used to invoke calls to 'opaque' - # ---------------------------------------- - def p_unitary_op_0(self, program): - """ - unitary_op : U '(' exp_list ')' primary - """ - program[0] = node.UniversalUnitary([program[3], program[5]]) - self.verify_reg(program[5], "qreg") - self.verify_exp_list(program[3]) - - def p_unitary_op_1(self, program): - """ - unitary_op : CX primary ',' primary - """ - program[0] = node.Cnot([program[2], program[4]]) - self.verify_reg(program[2], "qreg") - self.verify_reg(program[4], "qreg") - self.verify_distinct([program[2], program[4]]) - # TODO: check that if both primary are id, same size - # TODO: this needs to be checked in other cases too - - def p_unitary_op_2(self, program): - """ - unitary_op : id primary_list - """ - program[0] = node.CustomUnitary([program[1], program[2]]) - self.verify_as_gate(program[1], program[2]) - self.verify_reg_list(program[2], "qreg") - self.verify_distinct([program[2]]) - - def p_unitary_op_3(self, program): - """ - unitary_op : id '(' ')' primary_list - """ - program[0] = node.CustomUnitary([program[1], program[4]]) - self.verify_as_gate(program[1], program[4]) - self.verify_reg_list(program[4], "qreg") - self.verify_distinct([program[4]]) - - def p_unitary_op_4(self, program): - """ - unitary_op : id '(' exp_list ')' primary_list - """ - program[0] = node.CustomUnitary([program[1], program[3], program[5]]) - self.verify_as_gate(program[1], program[5], arglist=program[3]) - self.verify_reg_list(program[5], "qreg") - self.verify_exp_list(program[3]) - self.verify_distinct([program[5]]) - - # ---------------------------------------- - # This is a restricted set of "quantum_op" which also - # prohibits indexed ids, for use in a gate_body - # - # gate_op : U '(' exp_list ')' id ';' - # | CX id ',' id ';' - # | id id_list ';' - # | id '(' ')' id_list ';' - # | id '(' exp_list ')' id_list ';' - # | BARRIER id_list ';' - # ---------------------------------------- - def p_gate_op_0(self, program): - """ - gate_op : U '(' exp_list ')' id ';' - """ - program[0] = node.UniversalUnitary([program[3], program[5]]) - self.verify_declared_bit(program[5]) - self.verify_exp_list(program[3]) - - def p_gate_op_0e1(self, p): - """ - gate_op : U '(' exp_list ')' error - """ - raise QasmError("Invalid U inside gate definition. " + "Missing bit id or ';'") - - def p_gate_op_0e2(self, _): - """ - gate_op : U '(' exp_list error - """ - raise QasmError("Missing ')' in U invocation in gate definition.") - - def p_gate_op_1(self, program): - """ - gate_op : CX id ',' id ';' - """ - program[0] = node.Cnot([program[2], program[4]]) - self.verify_declared_bit(program[2]) - self.verify_declared_bit(program[4]) - self.verify_distinct([program[2], program[4]]) - - def p_gate_op_1e1(self, program): - """ - gate_op : CX error - """ - raise QasmError( - "Invalid CX inside gate definition. " - + "Expected an ID or ',', received '" - + str(program[2].value) - + "'" - ) - - def p_gate_op_1e2(self, program): - """ - gate_op : CX id ',' error - """ - raise QasmError( - "Invalid CX inside gate definition. " - + "Expected an ID or ';', received '" - + str(program[4].value) - + "'" - ) - - def p_gate_op_2(self, program): - """ - gate_op : id id_list ';' - """ - program[0] = node.CustomUnitary([program[1], program[2]]) - # To verify: - # 1. id is declared as a gate in global scope - # 2. everything in the id_list is declared as a bit in local scope - self.verify_as_gate(program[1], program[2]) - self.verify_bit_list(program[2]) - self.verify_distinct([program[2]]) - - def p_gate_op_2e(self, _): - """ - gate_op : id id_list error - """ - raise QasmError("Invalid gate invocation inside gate definition.") - - def p_gate_op_3(self, program): - """ - gate_op : id '(' ')' id_list ';' - """ - program[0] = node.CustomUnitary([program[1], program[4]]) - self.verify_as_gate(program[1], program[4]) - self.verify_bit_list(program[4]) - self.verify_distinct([program[4]]) - - def p_gate_op_4(self, program): - """ - gate_op : id '(' exp_list ')' id_list ';' - """ - program[0] = node.CustomUnitary([program[1], program[3], program[5]]) - self.verify_as_gate(program[1], program[5], arglist=program[3]) - self.verify_bit_list(program[5]) - self.verify_exp_list(program[3]) - self.verify_distinct([program[5]]) - - def p_gate_op_4e0(self, _): - """ - gate_op : id '(' ')' error - """ - raise QasmError("Invalid bit list inside gate definition or" + " missing ';'") - - def p_gate_op_4e1(self, _): - """ - gate_op : id '(' error - """ - raise QasmError("Unmatched () for gate invocation inside gate" + " invocation.") - - def p_gate_op_5(self, program): - """ - gate_op : BARRIER id_list ';' - """ - program[0] = node.Barrier([program[2]]) - self.verify_bit_list(program[2]) - self.verify_distinct([program[2]]) - - def p_gate_op_5e(self, _): - """ - gate_op : BARRIER error - """ - raise QasmError("Invalid barrier inside gate definition.") - - # ---------------------------------------- - # opaque : OPAQUE id gate_scope bit_list - # | OPAQUE id gate_scope '(' ')' bit_list - # | OPAQUE id gate_scope '(' gate_id_list ')' bit_list - # - # These are like gate declarations only without a body. - # ---------------------------------------- - def p_opaque_0(self, program): - """ - opaque : OPAQUE id gate_scope bit_list - """ - # TODO: Review Opaque function - program[0] = node.Opaque([program[2], program[4]]) - if program[2].name in self.external_functions: - raise QasmError( - "OPAQUE names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - self.pop_scope() - self.update_symtab(program[0]) - - def p_opaque_1(self, program): - """ - opaque : OPAQUE id gate_scope '(' ')' bit_list - """ - program[0] = node.Opaque([program[2], program[6]]) - self.pop_scope() - self.update_symtab(program[0]) - - def p_opaque_2(self, program): - """ - opaque : OPAQUE id gate_scope '(' gate_id_list ')' bit_list - """ - program[0] = node.Opaque([program[2], program[5], program[7]]) - if program[2].name in self.external_functions: - raise QasmError( - "OPAQUE names cannot be reserved words. " + "Received '" + program[2].name + "'" - ) - self.pop_scope() - self.update_symtab(program[0]) - - def p_opaque_1e(self, _): - """ - opaque : OPAQUE id gate_scope '(' error - """ - raise QasmError("Poorly formed OPAQUE statement.") - - # ---------------------------------------- - # measure : MEASURE primary ASSIGN primary - # ---------------------------------------- - def p_measure(self, program): - """ - measure : MEASURE primary ASSIGN primary - """ - program[0] = node.Measure([program[2], program[4]]) - self.verify_reg(program[2], "qreg") - self.verify_reg(program[4], "creg") - - def p_measure_e(self, program): - """ - measure : MEASURE primary error - """ - raise QasmError("Illegal measure statement." + str(program[3].value)) - - # ---------------------------------------- - # barrier : BARRIER primary_list - # - # Errors are covered by handling errors in primary_list - # ---------------------------------------- - def p_barrier(self, program): - """ - barrier : BARRIER primary_list - """ - program[0] = node.Barrier([program[2]]) - self.verify_reg_list(program[2], "qreg") - self.verify_distinct([program[2]]) - - # ---------------------------------------- - # reset : RESET primary - # ---------------------------------------- - def p_reset(self, program): - """ - reset : RESET primary - """ - program[0] = node.Reset([program[2]]) - self.verify_reg(program[2], "qreg") - - # ---------------------------------------- - # IF '(' ID MATCHES NNINTEGER ')' quantum_op - # ---------------------------------------- - def p_if(self, program): - """ - if : IF '(' id MATCHES NNINTEGER ')' quantum_op - if : IF '(' id error - if : IF '(' id MATCHES error - if : IF '(' id MATCHES NNINTEGER error - if : IF error - """ - if len(program) == 3: - raise QasmError("Ill-formed IF statement. Perhaps a" + " missing '('?") - if len(program) == 5: - raise QasmError( - "Ill-formed IF statement. Expected '==', " + "received '" + str(program[4].value) - ) - if len(program) == 6: - raise QasmError( - "Ill-formed IF statement. Expected a number, " - + "received '" - + str(program[5].value) - ) - if len(program) == 7: - raise QasmError("Ill-formed IF statement, unmatched '('") - - if program[7].type == "if": - raise QasmError("Nested IF statements not allowed") - if program[7].type == "barrier": - raise QasmError("barrier not permitted in IF statement") - - program[0] = node.If([program[3], node.Int(program[5]), program[7]]) - - # ---------------------------------------- - # These are all the things you can have outside of a gate declaration - # quantum_op : unitary_op - # | opaque - # | measure - # | reset - # | barrier - # | if - # - # ---------------------------------------- - def p_quantum_op(self, program): - """ - quantum_op : unitary_op - | opaque - | measure - | barrier - | reset - | if - """ - program[0] = program[1] - - # ---------------------------------------- - # unary : NNINTEGER - # | REAL - # | PI - # | ID - # | '(' expression ')' - # | id '(' expression ')' - # - # We will trust 'expression' to throw before we have to handle it here - # ---------------------------------------- - def p_unary_0(self, program): - """ - unary : NNINTEGER - """ - program[0] = node.Int(program[1]) - - def p_unary_1(self, program): - """ - unary : REAL - """ - program[0] = node.Real(program[1]) - - def p_unary_2(self, program): - """ - unary : PI - """ - program[0] = node.Real(np.pi) - - def p_unary_3(self, program): - """ - unary : id - """ - program[0] = program[1] - - def p_unary_4(self, program): - """ - unary : '(' expression ')' - """ - program[0] = program[2] - - def p_unary_6(self, program): - """ - unary : id '(' expression ')' - """ - # note this is a semantic check, not syntactic - if program[1].name not in self.external_functions: - raise QasmError("Illegal external function call: ", str(program[1].name)) - program[0] = node.External([program[1], program[3]]) - - # ---------------------------------------- - # Prefix - # ---------------------------------------- - - def p_expression_1(self, program): - """ - expression : '-' expression %prec negative - | '+' expression %prec positive - """ - program[0] = node.Prefix([node.UnaryOperator(program[1]), program[2]]) - - def p_expression_0(self, program): - """ - expression : expression '*' expression - | expression '/' expression - | expression '+' expression - | expression '-' expression - | expression '^' expression - """ - program[0] = node.BinaryOp([node.BinaryOperator(program[2]), program[1], program[3]]) - - def p_expression_2(self, program): - """ - expression : unary - """ - program[0] = program[1] - - # ---------------------------------------- - # exp_list : exp - # | exp_list ',' exp - # ---------------------------------------- - def p_exp_list_0(self, program): - """ - exp_list : expression - """ - program[0] = node.ExpressionList([program[1]]) - - def p_exp_list_1(self, program): - """ - exp_list : exp_list ',' expression - """ - program[0] = program[1] - program[0].add_child(program[3]) - - def p_ignore(self, _): - """ - ignore : STRING - """ - # this should never hit but it keeps the insuppressible warnings at bay - pass - - def p_error(self, program): - # EOF is a special case because the stupid error token isn't placed - # on the stack - if not program: - raise QasmError("Error at end of file. " + "Perhaps there is a missing ';'") - - col = self.find_column(self.lexer.data, program) - print("Error near line", str(self.lexer.lineno), "Column", col) - - def find_column(self, input_, token): - """Compute the column. - - Input is the input text string. - token is a token instance. - """ - if token is None: - return 0 - last_cr = input_.rfind("\n", 0, token.lexpos) - last_cr = max(last_cr, 0) - column = (token.lexpos - last_cr) + 1 - return column - - def read_tokens(self): - """finds and reads the tokens.""" - try: - while True: - token = self.lexer.token() - - if not token: - break - - yield token - except QasmError as e: - print("Exception tokenizing qasm file:", e.msg) - - def parse_debug(self, val): - """Set the parse_deb field.""" - if val is True: - self.parse_deb = True - elif val is False: - self.parse_deb = False - else: - raise QasmError("Illegal debug value '" + str(val) + "' must be True or False.") - - def parse(self, data): - """Parse some data.""" - self.parser.parse(data, lexer=self.lexer, debug=self.parse_deb) - if self.qasm is None: - raise QasmError("Uncaught exception in parser; " + "see previous messages for details.") - return self.qasm - - def print_tree(self): - """Print parsed OPENQASM.""" - if self.qasm is not None: - self.qasm.to_string(0) - else: - print("No parsed qasm to print") - - def run(self, data): - """Parser runner. - - To use this module stand-alone. - """ - ast = self.parser.parse(data, debug=True) - self.parser.parse(data, debug=True) - ast.to_string(0) diff --git a/qiskit/qasm2/__init__.py b/qiskit/qasm2/__init__.py index e14ab420f380..485c210c0632 100644 --- a/qiskit/qasm2/__init__.py +++ b/qiskit/qasm2/__init__.py @@ -397,19 +397,6 @@ def add_one(x): serialisation format, and expanded its behaviour as Qiskit expanded. The new parser under all its defaults implements the specification more strictly. -The complete legacy code-paths are - -.. code-block:: python - - from qiskit.converters import ast_to_dag, dag_to_circuit - from qiskit.qasm import Qasm - - def from_qasm_file(path: str): - dag_to_circuit(ast_to_dag(Qasm(filename=path).parse())) - - def from_qasm_str(qasm_str: str): - dag_to_circuit(ast_to_dag(Qasm(data=qasm_str).parse())) - In particular, in the legacy importers: * the `include_path` is effectively: diff --git a/qiskit/qasm3/__init__.py b/qiskit/qasm3/__init__.py index 36bb4dcc2843..8e3de893ea3c 100644 --- a/qiskit/qasm3/__init__.py +++ b/qiskit/qasm3/__init__.py @@ -83,7 +83,7 @@ All features enabled by the experimental flags are naturally transient. If it becomes necessary to remove flags, they will be subject to `the standard Qiskit deprecation policy - `__. We will leave these experimental + `__. We will leave these experimental flags in place for as long as is reasonable. However, we cannot guarantee any support windows for *consumers* of OpenQASM 3 code generated diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index d2fd4db57f40..e16894b3e35d 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -480,7 +480,7 @@ With the support of :class:`.~ScheduleBlock`, now :class:`~.QuantumCircuit` can be serialized together with :attr:`~.QuantumCircuit.calibrations`, or -`Pulse Gates `_. +`Pulse Gates `_. In QPY version 5 and above, :ref:`qpy_circuit_calibrations` payload is packed after the :ref:`qpy_instructions` block. @@ -921,7 +921,7 @@ SPARSE_PAULI_OP_LIST_ELEM ------------------------- -This represents an instance of :class:`.PauliSumOp`. +This represents an instance of :class:`.SparsePauliOp`. .. code-block:: c diff --git a/qiskit/qpy/binary_io/schedules.py b/qiskit/qpy/binary_io/schedules.py index 25ca9388bb4e..ce22702c89da 100644 --- a/qiskit/qpy/binary_io/schedules.py +++ b/qiskit/qpy/binary_io/schedules.py @@ -19,6 +19,10 @@ from io import BytesIO import numpy as np +import symengine as sym +from symengine.lib.symengine_wrapper import ( # pylint: disable = no-name-in-module + load_basic, +) from qiskit.exceptions import QiskitError from qiskit.pulse import library, channels, instructions @@ -26,14 +30,8 @@ from qiskit.qpy import formats, common, type_keys from qiskit.qpy.binary_io import value from qiskit.qpy.exceptions import QpyError -from qiskit.utils import optionals as _optional from qiskit.pulse.configuration import Kernel, Discriminator -if _optional.HAS_SYMENGINE: - import symengine as sym -else: - import sympy as sym - def _read_channel(file_obj, version): type_key = common.read_type_key(file_obj) @@ -106,23 +104,15 @@ def _read_discriminator(file_obj, version): def _loads_symbolic_expr(expr_bytes, use_symengine=False): if expr_bytes == b"": return None + expr_bytes = zlib.decompress(expr_bytes) if use_symengine: - _optional.HAS_SYMENGINE.require_now("load a symengine expression") - from symengine.lib.symengine_wrapper import ( # pylint: disable = no-name-in-module - load_basic, - ) - - expr = load_basic(zlib.decompress(expr_bytes)) + return load_basic(expr_bytes) else: from sympy import parse_expr - expr_txt = zlib.decompress(expr_bytes).decode(common.ENCODE) + expr_txt = expr_bytes.decode(common.ENCODE) expr = parse_expr(expr_txt) - if _optional.HAS_SYMENGINE: - from symengine import sympify - - return sympify(expr) - return expr + return sym.sympify(expr) def _read_symbolic_pulse(file_obj, version): @@ -404,7 +394,6 @@ def _dumps_symbolic_expr(expr, use_symengine): if expr is None: return b"" if use_symengine: - _optional.HAS_SYMENGINE.require_now("dump a symengine expression") expr_bytes = expr.__reduce__()[1][0] else: from sympy import srepr, sympify diff --git a/qiskit/qpy/binary_io/value.py b/qiskit/qpy/binary_io/value.py index 7bae82c6911f..686e72c9a4e6 100644 --- a/qiskit/qpy/binary_io/value.py +++ b/qiskit/qpy/binary_io/value.py @@ -19,6 +19,11 @@ import uuid import numpy as np +import symengine +from symengine.lib.symengine_wrapper import ( # pylint: disable = no-name-in-module + load_basic, +) + from qiskit.circuit import CASE_DEFAULT, Clbit, ClassicalRegister from qiskit.circuit.classical import expr, types @@ -26,7 +31,6 @@ from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.circuit.parametervector import ParameterVector, ParameterVectorElement from qiskit.qpy import common, formats, exceptions, type_keys -from qiskit.utils import optionals as _optional def _write_parameter(file_obj, obj): @@ -51,7 +55,6 @@ def _write_parameter_vec(file_obj, obj): def _write_parameter_expression(file_obj, obj, use_symengine): if use_symengine: - _optional.HAS_SYMENGINE.require_now("write_parameter_expression") expr_bytes = obj._symbol_expr.__reduce__()[1][0] else: from sympy import srepr, sympify @@ -224,13 +227,7 @@ def _read_parameter_expression(file_obj): ) from sympy.parsing.sympy_parser import parse_expr - if _optional.HAS_SYMENGINE: - from symengine import sympify - - expr_ = sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE))) - else: - expr_ = parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)) - + expr_ = symengine.sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE))) symbol_map = {} for _ in range(data.map_elements): elem_data = formats.PARAM_EXPR_MAP_ELEM( @@ -264,23 +261,14 @@ def _read_parameter_expression_v3(file_obj, vectors, use_symengine): data = formats.PARAMETER_EXPR( *struct.unpack(formats.PARAMETER_EXPR_PACK, file_obj.read(formats.PARAMETER_EXPR_SIZE)) ) - from sympy.parsing.sympy_parser import parse_expr payload = file_obj.read(data.expr_size) if use_symengine: - _optional.HAS_SYMENGINE.require_now("read_parameter_expression_v3") - from symengine.lib.symengine_wrapper import ( # pylint: disable = no-name-in-module - load_basic, - ) - expr_ = load_basic(payload) else: - if _optional.HAS_SYMENGINE: - from symengine import sympify + from sympy.parsing.sympy_parser import parse_expr - expr_ = sympify(parse_expr(payload.decode(common.ENCODE))) - else: - expr_ = parse_expr(payload.decode(common.ENCODE)) + expr_ = symengine.sympify(parse_expr(payload.decode(common.ENCODE))) symbol_map = {} for _ in range(data.map_elements): diff --git a/qiskit/qpy/interface.py b/qiskit/qpy/interface.py index f4a77ec14598..6de673afbc0f 100644 --- a/qiskit/qpy/interface.py +++ b/qiskit/qpy/interface.py @@ -75,7 +75,7 @@ def dump( programs: Union[List[QPY_SUPPORTED_TYPES], QPY_SUPPORTED_TYPES], file_obj: BinaryIO, metadata_serializer: Optional[Type[JSONEncoder]] = None, - use_symengine: bool = False, + use_symengine: bool = True, ): """Write QPY binary data to a file diff --git a/qiskit/result/sampled_expval.py b/qiskit/result/sampled_expval.py index 4968fc4b7436..b38840531018 100644 --- a/qiskit/result/sampled_expval.py +++ b/qiskit/result/sampled_expval.py @@ -40,7 +40,6 @@ def sampled_expectation_value(dist, oper): """ from .counts import Counts from qiskit.quantum_info import Pauli, SparsePauliOp - from qiskit.opflow import PauliOp, PauliSumOp # This should be removed when these return bit-string keys if isinstance(dist, (QuasiDistribution, ProbDistribution)): @@ -54,13 +53,6 @@ def sampled_expectation_value(dist, oper): elif isinstance(oper, Pauli): oper_strs = [oper.to_label()] coeffs = np.asarray([1.0]) - elif isinstance(oper, PauliOp): - oper_strs = [oper.primitive.to_label()] - coeffs = np.asarray([1.0]) - elif isinstance(oper, PauliSumOp): - spo = oper.primitive - oper_strs = spo.paulis.to_labels() - coeffs = np.asarray(spo.coeffs) * oper.coeff elif isinstance(oper, SparsePauliOp): oper_strs = oper.paulis.to_labels() coeffs = np.asarray(oper.coeffs) diff --git a/qiskit/test/base.py b/qiskit/test/base.py index 839e15b0191f..1dde885bd67f 100644 --- a/qiskit/test/base.py +++ b/qiskit/test/base.py @@ -202,9 +202,6 @@ def setUpClass(cls): warnings.filterwarnings("error", category=DeprecationWarning) allow_DeprecationWarning_modules = [ - "test.python.pulse.test_parameters", - "test.python.pulse.test_transforms", - "test.python.circuit.test_gate_power", "test.python.pulse.test_builder", "test.python.pulse.test_block", "test.python.quantum_info.operators.symplectic.test_legacy_pauli", @@ -218,8 +215,6 @@ def setUpClass(cls): "qiskit.pulse.instructions.play", "qiskit.pulse.library.parametric_pulses", "qiskit.quantum_info.operators.symplectic.pauli", - "test.python.dagcircuit.test_dagcircuit", - "importlib_metadata", ] for mod in allow_DeprecationWarning_modules: warnings.filterwarnings("default", category=DeprecationWarning, module=mod) @@ -228,14 +223,8 @@ def setUpClass(cls): r"The jsonschema validation included in qiskit-terra.*", r"The DerivativeBase.parameter_expression_grad method.*", r"The property ``qiskit\.circuit\.bit\.Bit\.(register|index)`` is deprecated.*", - r"The CXDirection pass has been deprecated", # Caused by internal scikit-learn scipy usage r"The 'sym_pos' keyword is deprecated and should be replaced by using", - # jupyter_client 7.4.8 uses deprecated shims in pyzmq that raise warnings with pyzmq 25. - # These are due to be fixed by jupyter_client 8, see: - # - https://github.com/jupyter/jupyter_client/issues/913 - # - https://github.com/jupyter/jupyter_client/pull/842 - r"zmq\.eventloop\.ioloop is deprecated in pyzmq .*", ] for msg in allow_DeprecationWarning_message: warnings.filterwarnings("default", category=DeprecationWarning, message=msg) diff --git a/qiskit/tools/jupyter/library.py b/qiskit/tools/jupyter/library.py index 773416ad3a04..57a27ece8cdc 100644 --- a/qiskit/tools/jupyter/library.py +++ b/qiskit/tools/jupyter/library.py @@ -131,67 +131,6 @@ def properties_widget(circuit: QuantumCircuit) -> wid.VBox: return properties -@_optionals.HAS_PYGMENTS.require_in_call -@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. - - Args: - circuit: Input quantum circuit. - - Returns: - Output widget. - """ - import pygments - from pygments.formatters import HtmlFormatter - from qiskit.qasm.pygments import QasmHTMLStyle, OpenQASMLexer - - qasm_code = circuit.qasm() - code = pygments.highlight(qasm_code, OpenQASMLexer(), HtmlFormatter()) - - html_style = HtmlFormatter(style=QasmHTMLStyle).get_style_defs(".highlight") - - code_style = ( - """ - - """ - % html_style - ) - - out = wid.HTML( - code_style + code, - layout=wid.Layout(max_height="500px", height="auto", overflow="scroll scroll"), - ) - - out_label = wid.HTML( - f"

OpenQASM

", - layout=wid.Layout(margin="0px 0px 10px 0px"), - ) - - qasm = wid.VBox( - children=[out_label, out], - layout=wid.Layout( - height="auto", max_height="500px", width="60%", margin="0px 0px 0px 20px" - ), - ) - - qasm._code_length = len(qasm_code.split("\n")) - return qasm - - @deprecate_func( since="0.25.0", additional_msg="This is unused by Qiskit, and no replacement will be publicly provided.", @@ -230,8 +169,7 @@ def circuit_library_widget(circuit: QuantumCircuit) -> None: Args: circuit: Input quantum circuit. """ - qasm_wid = qasm_widget(circuit) - sep_length = str(min(20 * qasm_wid._code_length, 495)) + sep_length = str(min(20, 495)) # The separator widget sep = wid.HTML( @@ -239,7 +177,7 @@ def circuit_library_widget(circuit: QuantumCircuit) -> None: layout=wid.Layout(height="auto", max_height="495px", margin="40px 0px 0px 20px"), ) bottom = wid.HBox( - children=[properties_widget(circuit), sep, qasm_widget(circuit)], + children=[properties_widget(circuit), sep], layout=wid.Layout(max_height="550px", height="auto"), ) diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 13ae12e09097..d6ce7b31ca4c 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -105,28 +105,35 @@ .. code-block:: python - from qiskit.circuit.library import XGate, HGate, RXGate, PhaseGate, TGate, TdgGate + import numpy as np + from qiskit.circuit.library import HGate, PhaseGate, RXGate, TdgGate, TGate, XGate from qiskit.transpiler import PassManager - from qiskit.transpiler.passes import ALAPScheduleAnalysis, PadDynamicalDecoupling - from qiskit.transpiler.passes import CXCancellation, InverseCancellation + from qiskit.transpiler.passes import ( + ALAPScheduleAnalysis, + CXCancellation, + InverseCancellation, + PadDynamicalDecoupling, + ) - backend_durations = backend.target.durations() dd_sequence = [XGate(), XGate()] - scheduling_pm = PassManager([ - ALAPScheduleAnalysis(backend_durations), - PadDynamicalDecoupling(backend_durations, dd_sequence), - ]) + scheduling_pm = PassManager( + [ + ALAPScheduleAnalysis(target=backend.target), + PadDynamicalDecoupling(target=backend.target, dd_sequence=dd_sequence), + ] + ) inverse_gate_list = [ HGate(), (RXGate(np.pi / 4), RXGate(-np.pi / 4)), (PhaseGate(np.pi / 4), PhaseGate(-np.pi / 4)), (TGate(), TdgGate()), - - ]) - logical_opt = PassManager([ - CXCancellation(), - InverseCancellation([HGate(), (RXGate(np.pi / 4), RXGate(-np.pi / 4)) - ]) + ] + logical_opt = PassManager( + [ + CXCancellation(), + InverseCancellation(inverse_gate_list), + ] + ) # Add pre-layout stage to run extra logical optimization @@ -134,26 +141,26 @@ # Set scheduling stage to custom pass manager pass_manager.scheduling = scheduling_pm - -Then when :meth:`~.StagedPassManager.run` is called on ``pass_manager`` the -``logical_opt`` :class:`~.PassManager` will be called prior to the ``layout`` stage -and for the ``scheduling`` stage our custom :class:`~.PassManager` -``scheduling_pm`` will be used. +Now, when the staged pass manager is run via the :meth:`~.StagedPassManager.run` method, +the ``logical_opt`` pass manager will be called before the ``layout`` stage, and the +``scheduling_pm`` pass manager will be used for the ``scheduling`` stage instead of the default. Custom Pass Managers ==================== In addition to modifying preset pass managers, it is also possible to construct a pass manager to build an entirely custom pipeline for transforming input -circuits. You can leverage the :class:`~.StagedPassManager` class directly to do +circuits. You can use the :class:`~.StagedPassManager` class directly to do this. You can define arbitrary stage names and populate them with a :class:`~.PassManager` -instance. For example:: +instance. For example, the following code creates a new :class:`~.StagedPassManager` +that has 2 stages, ``init`` and ``translation``.:: from qiskit.transpiler.passes import ( UnitarySynthesis, Collect2qBlocks, ConsolidateBlocks, UnitarySynthesis, + Unroll3qOrMore, ) from qiskit.transpiler import PassManager, StagedPassManager @@ -171,13 +178,11 @@ stages=["init", "translation"], init=init, translation=translate ) -will create a new :class:`~.StagedPassManager` that has 2 stages ``init`` and ``translation``. -There is no limit on the number of stages you can put in a custom :class:`~.StagedPassManager` -instance. +There is no limit on the number of stages you can put in a :class:`~.StagedPassManager`. -The :ref:`stage_generators` functions may be useful for the construction of custom pass managers. -They generate stages which provide common functionality used in many pass managers. -For example, :func:`~.generate_embed_passmanager` can be used to generate a stage +The :ref:`stage_generators` may be useful for the construction of custom :class:`~.StagedPassManager`s. +They generate pass managers which provide common functionality used in many stages. +For example, :func:`~.generate_embed_passmanager` generates a :class:`~.PassManager` to "embed" a selected initial :class:`~.Layout` from a layout pass to the specified target device. Representing Quantum Computers diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 8568b8eac1a2..c0cb58b3f63e 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -177,6 +177,7 @@ GatesInBasis ConvertConditionsToIfOps UnrollForLoops + FilterOpNodes """ # layout selection (placement) @@ -292,3 +293,4 @@ from .utils import GatesInBasis from .utils import ConvertConditionsToIfOps from .utils import UnrollForLoops +from .utils import FilterOpNodes diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 3033ab398cfa..aed57ee34206 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -202,8 +202,8 @@ def run(self, dag): "target basis is not universal or there are additional equivalence rules " "needed in the EquivalenceLibrary being used. For more details on this " "error see: " - "https://qiskit.org/documentation/stubs/qiskit.transpiler.passes." - "BasisTranslator.html#translation_errors" + "https://docs.quantum-computing.ibm.com/api/qiskit/qiskit.transpiler.passes." + "BasisTranslator#translation-errors" ) qarg_local_basis_transforms[qarg] = local_basis_transforms @@ -220,8 +220,8 @@ def run(self, dag): f"basis: {list(target_basis)}. This likely means the target basis is not universal " "or there are additional equivalence rules needed in the EquivalenceLibrary being " "used. For more details on this error see: " - "https://qiskit.org/documentation/stubs/qiskit.transpiler.passes.BasisTranslator." - "html#translation_errors" + "https://docs.quantum-computing.ibm.com/api/qiskit/qiskit.transpiler.passes." + "BasisTranslator#translation-errors" ) # Compose found path into a set of instruction substitution rules. diff --git a/qiskit/transpiler/passes/optimization/inverse_cancellation.py b/qiskit/transpiler/passes/optimization/inverse_cancellation.py index 560cdcd707f4..bb9552b3aa19 100644 --- a/qiskit/transpiler/passes/optimization/inverse_cancellation.py +++ b/qiskit/transpiler/passes/optimization/inverse_cancellation.py @@ -29,12 +29,13 @@ def __init__(self, gates_to_cancel: List[Union[Gate, Tuple[Gate, Gate]]]): """Initialize InverseCancellation pass. Args: - gates_to_cancel: list of gates to cancel + gates_to_cancel: List describing the gates to cancel. Each element of the + list is either a single gate or a pair of gates. If a single gate, then + it should be self-inverse. If a pair of gates, then the gates in the + pair should be inverses of each other. Raises: - TranspilerError: - Initialization raises an error when the input is not a self-inverse gate - or a two-tuple of inverse gates. + TranspilerError: Input is not a self-inverse gate or a pair of inverse gates. """ for gates in gates_to_cancel: 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 65d39abf1ab7..402aa9146f0a 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 @@ -54,8 +54,8 @@ class Commuting2qGateRouter(TransformationPass): .. code-block:: python from qiskit import QuantumCircuit - from qiskit.opflow import PauliSumOp from qiskit.circuit.library import PauliEvolutionGate + from qiskit.quantum_info import SparsePauliOp from qiskit.transpiler import Layout, CouplingMap, PassManager from qiskit.transpiler.passes import FullAncillaAllocation from qiskit.transpiler.passes import EnlargeWithAncilla @@ -69,7 +69,7 @@ class Commuting2qGateRouter(TransformationPass): ) # Define the circuit on virtual qubits - op = PauliSumOp.from_list([("IZZI", 1), ("ZIIZ", 2), ("ZIZI", 3)]) + op = SparsePauliOp.from_list([("IZZI", 1), ("ZIIZ", 2), ("ZIZI", 3)]) circ = QuantumCircuit(4) circ.append(PauliEvolutionGate(op, 1), range(4)) diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py index 8b92c5cdaf53..641b40c9f3e1 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py @@ -113,7 +113,7 @@ def _pauli_to_edge(pauli: Pauli) -> Tuple[int, ...]: return edge def _decompose_to_2q(self, dag: DAGCircuit, op: PauliEvolutionGate) -> DAGCircuit: - """Decompose the PauliSumOp into two-qubit. + """Decompose the SparsePauliOp into two-qubit. Args: dag: The dag needed to get access to qubits. diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 3f83a02237cd..250b310d3a80 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -309,6 +309,7 @@ def process_dag(block_dag, wire_map): node._node_id, [wire_map[x] for x in node.qargs], cargs, + getattr(node.op, "_directive", False), ) ) return SabreDAG(num_physical_qubits, block_dag.num_clbits(), dag_list, node_blocks) diff --git a/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py b/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py index 697d36fa71b6..e4bcdecbb666 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py +++ b/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py @@ -30,7 +30,7 @@ class ValidatePulseGates(AnalysisPass): In Qiskit SDK, we can define the pulse-level implementation of custom quantum gate instructions, as a `pulse gate - `__, + `__, thus user gates should satisfy all waveform memory constraints imposed by the backend. This pass validates all attached calibration entries and raises ``TranspilerError`` to diff --git a/qiskit/transpiler/passes/utils/__init__.py b/qiskit/transpiler/passes/utils/__init__.py index c5d605f7d732..7227409a213c 100644 --- a/qiskit/transpiler/passes/utils/__init__.py +++ b/qiskit/transpiler/passes/utils/__init__.py @@ -27,6 +27,7 @@ from .convert_conditions_to_if_ops import ConvertConditionsToIfOps from .unroll_forloops import UnrollForLoops from .minimum_point import MinimumPoint +from .filter_op_nodes import FilterOpNodes # Utility functions from . import control_flow diff --git a/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py b/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py index 2222ef064010..4633cc57af54 100644 --- a/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py +++ b/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py @@ -27,6 +27,10 @@ class BarrierBeforeFinalMeasurements(TransformationPass): other measurements or barriers.) """ + def __init__(self, label=None): + super().__init__() + self.label = label + def run(self, dag): """Run the BarrierBeforeFinalMeasurements pass on `dag`.""" # Collect DAG nodes which are followed only by barriers or other measures. @@ -64,7 +68,7 @@ def run(self, dag): final_qubits = dag.qubits barrier_layer.apply_operation_back( - Barrier(len(final_qubits)), final_qubits, (), check=False + Barrier(len(final_qubits), label=self.label), final_qubits, (), check=False ) # Preserve order of final ops collected earlier from the original DAG. @@ -83,6 +87,9 @@ def run(self, dag): dag.compose(barrier_layer) - # Merge the new barrier into any other barriers - adjacent_pass = MergeAdjacentBarriers() - return adjacent_pass.run(dag) + if self.label is None: + # Merge the new barrier into any other barriers + adjacent_pass = MergeAdjacentBarriers() + return adjacent_pass.run(dag) + else: + return dag diff --git a/qiskit/transpiler/passes/utils/filter_op_nodes.py b/qiskit/transpiler/passes/utils/filter_op_nodes.py new file mode 100644 index 000000000000..344d2280e3f4 --- /dev/null +++ b/qiskit/transpiler/passes/utils/filter_op_nodes.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. + +"""Filter ops from a circuit""" + +from typing import Callable + +from qiskit.dagcircuit import DAGCircuit, DAGOpNode +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.passes.utils import control_flow + + +class FilterOpNodes(TransformationPass): + """Remove all operations that match a filter function + + This transformation pass is used to remove any operations that matches a + the provided filter function. + + Args: + predicate: A given callable that will be passed the :class:`.DAGOpNode` + for each node in the :class:`.DAGCircuit`. If the callable returns + ``True`` the :class:`.DAGOpNode` is retained in the circuit and if it + returns ``False`` it is removed from the circuit. + + Example: + + Filter out operations that are labelled ``"foo"`` + + .. plot:: + :include-source: + + from qiskit import QuantumCircuit + from qiskit.transpiler.passes import FilterOpNodes + + circuit = QuantumCircuit(1) + circuit.x(0, label='foo') + circuit.barrier() + circuit.h(0) + + circuit = FilterOpNodes( + lambda node: getattr(node.op, "label") != "foo" + )(circuit) + circuit.draw('mpl') + """ + + def __init__(self, predicate: Callable[[DAGOpNode], bool]): + super().__init__() + self.predicate = predicate + + @control_flow.trivial_recurse + def run(self, dag: DAGCircuit) -> DAGCircuit: + """Run the RemoveBarriers pass on `dag`.""" + for node in dag.op_nodes(): + if not self.predicate(node): + dag.remove_op_node(node) + return dag diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index c9c67be05d6a..cf7c44d69a7d 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -338,7 +338,7 @@ def passes(self) -> list[dict[str, BasePass]]: class StagedPassManager(PassManager): - """A Pass manager pipeline built up of individual stages + """A pass manager pipeline built from individual stages. This class enables building a compilation pipeline out of fixed stages. Each ``StagedPassManager`` defines a list of stages which are executed in @@ -349,21 +349,23 @@ class StagedPassManager(PassManager): pass manager you are not able to modify the individual passes and are only able to modify stages. - By default instances of ``StagedPassManager`` define a typical full compilation + By default, instances of ``StagedPassManager`` define a typical full compilation pipeline from an abstract virtual circuit to one that is optimized and capable of running on the specified backend. The default pre-defined stages are: - #. ``init`` - any initial passes that are run before we start embedding the circuit to the backend - #. ``layout`` - This stage runs layout and maps the virtual qubits in the - circuit to the physical qubits on a backend - #. ``routing`` - This stage runs after a layout has been run and will insert any - necessary gates to move the qubit states around until it can be run on - backend's coupling map. - #. ``translation`` - Perform the basis gate translation, in other words translate the gates - in the circuit to the target backend's basis set - #. ``optimization`` - The main optimization loop, this will typically run in a loop trying to - optimize the circuit until a condition (such as fixed depth) is reached. - #. ``scheduling`` - Any hardware aware scheduling passes + #. ``init`` - Initial passes to run before embedding the circuit to the backend. + #. ``layout`` - Maps the virtual qubits in the circuit to the physical qubits on + the backend. + #. ``routing`` - Inserts gates as needed to move the qubit states around until + the circuit can be run with the chosen layout on the backend's coupling map. + #. ``translation`` - Translates the gates in the circuit to the target backend's + basis gate set. + #. ``optimization`` - Optimizes the circuit to reduce the cost of executing it. + These passes will typically run in a loop until a convergence criteria is met. + For example, the convergence criteria might be that the circuit depth does not + decrease in successive iterations. + #. ``scheduling`` - Hardware-aware passes that schedule the operations in the + circuit. .. note:: diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index ca21bd2a3b57..a473fdf6533d 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -684,7 +684,13 @@ def _swap_mapped(property_set): and pass_manager_config.routing_method != "sabre", ) layout.append( - [BarrierBeforeFinalMeasurements(), choose_layout_2], condition=_vf2_match_not_found + [ + BarrierBeforeFinalMeasurements( + "qiskit.transpiler.internal.routing.protection.barrier" + ), + choose_layout_2, + ], + condition=_vf2_match_not_found, ) elif optimization_level == 2: choose_layout_0 = VF2Layout( @@ -706,7 +712,13 @@ def _swap_mapped(property_set): and pass_manager_config.routing_method != "sabre", ) layout.append( - [BarrierBeforeFinalMeasurements(), choose_layout_1], condition=_vf2_match_not_found + [ + BarrierBeforeFinalMeasurements( + "qiskit.transpiler.internal.routing.protection.barrier" + ), + choose_layout_1, + ], + condition=_vf2_match_not_found, ) elif optimization_level == 3: choose_layout_0 = VF2Layout( @@ -728,7 +740,13 @@ def _swap_mapped(property_set): and pass_manager_config.routing_method != "sabre", ) layout.append( - [BarrierBeforeFinalMeasurements(), choose_layout_1], condition=_vf2_match_not_found + [ + BarrierBeforeFinalMeasurements( + "qiskit.transpiler.internal.routing.protection.barrier" + ), + choose_layout_1, + ], + condition=_vf2_match_not_found, ) else: raise TranspilerError(f"Invalid optimization level: {optimization_level}") @@ -882,7 +900,13 @@ def _swap_mapped(property_set): else: raise TranspilerError(f"Invalid optimization level: {optimization_level}") layout.append( - [BarrierBeforeFinalMeasurements(), layout_pass], condition=_choose_layout_condition + [ + BarrierBeforeFinalMeasurements( + "qiskit.transpiler.internal.routing.protection.barrier" + ), + layout_pass, + ], + condition=_choose_layout_condition, ) embed = common.generate_embed_passmanager(coupling_map) layout.append( diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 494aa7a2159e..c5402855a2ba 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -41,6 +41,7 @@ from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import RemoveResetInZeroState +from qiskit.transpiler.passes import FilterOpNodes from qiskit.transpiler.passes import ValidatePulseGates from qiskit.transpiler.passes import PadDelay from qiskit.transpiler.passes import InstructionDurationCheck @@ -328,7 +329,15 @@ def _swap_condition(property_set): return not property_set["routing_not_needed"] if use_barrier_before_measurement: - routing.append([BarrierBeforeFinalMeasurements(), routing_pass], condition=_swap_condition) + routing.append( + [ + BarrierBeforeFinalMeasurements( + label="qiskit.transpiler.internal.routing.protection.barrier" + ), + routing_pass, + ], + condition=_swap_condition, + ) else: routing.append([routing_pass], condition=_swap_condition) @@ -348,6 +357,14 @@ def _swap_condition(property_set): ) routing.append(ApplyLayout(), condition=_apply_post_layout_condition) + def filter_fn(node): + return ( + getattr(node.op, "label", None) + != "qiskit.transpiler.internal.routing.protection.barrier" + ) + + routing.append([FilterOpNodes(filter_fn)]) + return routing diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 5171951119d9..bc42a9c11e99 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -1477,7 +1477,7 @@ def target_to_backend_properties(target: Target): if getattr(props, "duration", None) is not None: property_list.append( { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "gate_length", "unit": "s", "value": props.duration, @@ -1486,7 +1486,7 @@ def target_to_backend_properties(target: Target): if getattr(props, "error", None) is not None: property_list.append( { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "gate_error", "unit": "", "value": props.error, @@ -1511,7 +1511,7 @@ def target_to_backend_properties(target: Target): if getattr(props, "error", None) is not None: props_list.append( { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "readout_error", "unit": "", "value": props.error, @@ -1520,7 +1520,7 @@ def target_to_backend_properties(target: Target): if getattr(props, "duration", None) is not None: props_list.append( { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "readout_length", "unit": "s", "value": props.duration, diff --git a/qiskit/utils/__init__.py b/qiskit/utils/__init__.py index 45200413ba2a..a9b73b85f95d 100644 --- a/qiskit/utils/__init__.py +++ b/qiskit/utils/__init__.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2021. +# (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 @@ -29,28 +29,6 @@ .. autofunction:: detach_prefix .. autofunction:: wrap_method -Algorithm Utilities -=================== - -.. autofunction:: summarize_circuits -.. autofunction:: get_entangler_map -.. autofunction:: validate_entangler_map -.. autofunction:: has_ibmq -.. autofunction:: has_aer -.. autofunction:: name_args -.. autodata:: algorithm_globals - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - QuantumInstance - -A QuantumInstance holds the Qiskit `backend` as well as a number of compile and -runtime parameters controlling circuit compilation and execution. Quantum -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`) ============================================================ @@ -58,7 +36,6 @@ .. automodule:: qiskit.utils.optionals """ -from .quantum_instance import QuantumInstance from .deprecation import ( add_deprecation_to_docstring, deprecate_arg, @@ -74,25 +51,10 @@ from . import optionals -from .circuit_utils import summarize_circuits -from .entangler_map import get_entangler_map, validate_entangler_map -from .backend_utils import has_ibmq, has_aer -from .name_unnamed_args import name_args -from .algorithm_globals import algorithm_globals - - __all__ = [ "LazyDependencyManager", "LazyImportTester", "LazySubprocessTester", - "QuantumInstance", - "summarize_circuits", - "get_entangler_map", - "validate_entangler_map", - "has_ibmq", - "has_aer", - "name_args", - "algorithm_globals", "add_deprecation_to_docstring", "deprecate_arg", "deprecate_arguments", diff --git a/qiskit/utils/algorithm_globals.py b/qiskit/utils/algorithm_globals.py deleted file mode 100644 index eb1dd4f492e0..000000000000 --- a/qiskit/utils/algorithm_globals.py +++ /dev/null @@ -1,170 +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. - -"""Algorithm Globals""" - -from typing import Optional -import logging - -import numpy as np - -from qiskit.tools import parallel -from qiskit.utils.deprecation import deprecate_func -from ..user_config import get_config -from ..exceptions import QiskitError - - -logger = logging.getLogger(__name__) - - -class QiskitAlgorithmGlobals: - """Class for global properties.""" - - CPU_COUNT = parallel.local_hardware_info()["cpus"] - - @deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", - ) - def __init__(self) -> None: - self._random_seed = None # type: Optional[int] - self._num_processes = QiskitAlgorithmGlobals.CPU_COUNT - self._random = None - self._massive = False - try: - settings = get_config() - self.num_processes = settings.get("num_processes", QiskitAlgorithmGlobals.CPU_COUNT) - except Exception as ex: # pylint: disable=broad-except - logger.debug("User Config read error %s", str(ex)) - - @property - @deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", - is_property=True, - ) - def random_seed(self) -> Optional[int]: - """Return random seed.""" - return self._random_seed - - @random_seed.setter - @deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", - is_property=True, - ) - def random_seed(self, seed: Optional[int]) -> None: - """Set random seed.""" - self._random_seed = seed - self._random = None - - @property - @deprecate_func( - additional_msg=( - "This algorithm utility belongs to a legacy workflow and has no replacement." - ), - since="0.45.0", - is_property=True, - ) - def num_processes(self) -> int: - """Return num processes.""" - return self._num_processes - - @num_processes.setter - @deprecate_func( - additional_msg=( - "This algorithm utility belongs to a legacy workflow and has no replacement." - ), - since="0.45.0", - is_property=True, - ) - def num_processes(self, num_processes: Optional[int]) -> None: - """Set num processes. - If 'None' is passed, it resets to QiskitAlgorithmGlobals.CPU_COUNT - """ - if num_processes is None: - num_processes = QiskitAlgorithmGlobals.CPU_COUNT - elif num_processes < 1: - raise QiskitError(f"Invalid Number of Processes {num_processes}.") - elif num_processes > QiskitAlgorithmGlobals.CPU_COUNT: - raise QiskitError( - "Number of Processes {} cannot be greater than cpu count {}.".format( - num_processes, QiskitAlgorithmGlobals.CPU_COUNT - ) - ) - self._num_processes = num_processes - # TODO: change Terra CPU_COUNT until issue - # gets resolved: https://github.com/Qiskit/qiskit-terra/issues/1963 - try: - parallel.CPU_COUNT = self.num_processes - except Exception as ex: # pylint: disable=broad-except - logger.warning( - "Failed to set qiskit.tools.parallel.CPU_COUNT to value: '%s': Error: '%s'", - self.num_processes, - str(ex), - ) - - @property - @deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", - is_property=True, - ) - def random(self) -> np.random.Generator: - """Return a numpy np.random.Generator (default_rng).""" - if self._random is None: - self._random = np.random.default_rng(self._random_seed) - return self._random - - @property - @deprecate_func( - additional_msg=( - "This algorithm utility belongs to a legacy workflow and has no replacement." - ), - since="0.45.0", - is_property=True, - ) - def massive(self) -> bool: - """Return massive to allow processing of large matrices or vectors.""" - return self._massive - - @massive.setter - @deprecate_func( - additional_msg=( - "This algorithm utility belongs to a legacy workflow and has no replacement." - ), - since="0.45.0", - is_property=True, - ) - def massive(self, massive: bool) -> None: - """Set massive to allow processing of large matrices or vectors.""" - self._massive = massive - - -# Global instance to be used as the entry point for globals. -algorithm_globals = QiskitAlgorithmGlobals() diff --git a/qiskit/utils/arithmetic.py b/qiskit/utils/arithmetic.py deleted file mode 100644 index 23a838721f9e..000000000000 --- a/qiskit/utils/arithmetic.py +++ /dev/null @@ -1,152 +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. - -""" -Arithmetic Utilities -""" - -from typing import List, Tuple -import numpy as np - - -def normalize_vector(vector): - """ - Normalize the input state vector. - """ - return vector / np.linalg.norm(vector) - - -def is_power_of_2(num): - """ - Check if the input number is a power of 2. - """ - return num != 0 and ((num & (num - 1)) == 0) - - -def log2(num): - """ - Compute the log2 of the input number. Use bit operation if the input is a power of 2. - """ - if is_power_of_2(num): - ret = 0 - while True: - if num >> ret == 1: - return ret - else: - ret += 1 - else: - return np.log2(num) - - -def is_power(num, return_decomposition=False): - """ - Check if num is a perfect power in O(n^3) time, n=ceil(logN) - """ - b = 2 - while (2**b) <= num: - a = 1 - c = num - while (c - a) >= 2: - m = int((a + c) / 2) - - if (m**b) < (num + 1): - p = int(m**b) - else: - p = int(num + 1) - - if int(p) == int(num): - if return_decomposition: - return True, int(m), int(b) - else: - return True - - if p < num: - a = int(m) - else: - c = int(m) - b = b + 1 - if return_decomposition: - return False, num, 1 - else: - return False - - -def next_power_of_2_base(n): - """ - Return the base of the smallest power of 2 no less than the input number - """ - base = 0 - if n and not (n & (n - 1)): # pylint: disable=superfluous-parens - return log2(n) - - while n != 0: - n >>= 1 - base += 1 - - return base - - -def transpositions(permutation: List[int]) -> List[Tuple[int, int]]: - """Return a sequence of transpositions, corresponding to the permutation. - - Args: - permutation: The ``List[int]`` defining the permutation. An element at index ``j`` should be - permuted to index ``permutation[j]``. - - Returns: - List of transpositions, corresponding to the permutation. For permutation = [3, 0, 2, 1], - returns [(0,1), (0,3)] - """ - unchecked = [True] * len(permutation) - cyclic_form = [] - for i in range(len(permutation)): - if unchecked[i]: - cycle = [i] - unchecked[i] = False - j = i - while unchecked[permutation[j]]: - j = permutation[j] - cycle.append(j) - unchecked[j] = False - if len(cycle) > 1: - cyclic_form.append(cycle) - cyclic_form.sort() - res = [] - for x in cyclic_form: - len_x = len(x) - if len_x == 2: - res.append((x[0], x[1])) - elif len_x > 2: - first = x[0] - for y in x[len_x - 1 : 0 : -1]: - res.append((first, y)) - return res - - -def triu_to_dense(triu: np.ndarray) -> np.ndarray: - """Converts upper triangular part of matrix to dense matrix. - - Args: - triu: array in the form [[A, B, C], [D, E], [F]] - - Returns: - Array [[A, B, C], [B, D, E], [C, E, F]] - """ - dim = len(triu) - matrix = np.empty((dim, dim), dtype=complex) - for i in range(dim): - for j in range(dim - i): - matrix[i, i + j] = triu[i][j] - if j != 0: - matrix[i + j, i] = triu[i][j] - - return matrix diff --git a/qiskit/utils/backend_utils.py b/qiskit/utils/backend_utils.py deleted file mode 100644 index cdce30da8dc6..000000000000 --- a/qiskit/utils/backend_utils.py +++ /dev/null @@ -1,296 +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. - -"""backend utility functions""" - -import logging -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - -_UNSUPPORTED_BACKENDS = ["unitary_simulator", "clifford_simulator"] - -# pylint: disable=no-name-in-module,unused-import - - -class ProviderCheck: - """Contains Provider verification info.""" - - def __init__(self) -> None: - self.has_ibmq = False - self.checked_ibmq = False - self.has_aer = False - self.checked_aer = False - - -_PROVIDER_CHECK = ProviderCheck() - - -def _get_backend_interface_version(backend): - """Get the backend version int.""" - backend_interface_version = getattr(backend, "version", None) - return backend_interface_version - - -def _get_backend_provider(backend): - backend_interface_version = _get_backend_interface_version(backend) - if backend_interface_version > 1: - provider = backend.provider - else: - provider = backend.provider() - return provider - - -@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(): - """Check if IBMQ is installed.""" - if not _PROVIDER_CHECK.checked_ibmq: - try: - from qiskit.providers.ibmq import IBMQFactory - from qiskit.providers.ibmq.accountprovider import AccountProvider - - _PROVIDER_CHECK.has_ibmq = True - except Exception as ex: # pylint: disable=broad-except - _PROVIDER_CHECK.has_ibmq = False - logger.debug("IBMQFactory/AccountProvider not loaded: '%s'", str(ex)) - - _PROVIDER_CHECK.checked_ibmq = True - - return _PROVIDER_CHECK.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(): - """Check if Aer is installed.""" - if not _PROVIDER_CHECK.checked_aer: - try: - from qiskit.providers.aer import AerProvider - - _PROVIDER_CHECK.has_aer = True - except Exception as ex: # pylint: disable=broad-except - _PROVIDER_CHECK.has_aer = False - logger.debug("AerProvider not loaded: '%s'", str(ex)) - - _PROVIDER_CHECK.checked_aer = True - - return _PROVIDER_CHECK.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): - """Detect whether or not backend is from Aer provider. - - Args: - backend (Backend): backend instance - Returns: - bool: True is AerProvider - """ - if has_aer(): - from qiskit.providers.aer import AerProvider - - if isinstance(_get_backend_provider(backend), AerProvider): - return True - from qiskit.providers.aer.backends.aerbackend import AerBackend - - return isinstance(backend, AerBackend) - - return False - - -@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): - """Detect whether or not backend is from BasicAer provider. - - Args: - backend (Backend): backend instance - Returns: - bool: True is BasicAer - """ - from qiskit.providers.basicaer import BasicAerProvider - - return isinstance(_get_backend_provider(backend), BasicAerProvider) - - -@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): - """Detect whether or not backend is from IBMQ provider. - - Args: - backend (Backend): backend instance - Returns: - bool: True is IBMQ - """ - if has_ibmq(): - from qiskit.providers.ibmq.accountprovider import AccountProvider - - return isinstance(_get_backend_provider(backend), AccountProvider) - - return False - - -@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): - """ - Return True if backend object is statevector and from Aer provider. - - Args: - backend (Backend): backend instance - Returns: - bool: True is statevector - """ - return is_statevector_backend(backend) and 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_statevector_backend(backend): - """ - Return True if backend object is statevector. - - Args: - backend (Backend): backend instance - 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): - 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: - return backend.name.startswith("statevector") - - -@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): - """ - Return True if backend is a simulator. - - Args: - backend (Backend): backend instance - Returns: - bool: True is a simulator - """ - backend_interface_version = _get_backend_interface_version(backend) - if backend_interface_version <= 1: - return backend.configuration().simulator - else: - if "simulator" in backend.name: - return True - return False - - -@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): - """ - Return True if backend is a local backend. - - Args: - backend (Backend): backend instance - Returns: - bool: True is a local backend - """ - backend_interface_version = _get_backend_interface_version(backend) - if backend_interface_version <= 1: - return backend.configuration().local - else: - if "simulator" in backend.name: - return True - return False - - -@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): - """ - Return True if backend is Aer Qasm simulator - Args: - backend (Backend): backend instance - - Returns: - bool: True is Aer Qasm simulator - """ - ret = False - if is_aer_provider(backend): - if not is_statevector_backend(backend): - ret = True - return ret - - -@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): - """ - Return True if backend supports backend_options - Args: - backend (Backend): backend instance - - Returns: - bool: True is support backend_options - """ - ret = False - if is_basicaer_provider(backend) or is_aer_provider(backend): - ret = True - return ret diff --git a/qiskit/utils/circuit_utils.py b/qiskit/utils/circuit_utils.py deleted file mode 100644 index 2fe140d3780d..000000000000 --- a/qiskit/utils/circuit_utils.py +++ /dev/null @@ -1,69 +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. - -"""Circuit utility functions""" - -import numpy as np - - -def summarize_circuits(circuits): - """Summarize circuits based on QuantumCircuit, and five metrics are summarized. - - Number of qubits - - Number of classical bits - - Number of operations - - Depth of circuits - - Counts of different gate operations - - The average statistic of the first four is provided if multiple circuits are provided. - - Args: - circuits (QuantumCircuit or [QuantumCircuit]): the to-be-summarized circuits - - Returns: - str: a formatted string records the summary - """ - if not isinstance(circuits, list): - circuits = [circuits] - ret = "" - ret += f"Submitting {len(circuits)} circuits.\n" - ret += "============================================================================\n" - stats = np.zeros(4) - for i, circuit in enumerate(circuits): - depth = circuit.depth() - size = circuit.size() - num_qubits = sum(reg.size for reg in circuit.qregs) - num_clbits = sum(reg.size for reg in circuit.cregs) - op_counts = circuit.count_ops() - stats[0] += num_qubits - stats[1] += num_clbits - stats[2] += size - stats[3] += depth - ret = "".join( - [ - ret, - "{}-th circuit: {} qubits, {} classical bits and {} " - "operations with depth {}\nop_counts: {}\n".format( - i, num_qubits, num_clbits, size, depth, op_counts - ), - ] - ) - if len(circuits) > 1: - stats /= len(circuits) - ret = "".join( - [ - ret, - "Average: {:.2f} qubits, {:.2f} classical bits and {:.2f} " - "operations with depth {:.2f}\n".format(stats[0], stats[1], stats[2], stats[3]), - ] - ) - ret += "============================================================================\n" - return ret diff --git a/qiskit/utils/entangler_map.py b/qiskit/utils/entangler_map.py deleted file mode 100644 index 1cd750398ccb..000000000000 --- a/qiskit/utils/entangler_map.py +++ /dev/null @@ -1,111 +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. - -""" -This module contains the definition of creating and validating entangler map -based on the number of qubits. -""" - - -def get_entangler_map(map_type, num_qubits, offset=0): - """Utility method to get an entangler map among qubits. - - Args: - map_type (str): 'full' entangles each qubit with all the subsequent ones - 'linear' entangles each qubit with the next - 'sca' (shifted circular alternating entanglement) is a - circular entanglement where the 'long' entanglement is - shifted by one position every block and every block the - role or control/target qubits alternate - num_qubits (int): Number of qubits for which the map is needed - offset (int): Some map_types (e.g. 'sca') can shift the gates in - the entangler map by the specified integer offset. - - Returns: - list: A map of qubit index to an array of indexes to which this should be entangled - - Raises: - ValueError: if map_type is not valid. - """ - ret = [] - - if num_qubits > 1: - if map_type == "full": - ret = [[i, j] for i in range(num_qubits) for j in range(i + 1, num_qubits)] - elif map_type == "linear": - ret = [[i, i + 1] for i in range(num_qubits - 1)] - elif map_type == "sca": - offset_idx = offset % num_qubits - if offset_idx % 2 == 0: # even block numbers - for i in reversed(range(offset_idx)): - ret += [[i, i + 1]] - - ret += [[num_qubits - 1, 0]] - - for i in reversed(range(offset_idx + 1, num_qubits)): - ret += [[i - 1, i]] - - else: # odd block numbers - for i in range(num_qubits - offset_idx - 1, num_qubits - 1): - ret += [[i + 1, i]] - - ret += [[0, num_qubits - 1]] - - for i in range(num_qubits - offset_idx - 1): - ret += [[i + 1, i]] - else: - raise ValueError("map_type only supports 'full', 'linear' or 'sca' type.") - return ret - - -def validate_entangler_map(entangler_map, num_qubits, allow_double_entanglement=False): - """Validate a user supplied entangler map and converts entries to ints. - - Args: - entangler_map (list[list]) : An entangler map, keys are source qubit index (int), - value is array - of target qubit index(es) (int) - num_qubits (int) : Number of qubits - allow_double_entanglement (bool): If we allow in two qubits can be entangled each other - - Returns: - list: Validated/converted map - - Raises: - TypeError: entangler map is not list type or list of list - ValueError: the index of entangler map is out of range - ValueError: the qubits are cross-entangled. - - """ - - if isinstance(entangler_map, dict): - raise TypeError("The type of entangler map is changed to list of list.") - - if not isinstance(entangler_map, list): - raise TypeError("Entangler map type 'list' expected") - - for src_to_targ in entangler_map: - if not isinstance(src_to_targ, list): - raise TypeError(f"Entangle index list expected but got {type(src_to_targ)}") - - ret_map = [] - ret_map = [[int(src), int(targ)] for src, targ in entangler_map] - - for src, targ in ret_map: - if src < 0 or src >= num_qubits: - raise ValueError(f"Qubit entangle source value {src} invalid for {num_qubits} qubits") - if targ < 0 or targ >= num_qubits: - raise ValueError(f"Qubit entangle target value {targ} invalid for {num_qubits} qubits") - if not allow_double_entanglement and [targ, src] in ret_map: - raise ValueError(f"Qubit {src} and {targ} cross-entangled.") - - return ret_map diff --git a/qiskit/utils/measurement_error_mitigation.py b/qiskit/utils/measurement_error_mitigation.py deleted file mode 100644 index 4702fc0b52b8..000000000000 --- a/qiskit/utils/measurement_error_mitigation.py +++ /dev/null @@ -1,272 +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. - -"""Measurement error mitigation""" - -import copy -from typing import List, Optional, Tuple, Dict, Callable - -from qiskit import compiler -from qiskit.providers import Backend -from qiskit.circuit import QuantumCircuit -from qiskit.qobj import QasmQobj -from qiskit.assembler.run_config import RunConfig -from qiskit.exceptions import QiskitError -from qiskit.utils.mitigation import ( - complete_meas_cal, - tensored_meas_cal, - CompleteMeasFitter, - TensoredMeasFitter, -) -from qiskit.utils.deprecation import deprecate_func - - -@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( - transpiled_circuits: List[QuantumCircuit], -) -> Tuple[List[int], Dict[str, List[int]]]: - """ - Deprecated: Retrieve the measured qubits from transpiled circuits. - - Args: - transpiled_circuits: a list of transpiled circuits - - Returns: - The used and sorted qubit index - Key is qubit index str connected by '_', - value is the experiment index. {str: list[int]} - Raises: - QiskitError: invalid qubit mapping - """ - qubit_index = None - qubit_mappings = {} - for idx, qc in enumerate(transpiled_circuits): - measured_qubits = [] - for instruction in qc.data: - if instruction.operation.name != "measure": - continue - for qreg in qc.qregs: - if instruction.qubits[0] in qreg: - index = qreg[:].index(instruction.qubits[0]) - measured_qubits.append(index) - break - measured_qubits_str = "_".join([str(x) for x in measured_qubits]) - if measured_qubits_str not in qubit_mappings: - qubit_mappings[measured_qubits_str] = [] - qubit_mappings[measured_qubits_str].append(idx) - if qubit_index is None: - qubit_index = measured_qubits - elif set(qubit_index) != set(measured_qubits): - raise QiskitError( - "The used qubit index are different. ({}) vs ({}).\nCurrently, " - "we only support all circuits using the same set of qubits " - "regardless qubit order.".format(qubit_index, measured_qubits) - ) - - return sorted(qubit_index), qubit_mappings - - -@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]]]: - """ - Deprecated: Retrieve the measured qubits from transpiled circuits. - - Args: - qobj: qobj - - Returns: - the used and sorted qubit index - key is qubit index str connected by '_', - value is the experiment index. {str: list[int]} - Raises: - QiskitError: invalid qubit mapping - """ - - qubit_index = None - qubit_mappings = {} - - for idx, exp in enumerate(qobj.experiments): - measured_qubits = [] - for instr in exp.instructions: - if instr.name != "measure": - continue - measured_qubits.append(instr.qubits[0]) - measured_qubits_str = "_".join([str(x) for x in measured_qubits]) - if measured_qubits_str not in qubit_mappings: - qubit_mappings[measured_qubits_str] = [] - qubit_mappings[measured_qubits_str].append(idx) - if qubit_index is None: - qubit_index = measured_qubits - else: - if set(qubit_index) != set(measured_qubits): - raise QiskitError( - "The used qubit index are different. ({}) vs ({}).\nCurrently, " - "we only support all circuits using the same set of qubits " - "regardless qubit order.".format(qubit_index, measured_qubits) - ) - - return sorted(qubit_index), qubit_mappings - - -@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( - qubit_list: List[int], - fitter_cls: Callable, - backend: Backend, - backend_config: Optional[Dict] = None, - compile_config: Optional[Dict] = None, - mit_pattern: Optional[List[List[int]]] = None, -) -> Tuple[QuantumCircuit, List[str], List[str]]: - """Deprecated: Build measurement error mitigation circuits - Args: - qubit_list: list of ordered qubits used in the algorithm - fitter_cls: CompleteMeasFitter or TensoredMeasFitter - backend: backend instance - backend_config: configuration for backend - compile_config: configuration for compilation - mit_pattern: Qubits on which to perform the - measurement correction, divided to groups according to tensors. - If `None` and `qr` is given then assumed to be performed over the entire - `qr` as one group (default `None`). - - Returns: - the circuit - the state labels for build MeasFitter - the labels of the calibration circuits - Raises: - QiskitError: when the fitter_cls is not recognizable. - """ - circlabel = "mcal" - - if not qubit_list: - raise QiskitError("The measured qubit list can not be [].") - - run = False - if fitter_cls == CompleteMeasFitter: - meas_calibs_circuits, state_labels = complete_meas_cal( - qubit_list=range(len(qubit_list)), circlabel=circlabel - ) - run = True - elif fitter_cls == TensoredMeasFitter: - meas_calibs_circuits, state_labels = tensored_meas_cal( - mit_pattern=mit_pattern, circlabel=circlabel - ) - run = True - if not run: - try: - from qiskit.ignis.mitigation.measurement import ( - CompleteMeasFitter as CompleteMeasFitter_IG, - TensoredMeasFitter as TensoredMeasFitter_IG, - ) - except ImportError as ex: - # If ignis can't be imported we don't have a valid fitter - # class so just fail here with an appropriate error message - raise QiskitError(f"Unknown fitter {fitter_cls}") from ex - if fitter_cls == CompleteMeasFitter_IG: - meas_calibs_circuits, state_labels = complete_meas_cal( - qubit_list=range(len(qubit_list)), circlabel=circlabel - ) - elif fitter_cls == TensoredMeasFitter_IG: - meas_calibs_circuits, state_labels = tensored_meas_cal( - mit_pattern=mit_pattern, circlabel=circlabel - ) - else: - raise QiskitError(f"Unknown fitter {fitter_cls}") - - # the provided `qubit_list` would be used as the initial layout to - # assure the consistent qubit mapping used in the main circuits. - - tmp_compile_config = copy.deepcopy(compile_config) - tmp_compile_config["initial_layout"] = qubit_list - t_meas_calibs_circuits = compiler.transpile( - meas_calibs_circuits, backend, **backend_config, **tmp_compile_config - ) - return t_meas_calibs_circuits, state_labels, circlabel - - -@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( - qubit_list: List[int], - fitter_cls: Callable, - backend: Backend, - backend_config: Optional[Dict] = None, - compile_config: Optional[Dict] = None, - run_config: Optional[RunConfig] = None, - mit_pattern: Optional[List[List[int]]] = None, -) -> Tuple[QasmQobj, List[str], List[str]]: - """ - Args: - qubit_list: list of ordered qubits used in the algorithm - fitter_cls: CompleteMeasFitter or TensoredMeasFitter - backend: backend instance - backend_config: configuration for backend - compile_config: configuration for compilation - run_config: configuration for running a circuit - mit_pattern: Qubits on which to perform the - measurement correction, divided to groups according to tensors. - If `None` and `qr` is given then assumed to be performed over the entire - `qr` as one group (default `None`). - - Returns: - the Qobj with calibration circuits at the beginning - the state labels for build MeasFitter - the labels of the calibration circuits - - Raises: - QiskitError: when the fitter_cls is not recognizable. - MissingOptionalLibraryError: Qiskit-Ignis not installed - """ - - circlabel = "mcal" - - if not qubit_list: - raise QiskitError("The measured qubit list can not be [].") - - if fitter_cls == CompleteMeasFitter: - meas_calibs_circuits, state_labels = complete_meas_cal( - qubit_list=range(len(qubit_list)), circlabel=circlabel - ) - elif fitter_cls == TensoredMeasFitter: - meas_calibs_circuits, state_labels = tensored_meas_cal( - mit_pattern=mit_pattern, circlabel=circlabel - ) - else: - raise QiskitError(f"Unknown fitter {fitter_cls}") - - # the provided `qubit_list` would be used as the initial layout to - # assure the consistent qubit mapping used in the main circuits. - - tmp_compile_config = copy.deepcopy(compile_config) - tmp_compile_config["initial_layout"] = qubit_list - t_meas_calibs_circuits = compiler.transpile( - meas_calibs_circuits, backend, **backend_config, **tmp_compile_config - ) - cals_qobj = compiler.assemble(t_meas_calibs_circuits, backend, **run_config.to_dict()) - if hasattr(cals_qobj.config, "parameterizations"): - del cals_qobj.config.parameterizations - return cals_qobj, state_labels, circlabel diff --git a/qiskit/utils/mitigation/__init__.py b/qiskit/utils/mitigation/__init__.py deleted file mode 100644 index 54251715424d..000000000000 --- a/qiskit/utils/mitigation/__init__.py +++ /dev/null @@ -1,58 +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. - -# This code was originally copied from the qiskit-ignis repsoitory see: -# https://github.com/Qiskit/qiskit-ignis/blob/b91066c72171bcd55a70e6e8993b813ec763cf41/qiskit/ignis/mitigation/measurement/__init__.py -# it was migrated as qiskit-ignis is being deprecated - -""" -============================================================= -Measurement Mitigation Utils (:mod:`qiskit.utils.mitigation`) -============================================================= - -.. currentmodule:: qiskit.utils.mitigation - -.. deprecated:: 0.24.0 - This module is deprecated and will be removed no sooner than 3 months - after the release date. For code migration guidelines, - visit https://qisk.it/qi_migration. - -.. warning:: - - The user-facing API stability of this module is not guaranteed except for - its use with the :class:`~qiskit.utils.QuantumInstance` (i.e. using the - :class:`~qiskit.utils.mitigation.CompleteMeasFitter` or - :class:`~qiskit.utils.mitigation.TensoredMeasFitter` classes as values for the - ``meas_error_mitigation_cls``). The rest of this module should be treated as - an internal private API that can not be relied upon. - -Measurement correction -====================== - -The measurement calibration is used to mitigate measurement errors. -The main idea is to prepare all :math:`2^n` basis input states and compute -the probability of measuring counts in the other basis states. -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.opflow`. - -.. autosummary:: - :toctree: ../stubs/ - - CompleteMeasFitter - TensoredMeasFitter -""" - -# Measurement correction functions -from .circuits import complete_meas_cal, tensored_meas_cal -from .fitters import CompleteMeasFitter, TensoredMeasFitter diff --git a/qiskit/utils/mitigation/_filters.py b/qiskit/utils/mitigation/_filters.py deleted file mode 100644 index d29700338d4e..000000000000 --- a/qiskit/utils/mitigation/_filters.py +++ /dev/null @@ -1,510 +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. - -# This code was originally copied from the qiskit-ignis see: -# https://github.com/Qiskit/qiskit-ignis/blob/b91066c72171bcd55a70e6e8993b813ec763cf41/qiskit/ignis/mitigation/measurement/filters.py -# it was migrated as qiskit-ignis is being deprecated - -# pylint: disable=cell-var-from-loop - - -""" -Measurement correction filters. - -""" - -from typing import List -from copy import deepcopy - -import numpy as np - -import qiskit -from qiskit import QiskitError -from qiskit.tools import parallel_map -from qiskit.utils.mitigation.circuits import count_keys -from qiskit.utils.deprecation import deprecate_func - - -class MeasurementFilter: - """ - Deprecated: Measurement error mitigation filter. - - Produced from a measurement calibration fitter and can be applied - to data. - - """ - - @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): - """ - Initialize a measurement error mitigation filter using the cal_matrix - from a measurement calibration fitter. - - Args: - cal_matrix: the calibration matrix for applying the correction - state_labels: the states for the ordering of the cal matrix - """ - - self._cal_matrix = cal_matrix - self._state_labels = state_labels - - @property - def cal_matrix(self): - """Return cal_matrix.""" - return self._cal_matrix - - @property - def state_labels(self): - """return the state label ordering of the cal matrix""" - return self._state_labels - - @state_labels.setter - def state_labels(self, new_state_labels): - """set the state label ordering of the cal matrix""" - self._state_labels = new_state_labels - - @cal_matrix.setter - def cal_matrix(self, new_cal_matrix): - """Set cal_matrix.""" - self._cal_matrix = new_cal_matrix - - def apply(self, raw_data, method="least_squares"): - """Apply the calibration matrix to results. - - Args: - raw_data (dict or list): The data to be corrected. Can be in a number of forms: - - Form 1: a counts dictionary from results.get_counts - - Form 2: a list of counts of `length==len(state_labels)` - - Form 3: a list of counts of `length==M*len(state_labels)` where M is an - integer (e.g. for use with the tomography data) - - Form 4: a qiskit Result - - method (str): fitting method. If `None`, then least_squares is used. - - ``pseudo_inverse``: direct inversion of the A matrix - - ``least_squares``: constrained to have physical probabilities - - Returns: - dict or list: The corrected data in the same form as `raw_data` - - Raises: - QiskitError: if `raw_data` is not an integer multiple - of the number of calibrated states. - - """ - from scipy.optimize import minimize - from scipy import linalg as la - - # check forms of raw_data - if isinstance(raw_data, dict): - # counts dictionary - for data_label in raw_data.keys(): - if data_label not in self._state_labels: - raise QiskitError( - f"Unexpected state label '{data_label}'." - " Verify the fitter's state labels correspond to the input data." - ) - - data_format = 0 - # convert to form2 - raw_data2 = [np.zeros(len(self._state_labels), dtype=float)] - for stateidx, state in enumerate(self._state_labels): - raw_data2[0][stateidx] = raw_data.get(state, 0) - - elif isinstance(raw_data, list): - size_ratio = len(raw_data) / len(self._state_labels) - if len(raw_data) == len(self._state_labels): - data_format = 1 - raw_data2 = [raw_data] - elif int(size_ratio) == size_ratio: - data_format = 2 - size_ratio = int(size_ratio) - # make the list into chunks the size of state_labels for easier - # processing - raw_data2 = np.zeros([size_ratio, len(self._state_labels)]) - for i in range(size_ratio): - raw_data2[i][:] = raw_data[ - i * len(self._state_labels) : (i + 1) * len(self._state_labels) - ] - else: - raise QiskitError( - "Data list is not an integer multiple of the number of calibrated states" - ) - - elif isinstance(raw_data, qiskit.result.result.Result): - - # extract out all the counts, re-call the function with the - # counts and push back into the new result - new_result = deepcopy(raw_data) - - new_counts_list = parallel_map( - self._apply_correction, - [resultidx for resultidx, _ in enumerate(raw_data.results)], - task_args=(raw_data, method), - ) - - for resultidx, new_counts in new_counts_list: - new_result.results[resultidx].data.counts = new_counts - - return new_result - - else: - raise QiskitError("Unrecognized type for raw_data.") - - if method == "pseudo_inverse": - pinv_cal_mat = la.pinv(self._cal_matrix) - - # Apply the correction - for data_idx, _ in enumerate(raw_data2): - - if method == "pseudo_inverse": - raw_data2[data_idx] = np.dot(pinv_cal_mat, raw_data2[data_idx]) - - elif method == "least_squares": - nshots = sum(raw_data2[data_idx]) - - def fun(x): - return sum((raw_data2[data_idx] - np.dot(self._cal_matrix, x)) ** 2) - - x0 = np.random.rand(len(self._state_labels)) - x0 = x0 / sum(x0) - cons = {"type": "eq", "fun": lambda x: nshots - sum(x)} - bnds = tuple((0, nshots) for x in x0) - res = minimize(fun, x0, method="SLSQP", constraints=cons, bounds=bnds, tol=1e-6) - raw_data2[data_idx] = res.x - - else: - raise QiskitError("Unrecognized method.") - - if data_format == 2: - # flatten back out the list - raw_data2 = raw_data2.flatten() - - elif data_format == 0: - # convert back into a counts dictionary - new_count_dict = {} - for stateidx, state in enumerate(self._state_labels): - if raw_data2[0][stateidx] != 0: - new_count_dict[state] = raw_data2[0][stateidx] - - raw_data2 = new_count_dict - else: - # TODO: should probably change to: - # raw_data2 = raw_data2[0].tolist() - raw_data2 = raw_data2[0] - return raw_data2 - - def _apply_correction(self, resultidx, raw_data, method): - """Wrapper to call apply with a counts dictionary.""" - new_counts = self.apply(raw_data.get_counts(resultidx), method=method) - return resultidx, new_counts - - -class TensoredFilter: - """ - Deprecated: Tensored measurement error mitigation filter. - - Produced from a tensored measurement calibration fitter and can be applied - to data. - """ - - @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): - """ - Initialize a tensored measurement error mitigation filter using - the cal_matrices from a tensored measurement calibration fitter. - A simple usage this class is explained [here] - (https://qiskit.org/documentation/tutorials/noise/3_measurement_error_mitigation.html). - - Args: - cal_matrices: the calibration matrices for applying the correction. - substate_labels_list: for each calibration matrix - a list of the states (as strings, states in the subspace) - mit_pattern: for each calibration matrix - a list of the logical qubit indices (as int, states in the subspace) - """ - - self._cal_matrices = cal_matrices - self._qubit_list_sizes = [] - self._indices_list = [] - self._substate_labels_list = [] - self.substate_labels_list = substate_labels_list - self._mit_pattern = mit_pattern - - @property - def cal_matrices(self): - """Return cal_matrices.""" - return self._cal_matrices - - @cal_matrices.setter - def cal_matrices(self, new_cal_matrices): - """Set cal_matrices.""" - self._cal_matrices = deepcopy(new_cal_matrices) - - @property - def substate_labels_list(self): - """Return _substate_labels_list""" - return self._substate_labels_list - - @substate_labels_list.setter - def substate_labels_list(self, new_substate_labels_list): - """Return _substate_labels_list""" - self._substate_labels_list = new_substate_labels_list - - # get the number of qubits in each subspace - self._qubit_list_sizes = [] - for _, substate_label_list in enumerate(self._substate_labels_list): - self._qubit_list_sizes.append(int(np.log2(len(substate_label_list)))) - - # get the indices in the calibration matrix - self._indices_list = [] - for _, sub_labels in enumerate(self._substate_labels_list): - - self._indices_list.append({lab: ind for ind, lab in enumerate(sub_labels)}) - - @property - def qubit_list_sizes(self): - """Return _qubit_list_sizes.""" - return self._qubit_list_sizes - - @property - def nqubits(self): - """Return the number of qubits. See also MeasurementFilter.apply()""" - return sum(self._qubit_list_sizes) - - def apply( - self, - raw_data, - method="least_squares", - meas_layout=None, - ): - """ - Apply the calibration matrices to results. - - Args: - raw_data (dict or Result): The data to be corrected. Can be in one of two forms: - - * A counts dictionary from results.get_counts - - * A Qiskit Result - - method (str): fitting method. The following methods are supported: - - * 'pseudo_inverse': direct inversion of the cal matrices. - Mitigated counts can contain negative values - and the sum of counts would not equal to the shots. - Mitigation is conducted qubit wise: - For each qubit, mitigate the whole counts using the calibration matrices - which affect the corresponding qubit. - For example, assume we are mitigating the 3rd bit of the 4-bit counts - using '2\times 2' calibration matrix `A_3`. - When mitigating the count of '0110' in this step, - the following formula is applied: - `count['0110'] = A_3^{-1}[1, 0]*count['0100'] + A_3^{-1}[1, 1]*count['0110']`. - - The total time complexity of this method is `O(m2^{n + t})`, - where `n` is the size of calibrated qubits, - `m` is the number of sets in `mit_pattern`, - and `t` is the size of largest set of mit_pattern. - If the `mit_pattern` is shaped like `[[0], [1], [2], ..., [n-1]]`, - which corresponds to the tensor product noise model without cross-talk, - then the time complexity would be `O(n2^n)`. - If the `mit_pattern` is shaped like `[[0, 1, 2, ..., n-1]]`, - which exactly corresponds to the complete error mitigation, - then the time complexity would be `O(2^(n+n)) = O(4^n)`. - - - * 'least_squares': constrained to have physical probabilities. - Instead of directly applying inverse calibration matrices, - this method solve a constrained optimization problem to find - the closest probability vector to the result from 'pseudo_inverse' method. - Sequential least square quadratic programming (SLSQP) is used - in the internal process. - Every updating step in SLSQP takes `O(m2^{n+t})` time. - Since this method is using the SLSQP optimization over - the vector with lenght `2^n`, the mitigation for 8 bit counts - with the `mit_pattern = [[0], [1], [2], ..., [n-1]]` would - take 10 seconds or more. - - * If `None`, 'least_squares' is used. - - meas_layout (list of int): the mapping from classical registers to qubits - - * If you measure qubit `2` to clbit `0`, `0` to `1`, and `1` to `2`, - the list becomes `[2, 0, 1]` - - * If `None`, flatten(mit_pattern) is used. - - Returns: - dict or Result: The corrected data in the same form as raw_data - - Raises: - QiskitError: if raw_data is not in a one of the defined forms. - """ - from scipy.optimize import minimize - from scipy import linalg as la - - all_states = count_keys(self.nqubits) - num_of_states = 2**self.nqubits - - if meas_layout is None: - meas_layout = [] - for qubits in self._mit_pattern: - meas_layout += qubits - - # check forms of raw_data - if isinstance(raw_data, dict): - # counts dictionary - # convert to list - raw_data2 = [np.zeros(num_of_states, dtype=float)] - for state, count in raw_data.items(): - stateidx = int(state, 2) - raw_data2[0][stateidx] = count - - elif isinstance(raw_data, qiskit.result.result.Result): - - # extract out all the counts, re-call the function with the - # counts and push back into the new result - new_result = deepcopy(raw_data) - - new_counts_list = parallel_map( - self._apply_correction, - [resultidx for resultidx, _ in enumerate(raw_data.results)], - task_args=(raw_data, method, meas_layout), - ) - - for resultidx, new_counts in new_counts_list: - new_result.results[resultidx].data.counts = new_counts - - return new_result - - else: - raise QiskitError("Unrecognized type for raw_data.") - - if method == "pseudo_inverse": - pinv_cal_matrices = [] - for cal_mat in self._cal_matrices: - pinv_cal_matrices.append(la.pinv(cal_mat)) - - meas_layout = meas_layout[::-1] # reverse endian - qubits_to_clbits = [-1 for _ in range(max(meas_layout) + 1)] - for i, qubit in enumerate(meas_layout): - qubits_to_clbits[qubit] = i - - # Apply the correction - for data_idx, _ in enumerate(raw_data2): - - if method == "pseudo_inverse": - for pinv_cal_mat, pos_qubits, indices in zip( - pinv_cal_matrices, self._mit_pattern, self._indices_list - ): - inv_mat_dot_x = np.zeros([num_of_states], dtype=float) - pos_clbits = [qubits_to_clbits[qubit] for qubit in pos_qubits] - for state_idx, state in enumerate(all_states): - first_index = self.compute_index_of_cal_mat(state, pos_clbits, indices) - for i in range(len(pinv_cal_mat)): # i is index of pinv_cal_mat - source_state = self.flip_state(state, i, pos_clbits) - second_index = self.compute_index_of_cal_mat( - source_state, pos_clbits, indices - ) - inv_mat_dot_x[state_idx] += ( - pinv_cal_mat[first_index, second_index] - * raw_data2[data_idx][int(source_state, 2)] - ) - raw_data2[data_idx] = inv_mat_dot_x - - elif method == "least_squares": - - def fun(x): - mat_dot_x = deepcopy(x) - for cal_mat, pos_qubits, indices in zip( - self._cal_matrices, self._mit_pattern, self._indices_list - ): - res_mat_dot_x = np.zeros([num_of_states], dtype=float) - pos_clbits = [qubits_to_clbits[qubit] for qubit in pos_qubits] - for state_idx, state in enumerate(all_states): - second_index = self.compute_index_of_cal_mat(state, pos_clbits, indices) - for i in range(len(cal_mat)): - target_state = self.flip_state(state, i, pos_clbits) - first_index = self.compute_index_of_cal_mat( - target_state, pos_clbits, indices - ) - res_mat_dot_x[int(target_state, 2)] += ( - cal_mat[first_index, second_index] * mat_dot_x[state_idx] - ) - mat_dot_x = res_mat_dot_x - return sum((raw_data2[data_idx] - mat_dot_x) ** 2) - - x0 = np.random.rand(num_of_states) - x0 = x0 / sum(x0) - nshots = sum(raw_data2[data_idx]) - cons = {"type": "eq", "fun": lambda x: nshots - sum(x)} - bnds = tuple((0, nshots) for x in x0) - res = minimize(fun, x0, method="SLSQP", constraints=cons, bounds=bnds, tol=1e-6) - raw_data2[data_idx] = res.x - - else: - raise QiskitError("Unrecognized method.") - - # convert back into a counts dictionary - new_count_dict = {} - for state_idx, state in enumerate(all_states): - if raw_data2[0][state_idx] != 0: - new_count_dict[state] = raw_data2[0][state_idx] - - return new_count_dict - - def flip_state(self, state: str, mat_index: int, flip_poses: List[int]) -> str: - """Flip the state according to the chosen qubit positions""" - flip_poses = [pos for i, pos in enumerate(flip_poses) if (mat_index >> i) & 1] - flip_poses = sorted(flip_poses) - new_state = "" - pos = 0 - for flip_pos in flip_poses: - new_state += state[pos:flip_pos] - new_state += str(int(state[flip_pos], 2) ^ 1) # flip the state - pos = flip_pos + 1 - new_state += state[pos:] - return new_state - - def compute_index_of_cal_mat(self, state: str, pos_qubits: List[int], indices: dict) -> int: - """Return the index of (pseudo inverse) calibration matrix for the input quantum state""" - sub_state = "" - for pos in pos_qubits: - sub_state += state[pos] - return indices[sub_state] - - def _apply_correction( - self, - resultidx, - raw_data, - method, - meas_layout, - ): - """Wrapper to call apply with a counts dictionary.""" - new_counts = self.apply( - raw_data.get_counts(resultidx), method=method, meas_layout=meas_layout - ) - return resultidx, new_counts diff --git a/qiskit/utils/mitigation/circuits.py b/qiskit/utils/mitigation/circuits.py deleted file mode 100644 index d6a6935d7c47..000000000000 --- a/qiskit/utils/mitigation/circuits.py +++ /dev/null @@ -1,253 +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. - -# This code was originally copied from the qiskit-ignis repsoitory see: -# https://github.com/Qiskit/qiskit-ignis/blob/b91066c72171bcd55a70e6e8993b813ec763cf41/qiskit/ignis/mitigation/measurement/circuits.py -# it was migrated to qiskit-terra as qiskit-ignis is being deprecated - -""" -Measurement calibration circuits. To apply the measurement mitigation -use the fitters to produce a filter. -""" -from typing import List, Tuple, Union -from qiskit.utils.deprecation import deprecate_func - - -@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]: - """Deprecated: Return ordered count keys. - - Args: - num_qubits: The number of qubits in the generated list. - Returns: - The strings of all 0/1 combinations of the given number of qubits - Example: - >>> count_keys(3) - ['000', '001', '010', '011', '100', '101', '110', '111'] - """ - return [bin(j)[2:].zfill(num_qubits) for j in range(2**num_qubits)] - - -@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( - qubit_list: List[int] = None, - qr: Union[int, List["QuantumRegister"]] = None, - cr: Union[int, List["ClassicalRegister"]] = None, - circlabel: str = "", -) -> Tuple[List["QuantumCircuit"], List[str]]: - """ - Deprecated: Return a list of measurement calibration circuits for the full - Hilbert space. - - If the circuit contains :math:`n` qubits, then :math:`2^n` calibration circuits - are created, each of which creates a basis state. - - Args: - qubit_list: A list of qubits to perform the measurement correction on. - If `None`, and qr is given then assumed to be performed over the entire - qr. The calibration states will be labelled according to this ordering (default `None`). - - qr: Quantum registers (or their size). - If ``None``, one is created (default ``None``). - - cr: Classical registers (or their size). - If ``None``, one is created(default ``None``). - - circlabel: A string to add to the front of circuit names for - unique identification(default ' '). - - Returns: - A list of QuantumCircuit objects containing the calibration circuits. - - A list of calibration state labels. - - Additional Information: - The returned circuits are named circlabel+cal_XXX - where XXX is the basis state, - e.g., cal_1001. - - Pass the results of these circuits to the CompleteMeasurementFitter - constructor. - - Raises: - QiskitError: if both `qubit_list` and `qr` are `None`. - - """ - # Runtime imports to avoid circular imports causeed by QuantumInstance - # getting initialized by imported utils/__init__ which is imported - # by qiskit.circuit - from qiskit.circuit.quantumregister import QuantumRegister - from qiskit.circuit.classicalregister import ClassicalRegister - from qiskit.circuit.exceptions import QiskitError - - if qubit_list is None and qr is None: - raise QiskitError("Must give one of a qubit_list or a qr") - - # Create the registers if not already done - if qr is None: - qr = QuantumRegister(max(qubit_list) + 1) - - if isinstance(qr, int): - qr = QuantumRegister(qr) - - if qubit_list is None: - qubit_list = range(len(qr)) - - if isinstance(cr, int): - cr = ClassicalRegister(cr) - - nqubits = len(qubit_list) - - # labels for 2**n qubit states - state_labels = count_keys(nqubits) - - cal_circuits, _ = tensored_meas_cal([qubit_list], qr, cr, circlabel) - - return cal_circuits, state_labels - - -@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( - mit_pattern: List[List[int]] = None, - qr: Union[int, List["QuantumRegister"]] = None, - cr: Union[int, List["ClassicalRegister"]] = None, - circlabel: str = "", -) -> Tuple[List["QuantumCircuit"], List[List[int]]]: - """ - Deprecated: Return a list of calibration circuits - - Args: - mit_pattern: Qubits on which to perform the - measurement correction, divided to groups according to tensors. - If `None` and `qr` is given then assumed to be performed over the entire - `qr` as one group (default `None`). - - qr: A quantum register (or its size). - If `None`, one is created (default `None`). - - cr: A classical register (or its size). - If `None`, one is created (default `None`). - - circlabel: A string to add to the front of circuit names for - unique identification (default ' '). - - Returns: - A list of two QuantumCircuit objects containing the calibration circuits - mit_pattern - - Additional Information: - The returned circuits are named circlabel+cal_XXX - where XXX is the basis state, - e.g., cal_000 and cal_111. - - Pass the results of these circuits to the TensoredMeasurementFitter - constructor. - - Raises: - QiskitError: if both `mit_pattern` and `qr` are None. - QiskitError: if a qubit appears more than once in `mit_pattern`. - - """ - # Runtime imports to avoid circular imports causeed by QuantumInstance - # getting initialized by imported utils/__init__ which is imported - # by qiskit.circuit - from qiskit.circuit.quantumregister import QuantumRegister - from qiskit.circuit.classicalregister import ClassicalRegister - from qiskit.circuit.quantumcircuit import QuantumCircuit - from qiskit.circuit.exceptions import QiskitError - - if mit_pattern is None and qr is None: - raise QiskitError("Must give one of mit_pattern or qr") - - if isinstance(qr, int): - qr = QuantumRegister(qr) - - qubits_in_pattern = [] - if mit_pattern is not None: - for qubit_list in mit_pattern: - for qubit in qubit_list: - if qubit in qubits_in_pattern: - raise QiskitError( - "mit_pattern cannot contain multiple instances of the same qubit" - ) - qubits_in_pattern.append(qubit) - - # Create the registers if not already done - if qr is None: - qr = QuantumRegister(max(qubits_in_pattern) + 1) - else: - qubits_in_pattern = range(len(qr)) - mit_pattern = [qubits_in_pattern] - - nqubits = len(qubits_in_pattern) - - # create classical bit registers - if cr is None: - cr = ClassicalRegister(nqubits) - - if isinstance(cr, int): - cr = ClassicalRegister(cr) - - qubits_list_sizes = [len(qubit_list) for qubit_list in mit_pattern] - nqubits = sum(qubits_list_sizes) - size_of_largest_group = max(qubits_list_sizes) - largest_labels = count_keys(size_of_largest_group) - - state_labels = [] - for largest_state in largest_labels: - basis_state = "" - for list_size in qubits_list_sizes: - basis_state = largest_state[:list_size] + basis_state - state_labels.append(basis_state) - - cal_circuits = [] - for basis_state in state_labels: - qc_circuit = QuantumCircuit(qr, cr, name=f"{circlabel}cal_{basis_state}") - - end_index = nqubits - for qubit_list, list_size in zip(mit_pattern, qubits_list_sizes): - - start_index = end_index - list_size - substate = basis_state[start_index:end_index] - - for qind in range(list_size): - if substate[list_size - qind - 1] == "1": - qc_circuit.x(qr[qubit_list[qind]]) - - end_index = start_index - - qc_circuit.barrier(qr) - - # add measurements - end_index = nqubits - for qubit_list, list_size in zip(mit_pattern, qubits_list_sizes): - - for qind in range(list_size): - qc_circuit.measure(qr[qubit_list[qind]], cr[nqubits - (end_index - qind)]) - - end_index -= list_size - - cal_circuits.append(qc_circuit) - - return cal_circuits, mit_pattern diff --git a/qiskit/utils/mitigation/fitters.py b/qiskit/utils/mitigation/fitters.py deleted file mode 100644 index a399b863d102..000000000000 --- a/qiskit/utils/mitigation/fitters.py +++ /dev/null @@ -1,493 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# This code was originally copied from the qiskit-ignis see: -# https://github.com/Qiskit/qiskit-ignis/blob/b91066c72171bcd55a70e6e8993b813ec763cf41/qiskit/ignis/mitigation/measurement/fitters.py -# it was migrated as qiskit-ignis is being deprecated - - -""" -Measurement correction fitters. -""" -from typing import List -import copy -import re - -import numpy as np - -from qiskit import QiskitError -from qiskit.utils.mitigation.circuits import count_keys -from qiskit.utils.mitigation._filters import MeasurementFilter, TensoredFilter -from qiskit.utils.deprecation import deprecate_func - - -class CompleteMeasFitter: - """ - Deprecated: Measurement correction fitter for a full calibration - """ - - @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, - results, - state_labels: List[str], - qubit_list: List[int] = None, - circlabel: str = "", - ): - """ - Initialize a measurement calibration matrix from the results of running - the circuits returned by `measurement_calibration_circuits` - - A wrapper for the tensored fitter - - .. warning:: - - This class is not a public API. The internals are not stable and will - likely change. It is used solely for the - ``measurement_error_mitigation_cls`` kwarg of the - :class:`~qiskit.utils.QuantumInstance` class's constructor (as - a class not an instance). Anything outside of that usage does - not have the normal user-facing API stability. - - Args: - results: the results of running the measurement calibration - circuits. If this is `None` the user will set a calibration - matrix later. - state_labels: list of calibration state labels - returned from `measurement_calibration_circuits`. - The output matrix will obey this ordering. - qubit_list: List of the qubits (for reference and if the - subset is needed). If `None`, the qubit_list will be - created according to the length of state_labels[0]. - circlabel: if the qubits were labeled. - """ - if qubit_list is None: - qubit_list = range(len(state_labels[0])) - self._qubit_list = qubit_list - - self._tens_fitt = TensoredMeasFitter(results, [qubit_list], [state_labels], circlabel) - - @property - def cal_matrix(self): - """Return cal_matrix.""" - return self._tens_fitt.cal_matrices[0] - - @cal_matrix.setter - def cal_matrix(self, new_cal_matrix): - """set cal_matrix.""" - self._tens_fitt.cal_matrices = [copy.deepcopy(new_cal_matrix)] - - @property - def state_labels(self): - """Return state_labels.""" - return self._tens_fitt.substate_labels_list[0] - - @property - def qubit_list(self): - """Return list of qubits.""" - return self._qubit_list - - @state_labels.setter - def state_labels(self, new_state_labels): - """Set state label.""" - self._tens_fitt.substate_labels_list[0] = new_state_labels - - @property - def filter(self): - """Return a measurement filter using the cal matrix.""" - return MeasurementFilter(self.cal_matrix, self.state_labels) - - def add_data(self, new_results, rebuild_cal_matrix=True): - """ - Add measurement calibration data - - Args: - new_results (list or qiskit.result.Result): a single result or list - of result objects. - rebuild_cal_matrix (bool): rebuild the calibration matrix - """ - - self._tens_fitt.add_data(new_results, rebuild_cal_matrix) - - def subset_fitter(self, qubit_sublist): - """ - Return a fitter object that is a subset of the qubits in the original - list. - - Args: - qubit_sublist (list): must be a subset of qubit_list - - Returns: - CompleteMeasFitter: A new fitter that has the calibration for a - subset of qubits - - Raises: - QiskitError: If the calibration matrix is not initialized - """ - - if self._tens_fitt.cal_matrices is None: - raise QiskitError("Calibration matrix is not initialized") - - if qubit_sublist is None: - raise QiskitError("Qubit sublist must be specified") - - for qubit in qubit_sublist: - if qubit not in self._qubit_list: - raise QiskitError("Qubit not in the original set of qubits") - - # build state labels - new_state_labels = count_keys(len(qubit_sublist)) - - # mapping between indices in the state_labels and the qubits in - # the sublist - qubit_sublist_ind = [] - for sqb in qubit_sublist: - for qbind, qubit in enumerate(self._qubit_list): - if qubit == sqb: - qubit_sublist_ind.append(qbind) - - # states in the full calibration which correspond - # to the reduced labels - q_q_mapping = [] - state_labels_reduced = [] - for label in self.state_labels: - tmplabel = [label[index] for index in qubit_sublist_ind] - state_labels_reduced.append("".join(tmplabel)) - - for sub_lab_ind, _ in enumerate(new_state_labels): - q_q_mapping.append([]) - for labelind, label in enumerate(state_labels_reduced): - if label == new_state_labels[sub_lab_ind]: - q_q_mapping[-1].append(labelind) - - new_fitter = CompleteMeasFitter( - results=None, state_labels=new_state_labels, qubit_list=qubit_sublist - ) - - new_cal_matrix = np.zeros([len(new_state_labels), len(new_state_labels)]) - - # do a partial trace - for i in range(len(new_state_labels)): - for j in range(len(new_state_labels)): - - for q_q_i_map in q_q_mapping[i]: - for q_q_j_map in q_q_mapping[j]: - new_cal_matrix[i, j] += self.cal_matrix[q_q_i_map, q_q_j_map] - - new_cal_matrix[i, j] /= len(q_q_mapping[i]) - - new_fitter.cal_matrix = new_cal_matrix - - return new_fitter - - def readout_fidelity(self, label_list=None): - """ - Based on the results, output the readout fidelity which is the - normalized trace of the calibration matrix - - Args: - label_list (bool): If `None`, returns the average assignment fidelity - of a single state. Otherwise it returns the assignment fidelity - to be in any one of these states averaged over the second - index. - - Returns: - numpy.array: readout fidelity (assignment fidelity) - - Additional Information: - The on-diagonal elements of the calibration matrix are the - probabilities of measuring state 'x' given preparation of state - 'x' and so the normalized trace is the average assignment fidelity - """ - return self._tens_fitt.readout_fidelity(0, label_list) - - -class TensoredMeasFitter: - """ - Deprecated: Measurement correction fitter for a tensored calibration. - """ - - @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, - results, - mit_pattern: List[List[int]], - substate_labels_list: List[List[str]] = None, - circlabel: str = "", - ): - """ - Initialize a measurement calibration matrix from the results of running - the circuits returned by `measurement_calibration_circuits`. - - .. warning:: - - This class is not a public API. The internals are not stable and will - likely change. It is used solely for the - ``measurement_error_mitigation_cls`` kwarg of the - :class:`~qiskit.utils.QuantumInstance` class's constructor (as - a class not an instance). Anything outside of that usage does - not have the normal user-facing API stability. - - Args: - results: the results of running the measurement calibration - circuits. If this is `None`, the user will set calibration - matrices later. - - mit_pattern: qubits to perform the - measurement correction on, divided to groups according to - tensors - - substate_labels_list: for each - calibration matrix, the labels of its rows and columns. - If `None`, the labels are ordered lexicographically - - circlabel: if the qubits were labeled - - Raises: - ValueError: if the mit_pattern doesn't match the - substate_labels_list - """ - - self._result_list = [] - self._cal_matrices = None - self._circlabel = circlabel - self._mit_pattern = mit_pattern - - self._qubit_list_sizes = [len(qubit_list) for qubit_list in mit_pattern] - - self._indices_list = [] - if substate_labels_list is None: - self._substate_labels_list = [] - for list_size in self._qubit_list_sizes: - self._substate_labels_list.append(count_keys(list_size)) - else: - self._substate_labels_list = substate_labels_list - if len(self._qubit_list_sizes) != len(substate_labels_list): - raise ValueError("mit_pattern does not match substate_labels_list") - - self._indices_list = [] - for _, sub_labels in enumerate(self._substate_labels_list): - self._indices_list.append({lab: ind for ind, lab in enumerate(sub_labels)}) - - self.add_data(results) - - @property - def cal_matrices(self): - """Return cal_matrices.""" - return self._cal_matrices - - @cal_matrices.setter - def cal_matrices(self, new_cal_matrices): - """Set _cal_matrices.""" - self._cal_matrices = copy.deepcopy(new_cal_matrices) - - @property - def substate_labels_list(self): - """Return _substate_labels_list.""" - return self._substate_labels_list - - @property - def filter(self): - """Return a measurement filter using the cal matrices.""" - return TensoredFilter(self._cal_matrices, self._substate_labels_list, self._mit_pattern) - - @property - def nqubits(self): - """Return _qubit_list_sizes.""" - return sum(self._qubit_list_sizes) - - def add_data(self, new_results, rebuild_cal_matrix=True): - """ - Add measurement calibration data - - Args: - new_results (list or qiskit.result.Result): a single result or list - of Result objects. - rebuild_cal_matrix (bool): rebuild the calibration matrix - """ - - if new_results is None: - return - - if not isinstance(new_results, list): - new_results = [new_results] - - for result in new_results: - self._result_list.append(result) - - if rebuild_cal_matrix: - self._build_calibration_matrices() - - def readout_fidelity(self, cal_index=0, label_list=None): - """ - Based on the results, output the readout fidelity, which is the average - of the diagonal entries in the calibration matrices. - - Args: - cal_index(integer): readout fidelity for this index in _cal_matrices - label_list (list): Returns the average fidelity over of the groups - f states. In the form of a list of lists of states. If `None`, - then each state used in the construction of the calibration - matrices forms a group of size 1 - - Returns: - numpy.array: The readout fidelity (assignment fidelity) - - Raises: - QiskitError: If the calibration matrix has not been set for the - object. - - Additional Information: - The on-diagonal elements of the calibration matrices are the - probabilities of measuring state 'x' given preparation of state - 'x'. - """ - - if self._cal_matrices is None: - raise QiskitError("Cal matrix has not been set") - - if label_list is None: - label_list = [[label] for label in self._substate_labels_list[cal_index]] - - state_labels = self._substate_labels_list[cal_index] - fidelity_label_list = [] - if label_list is None: - fidelity_label_list = [[label] for label in state_labels] - else: - for fid_sublist in label_list: - fidelity_label_list.append([]) - for fid_statelabl in fid_sublist: - for label_idx, label in enumerate(state_labels): - if fid_statelabl == label: - fidelity_label_list[-1].append(label_idx) - continue - - # fidelity_label_list is a 2D list of indices in the - # cal_matrix, we find the assignment fidelity of each - # row and average over the list - assign_fid_list = [] - - for fid_label_sublist in fidelity_label_list: - assign_fid_list.append(0) - for state_idx_i in fid_label_sublist: - for state_idx_j in fid_label_sublist: - assign_fid_list[-1] += self._cal_matrices[cal_index][state_idx_i][state_idx_j] - assign_fid_list[-1] /= len(fid_label_sublist) - - return np.mean(assign_fid_list) - - def _build_calibration_matrices(self): - """ - Build the measurement calibration matrices from the results of running - the circuits returned by `measurement_calibration`. - """ - - # initialize the set of empty calibration matrices - self._cal_matrices = [] - for list_size in self._qubit_list_sizes: - self._cal_matrices.append(np.zeros([2**list_size, 2**list_size], dtype=float)) - - # go through for each calibration experiment - for result in self._result_list: - for experiment in result.results: - circ_name = experiment.header.name - # extract the state from the circuit name - # this was the prepared state - circ_search = re.search("(?<=" + self._circlabel + "cal_)\\w+", circ_name) - - # this experiment is not one of the calcs so skip - if circ_search is None: - continue - - state = circ_search.group(0) - - # get the counts from the result - state_cnts = result.get_counts(circ_name) - for measured_state, counts in state_cnts.items(): - end_index = self.nqubits - for cal_ind, cal_mat in enumerate(self._cal_matrices): - - start_index = end_index - self._qubit_list_sizes[cal_ind] - - substate_index = self._indices_list[cal_ind][state[start_index:end_index]] - measured_substate_index = self._indices_list[cal_ind][ - measured_state[start_index:end_index] - ] - end_index = start_index - - cal_mat[measured_substate_index][substate_index] += counts - - for mat_index, _ in enumerate(self._cal_matrices): - sums_of_columns = np.sum(self._cal_matrices[mat_index], axis=0) - self._cal_matrices[mat_index] = np.divide( - self._cal_matrices[mat_index], - sums_of_columns, - out=np.zeros_like(self._cal_matrices[mat_index]), - where=sums_of_columns != 0, - ) - - def subset_fitter(self, qubit_sublist): - """Return a fitter object that is a subset of the qubits in the original list. - - This is only a partial implementation of the ``subset_fitter`` method since only - mitigation patterns of length 1 are supported. This corresponds to patterns of the - form ``[[0], [1], [2], ...]``. Note however, that such patterns are a good first - approximation to mitigate readout errors on large quantum circuits. - - Args: - qubit_sublist (list): must be a subset of qubit_list - - Returns: - TensoredMeasFitter: A new fitter that has the calibration for a - subset of qubits - - Raises: - QiskitError: If the calibration matrix is not initialized - QiskitError: If the mit pattern is not a tensor of single-qubit - measurement error mitigation. - QiskitError: If a qubit in the given ``qubit_sublist`` is not in the list of - qubits in the mit. pattern. - """ - if self._cal_matrices is None: - raise QiskitError("Calibration matrices are not initialized.") - - if qubit_sublist is None: - raise QiskitError("Qubit sublist must be specified.") - - if not all(len(tensor) == 1 for tensor in self._mit_pattern): - raise QiskitError( - f"Each element in the mit pattern should have length 1. Found {self._mit_pattern}." - ) - - supported_qubits = {tensor[0] for tensor in self._mit_pattern} - for qubit in qubit_sublist: - if qubit not in supported_qubits: - raise QiskitError(f"Qubit {qubit} is not in the mit pattern {self._mit_pattern}.") - - new_mit_pattern = [[idx] for idx in qubit_sublist] - new_substate_labels_list = [self._substate_labels_list[idx] for idx in qubit_sublist] - - new_fitter = TensoredMeasFitter( - results=None, mit_pattern=new_mit_pattern, substate_labels_list=new_substate_labels_list - ) - - new_fitter.cal_matrices = [self._cal_matrices[idx] for idx in qubit_sublist] - - return new_fitter diff --git a/qiskit/utils/name_unnamed_args.py b/qiskit/utils/name_unnamed_args.py deleted file mode 100644 index 4e153dcfefd2..000000000000 --- a/qiskit/utils/name_unnamed_args.py +++ /dev/null @@ -1,73 +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. - -"""Tool to name unnamed arguments.""" - -import functools - - -def name_args(mapping, skip=0): - """Decorator to convert unnamed arguments to named ones. - - Can be used to deprecate old signatures of a function, e.g. - - .. code-block:: - - old_f(a: TypeA, b: TypeB, c: TypeC) - new_f(a: TypeA, d: TypeD, b: TypeB=None, c: TypeC=None) - - Then, to support the old signature this decorator can be used as - - .. code-block:: - - @name_args([ - ('a'), # stays the same - ('d', {TypeB: 'b'}), # if arg is of type TypeB, call if 'b' else 'd' - ('b', {TypeC: 'c'}) - ]) - def new_f(a: TypeA, d: TypeD, b: TypeB=None, c: TypeC=None): - if b is not None: - # raise warning, this is deprecated! - if c is not None: - # raise warning, this is deprecated! - - """ - - def decorator(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - # turn args into kwargs - for arg, replacement in zip(args[skip:], mapping): - default_name = replacement[0] - if len(replacement) == 1: # just renaming, no special cases - if default_name in kwargs: - raise ValueError(f"Name collapse on {default_name}") - kwargs[default_name] = arg - else: - # check if we find a special name - name = None - for special_type, special_name in replacement[1].items(): - if isinstance(arg, special_type): - name = special_name - break - if name is None: - name = default_name - - if name in kwargs: - raise ValueError(f"Name collapse on {default_name}") - kwargs[name] = arg - - return func(*args[:skip], **kwargs) - - return wrapper - - return decorator diff --git a/qiskit/utils/optionals.py b/qiskit/utils/optionals.py index 0321a1cbc7e5..07dc6e4376e5 100644 --- a/qiskit/utils/optionals.py +++ b/qiskit/utils/optionals.py @@ -103,7 +103,7 @@ `__ library as a core dependency, and during the change-over period, it was sometimes convenient to convert things into the Python-only `NetworkX `__ format. Some tests of application modules, such as - `Qiskit Nature `__ still use NetworkX. + `Qiskit Nature `__ still use NetworkX. * - .. py:data:: HAS_NLOPT - `NLopt `__ is a nonlinear optimization library, diff --git a/qiskit/utils/quantum_instance.py b/qiskit/utils/quantum_instance.py deleted file mode 100644 index f27050782fa5..000000000000 --- a/qiskit/utils/quantum_instance.py +++ /dev/null @@ -1,947 +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. - -"""Quantum Instance module""" - -from typing import Optional, List, Union, Dict, Callable, Tuple -from enum import Enum -import copy -import logging -import time -import warnings - -import numpy as np - -from qiskit.qobj import QasmQobj, PulseQobj -from qiskit.utils import circuit_utils -from qiskit.exceptions import QiskitError -from qiskit.utils.backend_utils import ( - is_ibmq_provider, - is_statevector_backend, - is_simulator_backend, - is_local_backend, - is_basicaer_provider, - support_backend_options, - _get_backend_provider, - _get_backend_interface_version, -) -from qiskit.utils.mitigation import ( - CompleteMeasFitter, - TensoredMeasFitter, -) -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -class _MeasFitterType(Enum): - """Meas Fitter Type.""" - - COMPLETE_MEAS_FITTER = 0 - TENSORED_MEAS_FITTER = 1 - - @staticmethod - def type_from_class(meas_class): - """ - Returns fitter type from class - """ - if meas_class == CompleteMeasFitter: - return _MeasFitterType.COMPLETE_MEAS_FITTER - elif meas_class == TensoredMeasFitter: - return _MeasFitterType.TENSORED_MEAS_FITTER - try: - from qiskit.ignis.mitigation.measurement import ( - CompleteMeasFitter as CompleteMeasFitter_IG, - TensoredMeasFitter as TensoredMeasFitter_IG, - ) - except ImportError: - pass - if meas_class == CompleteMeasFitter_IG: - warnings.warn( - "The use of qiskit-ignis for measurement mitigation is " - "deprecated and will be removed in a future release. Instead " - "use the CompleteMeasFitter class from qiskit.utils.mitigation", - DeprecationWarning, - stacklevel=3, - ) - return _MeasFitterType.COMPLETE_MEAS_FITTER - elif meas_class == TensoredMeasFitter_IG: - warnings.warn( - "The use of qiskit-ignis for measurement mitigation is " - "deprecated and will be removed in a future release. Instead " - "use the TensoredMeasFitter class from qiskit.utils.mitigation", - DeprecationWarning, - stacklevel=3, - ) - return _MeasFitterType.TENSORED_MEAS_FITTER - else: - raise QiskitError(f"Unknown fitter {meas_class}") - - @staticmethod - def type_from_instance(meas_instance): - """ - Returns fitter type from instance - """ - if isinstance(meas_instance, CompleteMeasFitter): - return _MeasFitterType.COMPLETE_MEAS_FITTER - elif isinstance(meas_instance, TensoredMeasFitter): - return _MeasFitterType.TENSORED_MEAS_FITTER - try: - from qiskit.ignis.mitigation.measurement import ( - CompleteMeasFitter as CompleteMeasFitter_IG, - TensoredMeasFitter as TensoredMeasFitter_IG, - ) - except ImportError: - pass - if isinstance(meas_instance, CompleteMeasFitter_IG): - warnings.warn( - "The use of qiskit-ignis for measurement mitigation is " - "deprecated and will be removed in a future release. Instead " - "use the CompleteMeasFitter class from qiskit.utils.mitigation", - DeprecationWarning, - stacklevel=3, - ) - return _MeasFitterType.COMPLETE_MEAS_FITTER - elif isinstance(meas_instance, TensoredMeasFitter_IG): - warnings.warn( - "The use of qiskit-ignis for measurement mitigation is " - "deprecated and will be removed in a future release. Instead " - "use the TensoredMeasFitter class from qiskit.utils.mitigation", - DeprecationWarning, - stacklevel=3, - ) - return _MeasFitterType.TENSORED_MEAS_FITTER - else: - raise QiskitError(f"Unknown fitter {meas_instance}") - - -class QuantumInstance: - """Deprecated: Quantum Backend including execution setting.""" - - _BACKEND_CONFIG = ["basis_gates", "coupling_map"] - _COMPILE_CONFIG = ["initial_layout", "seed_transpiler", "optimization_level"] - _RUN_CONFIG = ["shots", "memory", "seed_simulator"] - _QJOB_CONFIG = ["timeout", "wait"] - _NOISE_CONFIG = ["noise_model"] - - # https://github.com/Qiskit/qiskit-aer/blob/master/qiskit/providers/aer/backends/qasm_simulator.py - _BACKEND_OPTIONS_QASM_ONLY = ["statevector_sample_measure_opt", "max_parallel_shots"] - _BACKEND_OPTIONS = [ - "initial_statevector", - "chop_threshold", - "max_parallel_threads", - "max_parallel_experiments", - "statevector_parallel_threshold", - "statevector_hpc_gate_opt", - ] + _BACKEND_OPTIONS_QASM_ONLY - - @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, - backend, - # run config - shots: Optional[int] = None, - seed_simulator: Optional[int] = None, - # backend properties - basis_gates: Optional[List[str]] = None, - coupling_map=None, - # transpile - initial_layout=None, - pass_manager=None, - bound_pass_manager=None, - seed_transpiler: Optional[int] = None, - optimization_level: Optional[int] = None, - # simulation - backend_options: Optional[Dict] = None, - noise_model=None, - # job - timeout: Optional[float] = None, - wait: float = 5.0, - # others - skip_qobj_validation: bool = True, - measurement_error_mitigation_cls: Optional[Callable] = None, - cals_matrix_refresh_period: int = 30, - measurement_error_mitigation_shots: Optional[int] = None, - job_callback: Optional[Callable] = None, - mit_pattern: Optional[List[List[int]]] = None, - max_job_retries: int = 50, - ) -> None: - """ - Quantum Instance holds a Qiskit Terra backend as well as configuration for circuit - transpilation and execution. When provided to an Aqua algorithm the algorithm will - execute the circuits it needs to run using the instance. - - Args: - backend (Backend): Instance of selected backend - shots: Number of repetitions of each circuit, for sampling. If None, the shots are - extracted from the backend. If the backend has none set, the default is 1024. - seed_simulator: Random seed for simulators - basis_gates: List of basis gate names supported by the - target. Defaults to basis gates of the backend. - coupling_map (Optional[Union['CouplingMap', List[List]]]): - Coupling map (perhaps custom) to target in mapping - initial_layout (Optional[Union['Layout', Dict, List]]): - Initial layout of qubits in mapping - pass_manager (Optional['PassManager']): Pass manager to handle how to compile the circuits. - To run only this pass manager and not the ``bound_pass_manager``, call the - :meth:`~qiskit.utils.QuantumInstance.transpile` method with the argument - ``pass_manager=quantum_instance.unbound_pass_manager``. - bound_pass_manager (Optional['PassManager']): A second pass manager to apply on bound - circuits only, that is, circuits without any free parameters. To only run this pass - manager and not ``pass_manager`` call the - :meth:`~qiskit.utils.QuantumInstance.transpile` method with the argument - ``pass_manager=quantum_instance.bound_pass_manager``. - manager should also be run. - seed_transpiler: The random seed for circuit mapper - optimization_level: How much optimization to perform on the circuits. - Higher levels generate more optimized circuits, at the expense of longer - transpilation time. - backend_options: All running options for backend, please refer - to the provider of the backend for information as to what options it supports. - noise_model (Optional['NoiseModel']): noise model for simulator - timeout: Seconds to wait for job. If None, wait indefinitely. - wait: Seconds between queries for job result - skip_qobj_validation: Bypass Qobj validation to decrease circuit - processing time during submission to backend. - measurement_error_mitigation_cls: The approach to mitigate - measurement errors. The classes :class:`~qiskit.utils.mitigation.CompleteMeasFitter` - or :class:`~qiskit.utils.mitigation.TensoredMeasFitter` from the - :mod:`qiskit.utils.mitigation` module can be used here as exact values, not - instances. ``TensoredMeasFitter`` doesn't support the ``subset_fitter`` method. - cals_matrix_refresh_period: How often to refresh the calibration - matrix in measurement mitigation. in minutes - measurement_error_mitigation_shots: The number of shots number for - building calibration matrix. If None, the main `shots` parameter value is used. - job_callback: Optional user supplied callback which can be used - to monitor job progress as jobs are submitted for processing by an Aqua algorithm. - The callback is provided the following arguments: `job_id, job_status, - queue_position, job` - mit_pattern: Qubits on which to perform the TensoredMeasFitter - measurement correction, divided to groups according to tensors. - If `None` and `qr` is given then assumed to be performed over the entire - `qr` as one group (default `None`). - max_job_retries(int): positive non-zero number of trials for the job set (-1 for - infinite trials) (default: 50) - - Raises: - QiskitError: the shots exceeds the maximum number of shots - QiskitError: set noise model but the backend does not support that - QiskitError: set backend_options but the backend does not support that - """ - self._backend = backend - self._backend_interface_version = _get_backend_interface_version(self._backend) - self._pass_manager = pass_manager - self._bound_pass_manager = bound_pass_manager - - # if the shots are none, try to get them from the backend - if shots is None: - from qiskit.providers.backend import Backend # pylint: disable=cyclic-import - - if isinstance(backend, Backend): - if hasattr(backend, "options"): # should always be true for V1 - backend_shots = backend.options.get("shots", 1024) - if shots != backend_shots: - logger.info( - "Overwriting the number of shots in the quantum instance with " - "the settings from the backend." - ) - shots = backend_shots - - # safeguard if shots are still not set - if shots is None: - shots = 1024 - - # pylint: disable=cyclic-import - from qiskit.assembler.run_config import RunConfig - - run_config = RunConfig(shots=shots) - if seed_simulator is not None: - run_config.seed_simulator = seed_simulator - - self._run_config = run_config - - # setup backend config - if self._backend_interface_version <= 1: - basis_gates = basis_gates or backend.configuration().basis_gates - coupling_map = coupling_map or getattr(backend.configuration(), "coupling_map", None) - self._backend_config = {"basis_gates": basis_gates, "coupling_map": coupling_map} - else: - self._backend_config = {} - - # setup compile config - self._compile_config = { - "initial_layout": initial_layout, - "seed_transpiler": seed_transpiler, - "optimization_level": optimization_level, - } - - # setup job config - self._qjob_config = ( - {"timeout": timeout} if self.is_local else {"timeout": timeout, "wait": wait} - ) - - # setup noise config - self._noise_config = {} - if noise_model is not None: - if is_simulator_backend(self._backend) and not is_basicaer_provider(self._backend): - self._noise_config = {"noise_model": noise_model} - else: - raise QiskitError( - "The noise model is not supported " - "on the selected backend {} ({}) " - "only certain backends, such as Aer qasm simulator " - "support noise.".format(self.backend_name, _get_backend_provider(self._backend)) - ) - - # setup backend options for run - self._backend_options = {} - if backend_options is not None: - if support_backend_options(self._backend): - self._backend_options = {"backend_options": backend_options} - else: - raise QiskitError( - "backend_options can not used with the backends in IBMQ provider." - ) - - # setup measurement error mitigation - self._meas_error_mitigation_cls = None - if self.is_statevector: - if measurement_error_mitigation_cls is not None: - raise QiskitError( - "Measurement error mitigation does not work with the statevector simulation." - ) - else: - self._meas_error_mitigation_cls = measurement_error_mitigation_cls - self._meas_error_mitigation_fitters: Dict[str, Tuple[np.ndarray, float]] = {} - # TODO: support different fitting method in error mitigation? - self._meas_error_mitigation_method = "least_squares" - self._cals_matrix_refresh_period = cals_matrix_refresh_period - self._meas_error_mitigation_shots = measurement_error_mitigation_shots - self._mit_pattern = mit_pattern - - if self._meas_error_mitigation_cls is not None: - logger.info( - "The measurement error mitigation is enabled. " - "It will automatically submit an additional job to help " - "calibrate the result of other jobs. " - "The current approach will submit a job with 2^N circuits " - "to build the calibration matrix, " - "where N is the number of measured qubits. " - "Furthermore, Aqua will re-use the calibration matrix for %s minutes " - "and re-build it after that.", - self._cals_matrix_refresh_period, - ) - - # setup others - if is_ibmq_provider(self._backend): - if skip_qobj_validation: - logger.info( - "skip_qobj_validation was set True but this setting is not " - "supported by IBMQ provider and has been ignored." - ) - skip_qobj_validation = False - self._skip_qobj_validation = skip_qobj_validation - self._circuit_summary = False - self._job_callback = job_callback - self._time_taken = 0.0 - self._max_job_retries = max_job_retries - logger.info(self) - - def __str__(self) -> str: - """Overload string. - - Returns: - str: the info of the object. - """ - from qiskit import __version__ as terra_version - - info = f"\nQiskit Terra version: {terra_version}\n" - info += "Backend: '{} ({})', with following setting:\n{}\n{}\n{}\n{}\n{}\n{}".format( - self.backend_name, - _get_backend_provider(self._backend), - self._backend_config, - self._compile_config, - self._run_config, - self._qjob_config, - self._backend_options, - self._noise_config, - ) - - info += f"\nMeasurement mitigation: {self._meas_error_mitigation_cls}" - - return info - - @property - def unbound_pass_manager(self): - """Return the pass manager for designated for unbound circuits. - - Returns: - Optional['PassManager']: The pass manager for unbound circuits, if it has been set. - """ - return self._pass_manager - - @property - def bound_pass_manager(self): - """Return the pass manager for designated for bound circuits. - - Returns: - Optional['PassManager']: The pass manager for bound circuits, if it has been set. - """ - return self._bound_pass_manager - - def transpile(self, circuits, pass_manager=None): - """A wrapper to transpile circuits to allow algorithm access the transpiled circuits. - - Args: - circuits (Union['QuantumCircuit', List['QuantumCircuit']]): circuits to transpile - pass_manager (Optional['PassManager']): A pass manager to transpile the circuits. If - none is given, but either ``pass_manager`` or ``bound_pass_manager`` has been set - in the initializer, these are run. If none has been provided there either, the - backend and compile configs from the initializer are used. - - Returns: - List['QuantumCircuit']: The transpiled circuits, it is always a list even though - the length is one. - """ - # pylint: disable=cyclic-import - from qiskit import compiler - from qiskit.transpiler import PassManager - - # if no pass manager here is given, check if they have been set in the init - if pass_manager is None: - # if they haven't been set in the init, use the transpile args from the init - if self._pass_manager is None and self._bound_pass_manager is None: - transpiled_circuits = compiler.transpile( - circuits, self._backend, **self._backend_config, **self._compile_config - ) - # it they have been set, run them - else: - pass_manager = PassManager() - if self._pass_manager is not None: - pass_manager += self._pass_manager # check if None - if self._bound_pass_manager is not None: - pass_manager += self._bound_pass_manager - - transpiled_circuits = pass_manager.run(circuits) - # custom pass manager set by user - else: - transpiled_circuits = pass_manager.run(circuits) - - if not isinstance(transpiled_circuits, list): - transpiled_circuits = [transpiled_circuits] - - if logger.isEnabledFor(logging.DEBUG) and self._circuit_summary: - logger.debug("==== Before transpiler ====") - logger.debug(circuit_utils.summarize_circuits(circuits)) - if transpiled_circuits is not None: - logger.debug("==== After transpiler ====") - logger.debug(circuit_utils.summarize_circuits(transpiled_circuits)) - - return transpiled_circuits - - def assemble(self, circuits) -> Union[QasmQobj, PulseQobj]: - """assemble circuits""" - # pylint: disable=cyclic-import - from qiskit import compiler - - return compiler.assemble(circuits, **self._run_config.to_dict()) - - def execute(self, circuits, had_transpiled: bool = False): - """ - A wrapper to interface with quantum backend. - - Args: - circuits (Union['QuantumCircuit', List['QuantumCircuit']]): - circuits to execute - had_transpiled: whether or not circuits had been transpiled - - Raises: - QiskitError: Invalid error mitigation fitter class - QiskitError: TensoredMeasFitter class doesn't support subset fitter - MissingOptionalLibraryError: Ignis not installed - - - Returns: - Result: result object - - TODO: Maybe we can combine the circuits for the main ones and calibration circuits before - assembling to the qobj. - """ - from qiskit.utils.run_circuits import run_circuits - from qiskit.utils.measurement_error_mitigation import ( - get_measured_qubits, - build_measurement_error_mitigation_circuits, - ) - - if had_transpiled: - # Convert to a list or make a copy. - # The measurement mitigation logic expects a list and - # may change it in place. This makes sure that logic works - # and any future logic that may change the input. - # It also makes the code easier: it will always deal with a list. - if isinstance(circuits, list): - circuits = circuits.copy() - else: - circuits = [circuits] - else: - # transpile here, the method always returns a copied list - circuits = self.transpile(circuits) - - if self.is_statevector and "aer_simulator_statevector" in self.backend_name: - try: - from qiskit.providers.aer.library import SaveStatevector - - def _find_save_state(data): - for instruction in reversed(data): - if isinstance(instruction.operation, SaveStatevector): - return True - return False - - if isinstance(circuits, list): - for circuit in circuits: - if not _find_save_state(circuit.data): - circuit.save_statevector() - else: - if not _find_save_state(circuits.data): - circuits.save_statevector() - except ImportError: - pass - - if self._meas_error_mitigation_cls is not None: - qubit_index, qubit_mappings = get_measured_qubits(circuits) - mit_pattern = self._mit_pattern - if mit_pattern is None: - mit_pattern = [[i] for i in range(len(qubit_index))] - qubit_index_str = "_".join([str(x) for x in qubit_index]) + "_{}".format( - self._meas_error_mitigation_shots or self._run_config.shots - ) - meas_error_mitigation_fitter, timestamp = self._meas_error_mitigation_fitters.get( - qubit_index_str, (None, 0.0) - ) - - if meas_error_mitigation_fitter is None: - # check the asked qubit_index are the subset of build matrices - for key, _ in self._meas_error_mitigation_fitters.items(): - stored_qubit_index = [int(x) for x in key.split("_")[:-1]] - stored_shots = int(key.split("_")[-1]) - if len(qubit_index) < len(stored_qubit_index): - tmp = list(set(qubit_index + stored_qubit_index)) - if ( - sorted(tmp) == sorted(stored_qubit_index) - and self._run_config.shots == stored_shots - ): - # the qubit used in current job is the subset and shots are the same - ( - meas_error_mitigation_fitter, - timestamp, - ) = self._meas_error_mitigation_fitters.get(key, (None, 0.0)) - meas_error_mitigation_fitter = ( - meas_error_mitigation_fitter.subset_fitter( - qubit_sublist=qubit_index - ) - ) - logger.info( - "The qubits used in the current job is the subset of " - "previous jobs, " - "reusing the calibration matrix if it is not out-of-date." - ) - - build_cals_matrix = ( - self.maybe_refresh_cals_matrix(timestamp) or meas_error_mitigation_fitter is None - ) - - cal_circuits = None - prepended_calibration_circuits: int = 0 - if build_cals_matrix: - logger.info("Updating to also run measurement error mitigation.") - use_different_shots = not ( - self._meas_error_mitigation_shots is None - or self._meas_error_mitigation_shots == self._run_config.shots - ) - temp_run_config = copy.deepcopy(self._run_config) - if use_different_shots: - temp_run_config.shots = self._meas_error_mitigation_shots - ( - cal_circuits, - state_labels, - circuit_labels, - ) = build_measurement_error_mitigation_circuits( - qubit_index, - self._meas_error_mitigation_cls, - self._backend, - self._backend_config, - self._compile_config, - mit_pattern=mit_pattern, - ) - if use_different_shots: - cals_result = run_circuits( - cal_circuits, - self._backend, - qjob_config=self._qjob_config, - backend_options=self._backend_options, - noise_config=self._noise_config, - run_config=self._run_config.to_dict(), - job_callback=self._job_callback, - max_job_retries=self._max_job_retries, - ) - self._time_taken += cals_result.time_taken - result = run_circuits( - circuits, - self._backend, - qjob_config=self.qjob_config, - backend_options=self.backend_options, - noise_config=self._noise_config, - run_config=self.run_config.to_dict(), - job_callback=self._job_callback, - max_job_retries=self._max_job_retries, - ) - self._time_taken += result.time_taken - else: - circuits[0:0] = cal_circuits - prepended_calibration_circuits = len(cal_circuits) - if hasattr(self.run_config, "parameterizations"): - cal_run_config = copy.deepcopy(self.run_config) - cal_run_config.parameterizations[0:0] = [[]] * len(cal_circuits) - else: - cal_run_config = self.run_config - result = run_circuits( - circuits, - self._backend, - qjob_config=self.qjob_config, - backend_options=self.backend_options, - noise_config=self._noise_config, - run_config=cal_run_config.to_dict(), - job_callback=self._job_callback, - max_job_retries=self._max_job_retries, - ) - self._time_taken += result.time_taken - cals_result = result - logger.info("Building calibration matrix for measurement error mitigation.") - meas_type = _MeasFitterType.type_from_class(self._meas_error_mitigation_cls) - if meas_type == _MeasFitterType.COMPLETE_MEAS_FITTER: - meas_error_mitigation_fitter = self._meas_error_mitigation_cls( - cals_result, state_labels, qubit_list=qubit_index, circlabel=circuit_labels - ) - elif meas_type == _MeasFitterType.TENSORED_MEAS_FITTER: - meas_error_mitigation_fitter = self._meas_error_mitigation_cls( - cals_result, mit_pattern=state_labels, circlabel=circuit_labels - ) - self._meas_error_mitigation_fitters[qubit_index_str] = ( - meas_error_mitigation_fitter, - time.time(), - ) - else: - result = run_circuits( - circuits, - self._backend, - qjob_config=self.qjob_config, - backend_options=self.backend_options, - noise_config=self._noise_config, - run_config=self._run_config.to_dict(), - job_callback=self._job_callback, - max_job_retries=self._max_job_retries, - ) - self._time_taken += result.time_taken - - if meas_error_mitigation_fitter is not None: - logger.info("Performing measurement error mitigation.") - if ( - hasattr(self._run_config, "parameterizations") - and len(self._run_config.parameterizations) > 0 - and len(self._run_config.parameterizations[0]) > 0 - and len(self._run_config.parameterizations[0][0]) > 0 - ): - num_circuit_templates = len(self._run_config.parameterizations) - num_param_variations = len(self._run_config.parameterizations[0][0]) - num_circuits = num_circuit_templates * num_param_variations - else: - input_circuits = circuits[prepended_calibration_circuits:] - num_circuits = len(input_circuits) - skip_num_circuits = len(result.results) - num_circuits - # remove the calibration counts from result object to assure the length of - # ExperimentalResult is equal length to input circuits - result.results = result.results[skip_num_circuits:] - tmp_result = copy.deepcopy(result) - for qubit_index_str, c_idx in qubit_mappings.items(): - curr_qubit_index = [int(x) for x in qubit_index_str.split("_")] - tmp_result.results = [result.results[i] for i in c_idx] - if curr_qubit_index == qubit_index: - tmp_fitter = meas_error_mitigation_fitter - elif isinstance(meas_error_mitigation_fitter, TensoredMeasFitter): - # Different from the complete meas. fitter as only the Terra fitter - # implements the ``subset_fitter`` method. - tmp_fitter = meas_error_mitigation_fitter.subset_fitter(curr_qubit_index) - elif _MeasFitterType.COMPLETE_MEAS_FITTER == _MeasFitterType.type_from_instance( - meas_error_mitigation_fitter - ): - tmp_fitter = meas_error_mitigation_fitter.subset_fitter(curr_qubit_index) - else: - raise QiskitError( - "{} doesn't support subset_fitter.".format( - meas_error_mitigation_fitter.__class__.__name__ - ) - ) - tmp_result = tmp_fitter.filter.apply( - tmp_result, self._meas_error_mitigation_method - ) - for i, n in enumerate(c_idx): - # convert counts to integer and remove 0 values - tmp_result.results[i].data.counts = { - k: round(v) - for k, v in tmp_result.results[i].data.counts.items() - if round(v) != 0 - } - result.results[n] = tmp_result.results[i] - - else: - result = run_circuits( - circuits, - self._backend, - qjob_config=self.qjob_config, - backend_options=self.backend_options, - noise_config=self._noise_config, - run_config=self._run_config.to_dict(), - job_callback=self._job_callback, - max_job_retries=self._max_job_retries, - ) - self._time_taken += result.time_taken - - if self._circuit_summary: - self._circuit_summary = False - - return result - - def set_config(self, **kwargs): - """Set configurations for the quantum instance.""" - for k, v in kwargs.items(): - if k in QuantumInstance._RUN_CONFIG: - setattr(self._run_config, k, v) - elif k in QuantumInstance._QJOB_CONFIG: - self._qjob_config[k] = v - elif k in QuantumInstance._COMPILE_CONFIG: - self._compile_config[k] = v - elif k in QuantumInstance._BACKEND_CONFIG: - self._backend_config[k] = v - elif k in QuantumInstance._BACKEND_OPTIONS: - if not support_backend_options(self._backend): - raise QiskitError( - "backend_options can not be used with this backend " - "{} ({}).".format(self.backend_name, _get_backend_provider(self._backend)) - ) - - if k in QuantumInstance._BACKEND_OPTIONS_QASM_ONLY and self.is_statevector: - raise QiskitError( - "'{}' is only applicable for qasm simulator but " - "statevector simulator is used as the backend." - ) - - if "backend_options" not in self._backend_options: - self._backend_options["backend_options"] = {} - self._backend_options["backend_options"][k] = v - elif k in QuantumInstance._NOISE_CONFIG: - if not is_simulator_backend(self._backend) or is_basicaer_provider(self._backend): - raise QiskitError( - "The noise model is not supported on the selected backend {} ({}) " - "only certain backends, such as Aer qasm support " - "noise.".format(self.backend_name, _get_backend_provider(self._backend)) - ) - - self._noise_config[k] = v - - else: - raise ValueError(f"unknown setting for the key ({k}).") - - @property - def time_taken(self) -> float: - """Accumulated time taken for execution.""" - return self._time_taken - - def reset_execution_results(self) -> None: - """Reset execution results""" - self._time_taken = 0.0 - - @property - def qjob_config(self): - """Getter of qjob_config.""" - return self._qjob_config - - @property - def backend_config(self): - """Getter of backend_config.""" - return self._backend_config - - @property - def compile_config(self): - """Getter of compile_config.""" - return self._compile_config - - @property - def run_config(self): - """Getter of run_config.""" - return self._run_config - - @property - def noise_config(self): - """Getter of noise_config.""" - return self._noise_config - - @property - def backend_options(self): - """Getter of backend_options.""" - return self._backend_options - - @property - def circuit_summary(self): - """Getter of circuit summary.""" - return self._circuit_summary - - @circuit_summary.setter - def circuit_summary(self, new_value): - """sets circuit summary""" - self._circuit_summary = new_value - - @property - def max_job_retries(self): - """Getter of max tries""" - return self._max_job_retries - - @max_job_retries.setter - def max_job_retries(self, new_value): - """Sets the maximum tries""" - if not isinstance(new_value, int): - raise TypeError("max_job_retries parameter must be an integer") - if new_value < -1 or new_value == 0: - raise ValueError( - "max_job_retries must either be a positive integer or -1(for infinite trials)" - ) - if new_value == -1: - self._max_job_retries = int(1e18) - else: - self._max_job_retries = new_value - - @property - def measurement_error_mitigation_cls(self): - """returns measurement error mitigation cls""" - return self._meas_error_mitigation_cls - - @measurement_error_mitigation_cls.setter - def measurement_error_mitigation_cls(self, new_value): - """sets measurement error mitigation cls""" - self._meas_error_mitigation_cls = new_value - - @property - def cals_matrix_refresh_period(self): - """returns matrix refresh period""" - return self._cals_matrix_refresh_period - - @cals_matrix_refresh_period.setter - def cals_matrix_refresh_period(self, new_value): - """sets matrix refresh period""" - self._cals_matrix_refresh_period = new_value - - @property - def measurement_error_mitigation_shots(self): - """returns measurement error mitigation shots""" - return self._meas_error_mitigation_shots - - @measurement_error_mitigation_shots.setter - def measurement_error_mitigation_shots(self, new_value): - """sets measurement error mitigation shots""" - self._meas_error_mitigation_shots = new_value - - @property - def backend(self): - """Return Backend backend object.""" - return self._backend - - @property - def backend_name(self): - """Return backend name.""" - if self._backend_interface_version <= 1: - return self._backend.name() - else: - return self._backend.name - - @property - def is_statevector(self): - """Return True if backend is a statevector-type simulator.""" - return is_statevector_backend(self._backend) - - @property - def is_simulator(self): - """Return True if backend is a simulator.""" - return is_simulator_backend(self._backend) - - @property - def is_local(self): - """Return True if backend is a local backend.""" - return is_local_backend(self._backend) - - @property - def skip_qobj_validation(self): - """checks if skip qobj validation""" - return self._skip_qobj_validation - - @skip_qobj_validation.setter - def skip_qobj_validation(self, new_value): - """sets skip qobj validation flag""" - self._skip_qobj_validation = new_value - - def maybe_refresh_cals_matrix(self, timestamp: Optional[float] = None) -> bool: - """ - Calculate the time difference from the query of last time. - - Args: - timestamp: timestamp - - Returns: - Whether or not refresh the cals_matrix - """ - timestamp = timestamp or 0.0 - ret = False - curr_timestamp = time.time() - difference = int(curr_timestamp - timestamp) / 60.0 - if difference > self._cals_matrix_refresh_period: - ret = True - - return ret - - def cals_matrix( - self, qubit_index: Optional[List[int]] = None - ) -> Optional[Union[Tuple[np.ndarray, float], Dict[str, Tuple[np.ndarray, float]]]]: - """ - Get the stored calibration matrices and its timestamp. - - Args: - qubit_index: the qubit index of corresponding calibration matrix. - If None, return all stored calibration matrices. - - Returns: - The calibration matrix and the creation timestamp if qubit_index - is not None otherwise, return all matrices and their timestamp - in a dictionary. - """ - shots = self._meas_error_mitigation_shots or self._run_config.shots - if qubit_index: - qubit_index_str = "_".join([str(x) for x in qubit_index]) + f"_{shots}" - fitter, timestamp = self._meas_error_mitigation_fitters.get(qubit_index_str, None) - if fitter is not None: - return fitter.cal_matrix, timestamp - else: - return { - k: (v.cal_matrix, t) for k, (v, t) in self._meas_error_mitigation_fitters.items() - } - return None diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py deleted file mode 100644 index ce26c5cf5b93..000000000000 --- a/qiskit/utils/run_circuits.py +++ /dev/null @@ -1,413 +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. - -"""run circuits functions""" - -from typing import Optional, Dict, Callable, List, Union, Tuple -import sys -import logging -import time -import copy -import os - -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit.providers import Backend, JobStatus, JobError, Job -from qiskit.providers.jobstatus import JOB_FINAL_STATES -from qiskit.result import Result -from qiskit.utils.deprecation import deprecate_func -from ..exceptions import QiskitError, MissingOptionalLibraryError -from .backend_utils import ( - is_aer_provider, - is_basicaer_provider, - is_simulator_backend, - is_local_backend, - is_ibmq_provider, - _get_backend_interface_version, -) - -MAX_CIRCUITS_PER_JOB = os.environ.get("QISKIT_AQUA_MAX_CIRCUITS_PER_JOB", None) -MAX_GATES_PER_JOB = os.environ.get("QISKIT_AQUA_MAX_GATES_PER_JOB", None) - -logger = logging.getLogger(__name__) - - -@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( - circuit: QuantumCircuit, name: str, qreg: bool = True -) -> Optional[Union[QuantumRegister, ClassicalRegister]]: - """Deprecated: Find the registers in the circuits. - - Args: - circuit: the quantum circuit. - name: name of register - qreg: quantum or classical register - - Returns: - if not found, return None. - - """ - found_reg = None - regs = circuit.qregs if qreg else circuit.cregs - for reg in regs: - if reg.name == name: - found_reg = reg - break - return found_reg - - -def _combine_result_objects(results: List[Result]) -> Result: - """Temporary helper function. - - TODO: This function would be removed after Terra supports job with infinite circuits. - """ - if len(results) == 1: - return results[0] - - new_result = copy.deepcopy(results[0]) - - for idx in range(1, len(results)): - new_result.results.extend(results[idx].results) - - return new_result - - -def _safe_get_job_status(job: Job, job_id: str, max_job_retries: int, wait: float) -> JobStatus: - for _ in range(max_job_retries): - try: - job_status = job.status() - break - except JobError as ex: - logger.warning( - "FAILURE: job id: %s, status: 'FAIL_TO_GET_STATUS' Terra job error: %s", - job_id, - ex, - ) - time.sleep(wait) - except Exception as ex: - raise QiskitError( - f"job id: {job_id}, status: 'FAIL_TO_GET_STATUS' Unknown error: ({ex})" - ) from ex - else: - raise QiskitError(f"Max retry limit reached. Failed to get status for job with id {job_id}") - - return job_status - - -@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( - circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend: Backend, - qjob_config: Dict, - backend_options: Optional[Dict] = None, - noise_config: Optional[Dict] = None, - run_config: Optional[Dict] = None, - job_callback: Optional[Callable] = None, - max_job_retries: int = 50, -) -> Result: - """ - Deprecated: An execution wrapper with Qiskit-Terra, with job auto recover capability. - - The auto-recovery feature is only applied for non-simulator backend. - This wrapper will try to get the result no matter how long it takes. - - Args: - circuits: circuits to execute - backend: backend instance - qjob_config: configuration for quantum job object - backend_options: backend options - noise_config: configuration for noise model - run_config: configuration for run - job_callback: callback used in querying info of the submitted job, and providing the - following arguments: job_id, job_status, queue_position, job. - max_job_retries(int): positive non-zero number of trials for the job set (-1 for infinite - trials) (default: 50) - - Returns: - Result object - - Raises: - QiskitError: Any error except for JobError raised by Qiskit Terra - """ - backend_interface_version = _get_backend_interface_version(backend) - - backend_options = backend_options or {} - noise_config = noise_config or {} - run_config = run_config or {} - if backend_interface_version <= 1: - with_autorecover = not is_simulator_backend(backend) - else: - with_autorecover = False - - if MAX_CIRCUITS_PER_JOB is not None: - max_circuits_per_job = int(MAX_CIRCUITS_PER_JOB) - else: - if backend_interface_version <= 1: - if is_local_backend(backend): - max_circuits_per_job = sys.maxsize - else: - max_circuits_per_job = backend.configuration().max_experiments - else: - if backend.max_circuits is not None: - max_circuits_per_job = backend.max_circuits - else: - max_circuits_per_job = sys.maxsize - - if len(circuits) > max_circuits_per_job: - jobs = [] - job_ids = [] - split_circuits = [] - count = 0 - while count < len(circuits): - some_circuits = circuits[count : count + max_circuits_per_job] - split_circuits.append(some_circuits) - job, job_id = _safe_submit_circuits( - some_circuits, - backend, - qjob_config=qjob_config, - backend_options=backend_options, - noise_config=noise_config, - run_config=run_config, - max_job_retries=max_job_retries, - ) - jobs.append(job) - job_ids.append(job_id) - count += max_circuits_per_job - else: - job, job_id = _safe_submit_circuits( - circuits, - backend, - qjob_config=qjob_config, - backend_options=backend_options, - noise_config=noise_config, - run_config=run_config, - max_job_retries=max_job_retries, - ) - jobs = [job] - job_ids = [job_id] - split_circuits = [circuits] - results = [] - if with_autorecover: - logger.info("Backend status: %s", backend.status()) - logger.info("There are %s jobs are submitted.", len(jobs)) - logger.info("All job ids:\n%s", job_ids) - for idx, _ in enumerate(jobs): - result = None - logger.info("Backend status: %s", backend.status()) - logger.info("There is one jobs are submitted: id: %s", job_id) - job = jobs[idx] - job_id = job_ids[idx] - for _ in range(max_job_retries): - logger.info("Running job id: %s", job_id) - # try to get result if possible - while True: - job_status = _safe_get_job_status( - job, job_id, max_job_retries, qjob_config["wait"] - ) # if the status was broken, an Exception would be raised anyway - queue_position = 0 - if job_status in JOB_FINAL_STATES: - # do callback again after the job is in the final states - if job_callback is not None: - job_callback(job_id, job_status, queue_position, job) - break - if job_status == JobStatus.QUEUED and hasattr(job, "queue_position"): - queue_position = job.queue_position() - logger.info("Job id: %s is queued at position %s", job_id, queue_position) - else: - logger.info("Job id: %s, status: %s", job_id, job_status) - if job_callback is not None: - job_callback(job_id, job_status, queue_position, job) - time.sleep(qjob_config["wait"]) - - # get result after the status is DONE - if job_status == JobStatus.DONE: - for _ in range(max_job_retries): - result = job.result() - if result.success: - results.append(result) - logger.info("COMPLETED the %s-th job, job id: %s", idx, job_id) - break - - logger.warning("FAILURE: Job id: %s", job_id) - logger.warning( - "Job (%s) is completed anyway, retrieve result from backend again.", - job_id, - ) - job = backend.retrieve_job(job_id) - else: - raise QiskitError( - f"Max retry limit reached. Failed to get result for job id {job_id}" - ) - break - # for other cases, resubmit the circuit until the result is available. - # since if there is no result returned, there is no way algorithm can do any process - if job_status == JobStatus.CANCELLED: - logger.warning( - "FAILURE: Job id: %s is cancelled. Re-submit the circuits.", job_id - ) - elif job_status == JobStatus.ERROR: - logger.warning( - "FAILURE: Job id: %s encounters the error. " - "Error is : %s. Re-submit the circuits.", - job_id, - job.error_message(), - ) - else: - logging.warning( - "FAILURE: Job id: %s. Unknown status: %s. Re-submit the circuits.", - job_id, - job_status, - ) - job, job_id = _safe_submit_circuits( - split_circuits[idx], - backend, - qjob_config=qjob_config, - backend_options=backend_options, - noise_config=noise_config, - run_config=run_config, - max_job_retries=max_job_retries, - ) - else: - raise QiskitError( - f"Max retry limit reached. Failed to get result for job with id {job_id} " - ) - else: - results = [] - for job in jobs: - results.append(job.result()) - - result = _combine_result_objects(results) if results else None - # If result was not successful then raise an exception with either the status msg or - # extra information if this was an Aer partial result return - if not result.success: - msg = result.status - if result.status == "PARTIAL COMPLETED": - # Aer can return partial results which Aqua algorithms cannot process and signals - # using partial completed status where each returned result has a success and status. - # We use the status from the first result that was not successful - for res in result.results: - if not res.success: - msg += ", " + res.status - break - raise QiskitError(f"Circuit execution failed: {msg}") - - if not hasattr(result, "time_taken"): - setattr(result, "time_taken", 0.0) - - return result - - -def _safe_submit_circuits( - circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend: Backend, - qjob_config: Dict, - backend_options: Dict, - noise_config: Dict, - run_config: Dict, - max_job_retries: int, -) -> Tuple[Job, str]: - # assure get job ids - for _ in range(max_job_retries): - try: - job = _run_circuits_on_backend( - backend, - circuits, - backend_options=backend_options, - noise_config=noise_config, - run_config=run_config, - ) - job_id = job.job_id() - break - except QiskitError as ex: - failure_warn = True - if is_ibmq_provider(backend): - try: - from qiskit.providers.ibmq import IBMQBackendJobLimitError - except ImportError as ex1: - raise MissingOptionalLibraryError( - libname="qiskit-ibmq-provider", - name="_safe_submit_circuits", - pip_install="pip install qiskit-ibmq-provider", - ) from ex1 - if isinstance(ex, IBMQBackendJobLimitError): - - oldest_running = backend.jobs( - limit=1, descending=False, status=["QUEUED", "VALIDATING", "RUNNING"] - ) - if oldest_running: - oldest_running = oldest_running[0] - logger.warning( - "Job limit reached, waiting for job %s to finish " - "before submitting the next one.", - oldest_running.job_id(), - ) - failure_warn = False # Don't issue a second warning. - try: - oldest_running.wait_for_final_state( - timeout=qjob_config["timeout"], wait=qjob_config["wait"] - ) - except Exception: # pylint: disable=broad-except - # If the wait somehow fails or times out, we'll just re-try - # the job submit and see if it works now. - pass - if failure_warn: - logger.warning( - "FAILURE: Can not get job id, Resubmit the qobj to get job id. " - "Terra job error: %s ", - ex, - ) - except Exception as ex: # pylint: disable=broad-except - logger.warning( - "FAILURE: Can not get job id, Resubmit the qobj to get job id. Error: %s ", ex - ) - else: - raise QiskitError("Max retry limit reached. Failed to submit the qobj correctly") - - return job, job_id - - -def _run_circuits_on_backend( - backend: Backend, - circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend_options: Dict, - noise_config: Dict, - run_config: Dict, -) -> Job: - """Run on backend.""" - run_kwargs = {} - if is_aer_provider(backend) or is_basicaer_provider(backend): - for key, value in backend_options.items(): - if key == "backend_options": - for k, v in value.items(): - run_kwargs[k] = v - else: - run_kwargs[key] = value - else: - run_kwargs.update(backend_options) - - run_kwargs.update(noise_config) - run_kwargs.update(run_config) - - if is_basicaer_provider(backend): - # BasicAer emits warning if option is not in its list - for key in list(run_kwargs.keys()): - if not hasattr(backend.options, key): - del run_kwargs[key] - - return backend.run(circuits, **run_kwargs) diff --git a/qiskit/utils/validation.py b/qiskit/utils/validation.py deleted file mode 100644 index 0d3d59340aae..000000000000 --- a/qiskit/utils/validation.py +++ /dev/null @@ -1,211 +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. - -""" -Validation module -""" - -from typing import Set -from qiskit.utils.deprecation import deprecate_func - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_in_set(name: str, value: object, values: Set[object]) -> None: - """ - Args: - name: value name. - value: value to check. - values: set that should contain value. - Raises: - ValueError: invalid value - """ - if value not in values: - raise ValueError(f"{name} must be one of '{values}', was '{value}'.") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_min(name: str, value: float, minimum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - Raises: - ValueError: invalid value - """ - if value < minimum: - raise ValueError(f"{name} must have value >= {minimum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_min_exclusive(name: str, value: float, minimum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - Raises: - ValueError: invalid value - """ - if value <= minimum: - raise ValueError(f"{name} must have value > {minimum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_max(name: str, value: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value > maximum: - raise ValueError(f"{name} must have value <= {maximum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_max_exclusive(name: str, value: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value >= maximum: - raise ValueError(f"{name} must have value < {maximum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_range(name: str, value: float, minimum: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value < minimum or value > maximum: - raise ValueError(f"{name} must have value >= {minimum} and <= {maximum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_range_exclusive(name: str, value: float, minimum: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value <= minimum or value >= maximum: - raise ValueError(f"{name} must have value > {minimum} and < {maximum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_range_exclusive_min(name: str, value: float, minimum: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value <= minimum or value > maximum: - raise ValueError(f"{name} must have value > {minimum} and <= {maximum}, was {value}") - - -@deprecate_func( - additional_msg=( - "This algorithm utility has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. You can run " - "``pip install qiskit_algorithms`` and import ``from qiskit_algorithms.utils`` instead. " - ), - since="0.45.0", -) -def validate_range_exclusive_max(name: str, value: float, minimum: float, maximum: float) -> None: - """ - Args: - name: value name. - value: value to check. - minimum: minimum value allowed. - maximum: maximum value allowed. - Raises: - ValueError: invalid value - """ - if value < minimum or value >= maximum: - raise ValueError(f"{name} must have value >= {minimum} and < {maximum}, was {value}") diff --git a/qiskit/version.py b/qiskit/version.py index 79cadf189742..b460280f58dd 100644 --- a/qiskit/version.py +++ b/qiskit/version.py @@ -16,9 +16,6 @@ import os import subprocess -from collections.abc import Mapping - -import warnings ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -85,97 +82,3 @@ def get_version_info(): __version__ = get_version_info() - - -class QiskitVersion(Mapping): - """DEPRECATED in 0.25.0 use qiskit.__version__""" - - __slots__ = ["_version_dict", "_loaded"] - - def __init__(self): - self._version_dict = { - "qiskit": __version__, - } - self._loaded = False - - def _load_versions(self): - warnings.warn( - "qiskit.__qiskit_version__ is deprecated since " - "Qiskit Terra 0.25.0, and will be removed 3 months or more later. " - "Instead, you should use qiskit.__version__. The other packages listed in the" - "former qiskit.__qiskit_version__ have their own __version__ module level dunder, " - "as standard in PEP 8.", - category=DeprecationWarning, - stacklevel=3, - ) - try: - # TODO: Update to use qiskit_aer instead when we remove the - # namespace redirect - from qiskit.providers import aer - - self._version_dict["qiskit-aer"] = aer.__version__ - except Exception: - self._version_dict["qiskit-aer"] = None - try: - from qiskit import ignis - - self._version_dict["qiskit-ignis"] = ignis.__version__ - except Exception: - self._version_dict["qiskit-ignis"] = None - try: - from qiskit.providers import ibmq - - self._version_dict["qiskit-ibmq-provider"] = ibmq.__version__ - except Exception: - self._version_dict["qiskit-ibmq-provider"] = None - try: - import qiskit_nature - - self._version_dict["qiskit-nature"] = qiskit_nature.__version__ - except Exception: - self._version_dict["qiskit-nature"] = None - try: - import qiskit_finance - - self._version_dict["qiskit-finance"] = qiskit_finance.__version__ - except Exception: - self._version_dict["qiskit-finance"] = None - try: - import qiskit_optimization - - self._version_dict["qiskit-optimization"] = qiskit_optimization.__version__ - except Exception: - self._version_dict["qiskit-optimization"] = None - try: - import qiskit_machine_learning - - self._version_dict["qiskit-machine-learning"] = qiskit_machine_learning.__version__ - except Exception: - self._version_dict["qiskit-machine-learning"] = None - self._loaded = True - - def __repr__(self): - if not self._loaded: - self._load_versions() - return repr(self._version_dict) - - def __str__(self): - if not self._loaded: - self._load_versions() - return str(self._version_dict) - - def __getitem__(self, key): - if not self._loaded: - self._load_versions() - return self._version_dict[key] - - def __iter__(self): - if not self._loaded: - self._load_versions() - return iter(self._version_dict) - - def __len__(self): - return len(self._version_dict) - - -__qiskit_version__ = QiskitVersion() diff --git a/qiskit/visualization/__init__.py b/qiskit/visualization/__init__.py index 29eb5cf2c0cc..ce824d84adb3 100644 --- a/qiskit/visualization/__init__.py +++ b/qiskit/visualization/__init__.py @@ -222,17 +222,6 @@ pass_manager_drawer -Pulse Visualizations -==================== - -.. autosummary:: - :toctree: ../stubs/ - - pulse_drawer - ~qiskit.visualization.pulse.IQXStandard - ~qiskit.visualization.pulse.IQXSimple - ~qiskit.visualization.pulse.IQXDebugging - Timeline Visualizations ======================= @@ -285,8 +274,6 @@ from .pass_manager_visualization import pass_manager_drawer from .pass_manager_visualization import staged_pass_manager_drawer -from .pulse.interpolation import step_wise, linear, cubic_spline -from .pulse.qcstyle import PulseStyle, SchedStyle from .pulse_v2 import draw as pulse_drawer from .timeline import draw as timeline_drawer diff --git a/qiskit/visualization/pulse/__init__.py b/qiskit/visualization/pulse/__init__.py deleted file mode 100644 index fdfde35ff835..000000000000 --- a/qiskit/visualization/pulse/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Pulse visualization module. -""" - -# These imports are preparing for the renaming of `pulse_v2` back to `pulse` in a future Terra. -from qiskit.visualization.pulse_v2 import * diff --git a/qiskit/visualization/pulse/interpolation.py b/qiskit/visualization/pulse/interpolation.py deleted file mode 100644 index 7fa757b33fe4..000000000000 --- a/qiskit/visualization/pulse/interpolation.py +++ /dev/null @@ -1,123 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# pylint: disable=bad-docstring-quotes - -""" -Deprecated. - -Interpolation module for pulse visualization. -""" -from __future__ import annotations -from functools import partial - -import numpy as np - -from qiskit.utils.deprecation import deprecate_func - - -@deprecate_func( - additional_msg=( - "Instead, use the new interface in ``qiskit.visualization.pulse_drawer`` for " - "pulse visualization." - ), - 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" -) -> tuple[np.ndarray, np.ndarray, np.ndarray]: - """Deprecated. - - Scipy interpolation wrapper. - - Args: - time: Time vector with length of ``samples`` + 1. - samples: Complex pulse envelope. - nop: Number of data points for interpolation. - kind: Scipy interpolation type. - See ``scipy.interpolate.interp1d`` documentation for more information. - Returns: - Interpolated time vector and real and imaginary part of waveform. - """ - from scipy import interpolate - - re_y = np.real(samples) - im_y = np.imag(samples) - - dt = time[1] - time[0] - - time += 0.5 * dt - cs_ry = interpolate.interp1d(time[:-1], re_y, kind=kind, bounds_error=False) - cs_iy = interpolate.interp1d(time[:-1], im_y, kind=kind, bounds_error=False) - - time_ = np.linspace(time[0], time[-1] * dt, nop) - - return time_, cs_ry(time_), cs_iy(time_) - - -@deprecate_func( - additional_msg=( - "Instead, use the new interface in ``qiskit.visualization.pulse_drawer`` for " - "pulse visualization." - ), - 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 -) -> tuple[np.ndarray, np.ndarray, np.ndarray]: - # pylint: disable=unused-argument - """Deprecated. - - Keep uniform variation between sample values. No interpolation is applied. - Args: - time: Time vector with length of ``samples`` + 1. - samples: Complex pulse envelope. - nop: This argument is not used. - Returns: - Time vector and real and imaginary part of waveform. - """ - samples_ = np.repeat(samples, 2) - re_y_ = np.real(samples_) - im_y_ = np.imag(samples_) - time__: np.ndarray = np.concatenate(([time[0]], np.repeat(time[1:-1], 2), [time[-1]])) - return time__, re_y_, im_y_ - - -linear = partial(interp1d, kind="linear") -linear.__doc__ = """Deprecated. - -Apply linear interpolation between sampling points. - -Args: - time: Time vector with length of ``samples`` + 1. - samples: Complex pulse envelope. - nop: Number of data points for interpolation. -Returns: - Interpolated time vector and real and imaginary part of waveform. -""" - -cubic_spline = partial(interp1d, kind="cubic") -cubic_spline.__doc__ = """Deprecated. - -Apply cubic interpolation between sampling points. - -Args: - time: Time vector with length of ``samples`` + 1. - samples: Complex pulse envelope. - nop: Number of data points for interpolation. -Returns: - Interpolated time vector and real and imaginary part of waveform. -""" diff --git a/qiskit/visualization/pulse/matplotlib.py b/qiskit/visualization/pulse/matplotlib.py deleted file mode 100644 index 7ca82af4c2e6..000000000000 --- a/qiskit/visualization/pulse/matplotlib.py +++ /dev/null @@ -1,1022 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# pylint: disable=invalid-name,bad-docstring-quotes - -"""Matplotlib classes for pulse visualization.""" - -from __future__ import annotations -import collections -from collections.abc import Callable, Sequence -from typing import Any - -import numpy as np - -from qiskit.utils import optionals as _optionals -from qiskit.visualization.pulse.qcstyle import PulseStyle, SchedStyle -from qiskit.visualization.pulse.interpolation import step_wise -from qiskit.pulse.channels import ( - DriveChannel, - ControlChannel, - MeasureChannel, - AcquireChannel, - SnapshotChannel, - Channel, -) -from qiskit.pulse import ( - Waveform, - Snapshot, - Play, - Acquire, - PulseError, - ParametricPulse, - SetFrequency, - ShiftPhase, - Instruction, - ShiftFrequency, - SetPhase, -) -from qiskit.pulse.schedule import ScheduleComponent -from qiskit.utils.deprecation import deprecate_func - - -class EventsOutputChannels: - """Pulse dataset for channel.""" - - @deprecate_func( - additional_msg=( - "Instead, use the new interface in ``qiskit.visualization.pulse_drawer`` for " - "pulse visualization." - ), - 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. - - TODO: remove PV - - Args: - t0: starting time of plot - tf: ending time of plot - """ - self.pulses: dict[int, Instruction] = {} - self.t0 = t0 - self.tf = tf - - self._waveform: np.ndarray | None = None - self._framechanges = None - self._setphase = None - self._frequencychanges = None - self._conditionals = None - self._snapshots = None - self._labels = None - self.enable = False - - def add_instruction(self, start_time: int, instruction: Instruction): - """Add new pulse instruction to channel. - - Args: - start_time: Starting time of instruction - instruction: Instruction object to be added - """ - if isinstance(instruction, Play): - pulse = instruction.pulse - else: - pulse = instruction - if start_time in self.pulses: - self.pulses[start_time].append(pulse) - else: - self.pulses[start_time] = [pulse] - - @property - def waveform(self) -> np.ndarray: - """Get waveform.""" - if self._waveform is None: - self._build_waveform() - - return self._waveform[self.t0 : self.tf] - - @property - def framechanges(self) -> dict[int, ShiftPhase]: - """Get frame changes.""" - if self._framechanges is None: - self._build_waveform() - - return self._trim(self._framechanges) - - @property - def setphase(self) -> dict[int, SetPhase]: - """Get the SetPhase phase values.""" - if self._setphase is None: - self._build_waveform() - - return self._trim(self._setphase) - - @property - def frequencychanges(self) -> dict[int, SetFrequency]: - """Get the frequency changes.""" - if self._frequencychanges is None: - self._build_waveform() - - return self._trim(self._frequencychanges) - - @property - def frequencyshift(self) -> dict[int, ShiftFrequency]: - """Set the frequency changes.""" - if self._frequencychanges is None: - self._build_waveform() - - return self._trim(self._frequencychanges) - - @property - def conditionals(self) -> dict[int, str]: - """Get conditionals.""" - if self._conditionals is None: - self._build_waveform() - - return self._trim(self._conditionals) - - @property - def snapshots(self) -> dict[int, Snapshot]: - """Get snapshots.""" - if self._snapshots is None: - self._build_waveform() - - return self._trim(self._snapshots) - - @property - def labels(self) -> dict[int, Waveform | Acquire]: - """Get labels.""" - if self._labels is None: - self._build_waveform() - - return self._trim(self._labels) - - def is_empty(self) -> bool: - """Return if pulse is empty. - - Returns: - bool: if the channel has nothing to plot - """ - if ( - any(self.waveform) - or self.framechanges - or self.setphase - or self.conditionals - or self.snapshots - ): - return False - - return True - - def to_table(self, name: str) -> list[tuple[int, str, str]]: - """Get table contains. - - Args: - name (str): name of channel - - Returns: - A list of events in the channel - """ - time_event = [] - - framechanges = self.framechanges - setphase = self.setphase - conditionals = self.conditionals - snapshots = self.snapshots - frequencychanges = self.frequencychanges - - for key, val in framechanges.items(): - data_str = "shift phase: %.2f" % val - time_event.append((key, name, data_str)) - for key, val in setphase.items(): - data_str = "set phase: %.2f" % val - time_event.append((key, name, data_str)) - for key, val in conditionals.items(): - data_str = "conditional, %s" % val - time_event.append((key, name, data_str)) - for key, val in snapshots.items(): - data_str = "snapshot: %s" % val - time_event.append((key, name, data_str)) - for key, val in frequencychanges.items(): - data_str = "frequency: %.4e" % val - time_event.append((key, name, data_str)) - - return time_event - - def _build_waveform(self): - """Create waveform from stored pulses.""" - self._framechanges = {} - self._setphase = {} - self._frequencychanges = {} - self._conditionals = {} - self._snapshots = {} - self._labels = {} - fc = 0 - pv = np.zeros(self.tf + 1, dtype=np.complex128) - wf = np.zeros(self.tf + 1, dtype=np.complex128) - for time, commands in sorted(self.pulses.items()): - if time > self.tf: - break - tmp_fc = 0 - tmp_set_phase = 0 - tmp_sf = None - for command in commands: - if isinstance(command, ShiftPhase): - tmp_fc += command.phase - pv[time:] = 0 - elif isinstance(command, SetPhase): - tmp_set_phase = command.phase - pv[time:] = 0 - elif isinstance(command, SetFrequency): - tmp_sf = command.frequency - elif isinstance(command, ShiftFrequency): - tmp_sf = command.frequency - elif isinstance(command, Snapshot): - self._snapshots[time] = command.name - if tmp_fc != 0: - self._framechanges[time] = tmp_fc - fc += tmp_fc - if tmp_set_phase != 0: - self._setphase[time] = tmp_set_phase - fc = tmp_set_phase - if tmp_sf is not None: - self._frequencychanges[time] = tmp_sf - - for command in commands: - duration = command.duration - tf = min(time + duration, self.tf) - if isinstance(command, ParametricPulse): - command = command.get_waveform() - if isinstance(command, Waveform): - wf[time:tf] = np.exp(1j * fc) * command.samples[: tf - time] - pv[time:] = 0 - self._labels[time] = (tf, command) - - elif isinstance(command, Acquire): - wf[time:tf] = np.ones(tf - time) - self._labels[time] = (tf, command) - self._waveform = wf + pv - - def _trim(self, events: dict[int, Any]) -> dict[int, Any]: - """Return events during given `time_range`. - - Args: - events: time and operation of events. - - Returns: - Events within the specified time range. - """ - events_in_time_range = {} - - for k, v in events.items(): - if self.t0 <= k <= self.tf: - events_in_time_range[k] = v - - return events_in_time_range - - -class WaveformDrawer: - """A class to create figure for sample pulse.""" - - @deprecate_func( - additional_msg=( - "Instead, use the new interface in ``qiskit.visualization.pulse_drawer`` for " - "pulse visualization." - ), - 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. - - Args: - style: Style sheet for pulse visualization. - """ - self.style = style or PulseStyle() - - @_optionals.HAS_MATPLOTLIB.require_in_call("waveform drawer") - def draw( - self, - pulse: Waveform, - dt: float = 1.0, - interp_method: Callable = None, - scale: float = 1, - draw_title: bool = False, - ): - """Draw figure. - - Args: - pulse: Waveform to draw. - dt: time interval. - interp_method: interpolation function. - scale: Relative visual scaling of waveform amplitudes. - draw_title: Add a title to the plot when set to ``True``. - - Returns: - matplotlib.figure.Figure: A matplotlib figure object of the pulse envelope. - - Raises: - MissingOptionalLibraryError: If matplotlib is not installed - """ - from matplotlib import pyplot as plt - - # If these self.style.dpi or self.style.figsize are None, they will - # revert back to their default rcParam keys. - figure = plt.figure(dpi=self.style.dpi, figsize=self.style.figsize) - - interp_method = interp_method or step_wise - - ax = figure.add_subplot(111) - ax.set_facecolor(self.style.bg_color) - - samples = pulse.samples - time = np.arange(0, len(samples) + 1, dtype=float) * dt - - time, re, im = interp_method(time, samples, self.style.num_points) - - # plot - ax.fill_between( - x=time, - y1=re, - y2=np.zeros_like(time), - facecolor=self.style.wave_color[0], - alpha=0.3, - edgecolor=self.style.wave_color[0], - linewidth=1.5, - label="real part", - ) - ax.fill_between( - x=time, - y1=im, - y2=np.zeros_like(time), - facecolor=self.style.wave_color[1], - alpha=0.3, - edgecolor=self.style.wave_color[1], - linewidth=1.5, - label="imaginary part", - ) - - ax.set_xlim(0, pulse.duration * dt) - if scale: - ax.set_ylim(-1 / scale, 1 / scale) - else: - v_max = max(max(np.abs(re)), max(np.abs(im))) - ax.set_ylim(-1.2 * v_max, 1.2 * v_max) - - bbox = ax.get_position() - - # This check is here for backwards compatibility. Before, the check was around - # the suptitle line, however since the font style can take on a type of None - # we need to unfortunately check both the type and the value of the object. - if isinstance(self.style.title_font_size, int) and self.style.title_font_size > 0: - if draw_title: - figure.suptitle( - pulse.name, fontsize=self.style.title_font_size, y=bbox.y1 + 0.02, va="bottom" - ) - - return figure - - -@_optionals.HAS_MATPLOTLIB.require_in_instance -class ScheduleDrawer: - """A class to create figure for schedule and channel.""" - - @deprecate_func( - additional_msg=( - "Instead, use the new interface in ``qiskit.visualization.pulse_drawer`` for " - "pulse visualization." - ), - 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. - - Args: - style: Style sheet for pulse schedule visualization. - Raises: - MissingOptionalLibraryError: If matplotlib is not installed - """ - from matplotlib import pyplot as plt - from matplotlib import gridspec - - self.plt_mod = plt - self.gridspec_mod = gridspec - self.style = style or SchedStyle() - - def _build_channels( - self, - schedule: ScheduleComponent, - channels: list[Channel], - t0: int, - tf: int, - show_framechange_channels: bool = True, - ) -> tuple[ - dict[Channel, EventsOutputChannels], - dict[Channel, EventsOutputChannels], - dict[Channel, EventsOutputChannels], - ]: - """Create event table of each pulse channels in the given schedule. - - Args: - schedule: Schedule object to plot. - channels: Channels to plot. - t0: Start time of plot. - tf: End time of plot. - show_framechange_channels: Plot channels only with FrameChanges (ShiftPhase). - - Returns: - channels: All channels. - output_channels: All (D, M, U, A) channels. - snapshot_channels: Snapshots. - """ - # prepare waveform channels - drive_channels: dict[Channel, EventsOutputChannels] = collections.OrderedDict() - measure_channels: dict[Channel, EventsOutputChannels] = collections.OrderedDict() - control_channels: dict[Channel, EventsOutputChannels] = collections.OrderedDict() - acquire_channels: dict[Channel, EventsOutputChannels] = collections.OrderedDict() - snapshot_channels: dict[Channel, EventsOutputChannels] = collections.OrderedDict() - _channels: set[Channel] = set() - if show_framechange_channels: - _channels.update(schedule.channels) - # take channels that do not only contain framechanges - else: - for start_time, instruction in schedule.instructions: - if not isinstance(instruction, (ShiftPhase, SetPhase)): - _channels.update(instruction.channels) - - _channels.update(channels) - for chan in _channels: - if isinstance(chan, DriveChannel): - try: - drive_channels[chan] = EventsOutputChannels(t0, tf) - except PulseError: - pass - elif isinstance(chan, MeasureChannel): - try: - measure_channels[chan] = EventsOutputChannels(t0, tf) - except PulseError: - pass - elif isinstance(chan, ControlChannel): - try: - control_channels[chan] = EventsOutputChannels(t0, tf) - except PulseError: - pass - elif isinstance(chan, AcquireChannel): - try: - acquire_channels[chan] = EventsOutputChannels(t0, tf) - except PulseError: - pass - elif isinstance(chan, SnapshotChannel): - try: - snapshot_channels[chan] = EventsOutputChannels(t0, tf) - except PulseError: - pass - - output_channels = { - **drive_channels, - **measure_channels, - **control_channels, - **acquire_channels, - } - channels = {**output_channels, **snapshot_channels} - # sort by index then name to group qubits together. - output_channels = collections.OrderedDict( - sorted(output_channels.items(), key=lambda x: (x[0].index, x[0].name)) - ) - channels = collections.OrderedDict( - sorted(channels.items(), key=lambda x: (x[0].index, x[0].name)) - ) - - for start_time, instruction in schedule.instructions: - for channel in instruction.channels: - if channel in output_channels: - output_channels[channel].add_instruction(start_time, instruction) - elif channel in snapshot_channels: - snapshot_channels[channel].add_instruction(start_time, instruction) - return channels, output_channels, snapshot_channels - - @staticmethod - def _scale_channels( - output_channels: dict[Channel, EventsOutputChannels], - scale: float, - channel_scales: dict[Channel, float] = None, - channels: list[Channel] = None, - plot_all: bool = False, - ) -> dict[Channel, float]: - """Count number of channels that contains any instruction to show - and find scale factor of that channel. - - Args: - output_channels: Event table of channels to show. - scale: Global scale factor. - channel_scales: Channel specific scale factors. - channels: Specified channels to plot. - plot_all: Plot empty channel. - - Returns: - scale_dict: Scale factor of each channel. - """ - # count numbers of valid waveform - scale_dict: dict[Channel, float] = {chan: 0.0 for chan in output_channels.keys()} - for channel, events in output_channels.items(): - v_max = 0 - if channels: - if channel in channels: - waveform = events.waveform - v_max = max( - v_max, max(np.abs(np.real(waveform))), max(np.abs(np.imag(waveform))) - ) - events.enable = True - else: - if not events.is_empty() or plot_all: - waveform = events.waveform - v_max = max( - v_max, max(np.abs(np.real(waveform))), max(np.abs(np.imag(waveform))) - ) - events.enable = True - - scale_val = channel_scales.get(channel, scale) - if not scale_val: - # when input schedule is empty or comprises only frame changes, - # we need to overwrite maximum amplitude by a value greater than zero, - # otherwise auto axis scaling will fail with zero division. - v_max = v_max or 1 - scale_dict[channel] = 1 / v_max - else: - scale_dict[channel] = scale_val - - return scale_dict - - def _draw_table(self, figure, channels: dict[Channel, EventsOutputChannels], dt: float): - """Draw event table if events exist. - - Args: - figure (matplotlib.figure.Figure): Figure object - channels: Dictionary of channel and event table - dt: Time interval - - Returns: - tuple[matplotlib.axes.Axes]: Axis objects for table and canvas of pulses. - """ - # create table - table_data = [] - if self.style.use_table: - for channel, events in channels.items(): - if events.enable: - table_data.extend(events.to_table(channel.name)) - table_data = sorted(table_data, key=lambda x: x[0]) - - # plot table - if table_data: - # table area size - ncols = self.style.table_columns - nrows = int(np.ceil(len(table_data) / ncols)) - max_size = self.style.max_table_ratio * figure.get_size_inches()[1] - max_rows = np.floor(max_size / self.style.fig_unit_h_table / ncols) - nrows = int(min(nrows, max_rows)) - # don't overflow plot with table data - table_data = table_data[: int(nrows * ncols)] - # fig size - h_table = nrows * self.style.fig_unit_h_table - h_waves = figure.get_size_inches()[1] - h_table - - # create subplots - gs = self.gridspec_mod.GridSpec(2, 1, height_ratios=[h_table, h_waves], hspace=0) - tb = self.plt_mod.subplot(gs[0]) - ax = self.plt_mod.subplot(gs[1]) - - # configure each cell - tb.axis("off") - cell_value = [["" for _kk in range(ncols * 3)] for _jj in range(nrows)] - cell_color = [self.style.table_color * ncols for _jj in range(nrows)] - cell_width = [*([0.2, 0.2, 0.5] * ncols)] - for ii, data in enumerate(table_data): - # pylint: disable=unbalanced-tuple-unpacking - r, c = np.unravel_index(ii, (nrows, ncols), order="f") - # pylint: enable=unbalanced-tuple-unpacking - time, ch_name, data_str = data - # item - cell_value[r][3 * c + 0] = "t = %s" % (time * dt) - cell_value[r][3 * c + 1] = "ch %s" % ch_name - cell_value[r][3 * c + 2] = data_str - table = tb.table( - cellText=cell_value, - cellLoc="left", - rowLoc="center", - colWidths=cell_width, - bbox=[0, 0, 1, 1], - cellColours=cell_color, - ) - table.auto_set_font_size(False) - table.set_fontsize = self.style.table_font_size - else: - tb = None - ax = figure.add_subplot(111) - - return tb, ax - - @staticmethod - def _draw_snapshots( - ax, snapshot_channels: dict[Channel, EventsOutputChannels], y0: float - ) -> None: - """Draw snapshots to given mpl axis. - - Args: - ax (matplotlib.axes.Axes): axis object to draw snapshots. - snapshot_channels: Event table of snapshots. - y0: vertical position to draw the snapshots. - """ - for events in snapshot_channels.values(): - snapshots = events.snapshots - if snapshots: - for time in snapshots: - ax.annotate( - s="\u25D8", - xy=(time, y0), - xytext=(time, y0 + 0.08), - arrowprops={"arrowstyle": "wedge"}, - ha="center", - ) - - def _draw_framechanges(self, ax, fcs: dict[int, ShiftPhase], y0: float) -> None: - """Draw frame change of given channel to given mpl axis. - - Args: - ax (matplotlib.axes.Axes): axis object to draw frame changes. - fcs: Event table of frame changes. - y0: vertical position to draw the frame changes. - """ - for time in fcs.keys(): - ax.text( - x=time, - y=y0, - s=r"$\circlearrowleft$", - fontsize=self.style.icon_font_size, - ha="center", - va="center", - ) - - def _draw_frequency_changes(self, ax, sf: dict[int, SetFrequency], y0: float) -> None: - """Draw set frequency of given channel to given mpl axis. - - Args: - ax (matplotlib.axes.Axes): axis object to draw frame changes. - sf: Event table of set frequency. - y0: vertical position to draw the frame changes. - """ - for time in sf.keys(): - ax.text( - x=time, - y=y0, - s=r"$\leftrightsquigarrow$", - fontsize=self.style.icon_font_size, - ha="center", - va="center", - rotation=90, - ) - - def _get_channel_color(self, channel: Channel) -> str: - """Lookup table for waveform color. - - Args: - channel: Type of channel. - - Return: - Color code or name of color. - """ - # choose color - if isinstance(channel, DriveChannel): - color = self.style.d_ch_color - elif isinstance(channel, ControlChannel): - color = self.style.u_ch_color - elif isinstance(channel, MeasureChannel): - color = self.style.m_ch_color - elif isinstance(channel, AcquireChannel): - color = self.style.a_ch_color - else: - color = "black" - return color - - @staticmethod - def _prev_label_at_time(prev_labels: list[dict[int, Waveform | Acquire]], time: int) -> bool: - """Check overlap of pulses with previous channels. - - Args: - prev_labels: List of labels in previous channels. - time: Start time of current pulse instruction. - - Returns: - `True` if current instruction overlaps with others. - """ - for labels in prev_labels: - for t0, (tf, _) in labels.items(): - if time in (t0, tf): - return True - return False - - def _draw_labels( - self, - ax, - labels: dict[int, Waveform | Acquire], - prev_labels: list[dict[int, Waveform | Acquire]], - y0: float, - ) -> None: - """Draw label of pulse instructions on given mpl axis. - - Args: - ax (matplotlib.axes.Axes): axis object to draw labels. - labels: Pulse labels of channel. - prev_labels: Pulse labels of previous channels. - y0: vertical position to draw the labels. - """ - for t0, (tf, cmd) in labels.items(): - if isinstance(cmd, Acquire): - name = cmd.name if cmd.name else "acquire" - else: - name = cmd.name - - ax.annotate( - r"%s" % name, - xy=((t0 + tf) // 2, y0), - xytext=((t0 + tf) // 2, y0 - 0.07), - fontsize=self.style.label_font_size, - ha="center", - va="center", - ) - - linestyle = self.style.label_ch_linestyle - alpha = self.style.label_ch_alpha - color = self.style.label_ch_color - - if not self._prev_label_at_time(prev_labels, t0): - ax.axvline(t0, -1, 1, color=color, linestyle=linestyle, alpha=alpha) - if not (self._prev_label_at_time(prev_labels, tf) or tf in labels): - ax.axvline(tf, -1, 1, color=color, linestyle=linestyle, alpha=alpha) - - def _draw_channels( - self, - ax, - output_channels: dict[Channel, EventsOutputChannels], - interp_method: Callable, - t0: int, - tf: int, - scale_dict: dict[Channel, float], - label: bool = False, - framechange: bool = True, - frequencychange: bool = True, - ) -> float: - """Draw pulse instructions on given mpl axis. - - Args: - ax (matplotlib.axes.Axes): axis object to draw pulses. - output_channels: Event table of channels. - interp_method: Callback function for waveform interpolation. - t0: Start time of schedule. - tf: End time of schedule. - scale_dict: Scale factor for each channel. - label: When set `True` draw labels. - framechange: When set `True` draw frame change symbols. - frequencychange: When set `True` draw frequency change symbols. - - Return: - Value of final vertical axis of canvas. - """ - y0 = 0 - prev_labels: list[dict[int, Waveform | Acquire]] = [] - for channel, events in output_channels.items(): - if events.enable: - # scaling value of this channel - scale = 0.5 * scale_dict.get(channel, 0.5) - # plot waveform - waveform = events.waveform - time = np.arange(t0, tf + 1, dtype=float) - if waveform.any(): - time, re, im = interp_method(time, waveform, self.style.num_points) - else: - # when input schedule is empty or comprises only frame changes, - # we should avoid interpolation due to lack of data points. - # instead, it just returns vector of zero. - re, im = np.zeros_like(time), np.zeros_like(time) - color = self._get_channel_color(channel) - # Minimum amplitude scaled - amp_min = scale * abs(min(0, np.nanmin(re), np.nanmin(im))) - # scaling and offset - re = scale * re + y0 - im = scale * im + y0 - offset = np.zeros_like(time) + y0 - # plot - ax.fill_between( - x=time, - y1=re, - y2=offset, - facecolor=color[0], - alpha=0.3, - edgecolor=color[0], - linewidth=1.5, - label="real part", - ) - ax.fill_between( - x=time, - y1=im, - y2=offset, - facecolor=color[1], - alpha=0.3, - edgecolor=color[1], - linewidth=1.5, - label="imaginary part", - ) - ax.plot((t0, tf), (y0, y0), color="#000000", linewidth=1.0) - - # plot frame changes - fcs = events.framechanges - if fcs and framechange: - self._draw_framechanges(ax, fcs, y0) - # plot frequency changes - sf = events.frequencychanges - if sf and frequencychange: - self._draw_frequency_changes(ax, sf, y0 + 0.05) - # plot labels - labels = events.labels - if labels and label: - self._draw_labels(ax, labels, prev_labels, y0) - prev_labels.append(labels) - - else: - continue - - # plot label - ax.text( - x=t0, - y=y0, - s=channel.name, - fontsize=self.style.axis_font_size, - ha="right", - va="center", - ) - # show scaling factor - ax.text( - x=t0, - y=y0 - 0.1, - s="x%.1f" % (2 * scale), - fontsize=0.7 * self.style.axis_font_size, - ha="right", - va="top", - ) - - # change the y0 offset for removing spacing when a channel has negative values - if self.style.remove_spacing: - y0 -= 0.5 + amp_min - else: - y0 -= 1 - return y0 - - def draw( - self, - schedule: ScheduleComponent, - dt: float, - interp_method: Callable, - plot_range: tuple[float, float], - scale: float | None = None, - channel_scales: dict[Channel, float] | None = None, - plot_all: bool = True, - table: bool = False, - label: bool = False, - framechange: bool = True, - channels: Sequence[Channel] | None = None, - show_framechange_channels: bool = True, - draw_title: bool = False, - ): - """Draw figure. - - Args: - schedule: schedule object to plot. - dt: Time interval of samples. Pulses are visualized in the unit of - cycle time if not provided. - interp_method: Interpolation function. See example. - Interpolation is disabled in default. - See `qiskit.visualization.pulse.interpolation` for more information. - plot_range: A tuple of time range to plot. - scale: Scaling of waveform amplitude. Pulses are automatically - scaled channel by channel if not provided. - channel_scales: Dictionary of scale factor for specific channels. - Scale of channels not specified here is overwritten by `scale`. - plot_all: When set `True` plot empty channels. - table: When set `True` draw event table for supported commands. - label: When set `True` draw label for individual instructions. - framechange: When set `True` draw framechange indicators. - channels: A list of channel names to plot. - All non-empty channels are shown if not provided. - show_framechange_channels: When set `True` plot channels - with only framechange instructions. - draw_title: Add a title to the plot when set to ``True``. - - Returns: - matplotlib.figure.Figure: A matplotlib figure object for the pulse envelope. - - Raises: - VisualizationError: When schedule cannot be drawn - """ - figure = self.plt_mod.figure(dpi=self.style.dpi, figsize=self.style.figsize) - - if channels is None: - channels = [] - interp_method = interp_method or step_wise - - if channel_scales is None: - channel_scales = {} - - # setup plot range - if plot_range: - t0 = int(np.floor(plot_range[0])) - tf = int(np.floor(plot_range[1])) - else: - t0 = 0 - # when input schedule is empty or comprises only frame changes, - # we need to overwrite pulse duration by an integer greater than zero, - # otherwise waveform returns empty array and matplotlib will be crashed. - if channels: - tf = schedule.ch_duration(*channels) - else: - tf = schedule.stop_time - tf = tf or 1 - - # prepare waveform channels - (schedule_channels, output_channels, snapshot_channels) = self._build_channels( - schedule, channels, t0, tf, show_framechange_channels - ) - - # count numbers of valid waveform - scale_dict = self._scale_channels( - output_channels, - scale=scale, - channel_scales=channel_scales, - channels=channels, - plot_all=plot_all, - ) - - if table: - tb, ax = self._draw_table(figure, schedule_channels, dt) - else: - tb = None - ax = figure.add_subplot(111) - - ax.set_facecolor(self.style.bg_color) - - y0 = self._draw_channels( - ax, - output_channels, - interp_method, - t0, - tf, - scale_dict, - label=label, - framechange=framechange, - ) - - y_ub = 0.5 + self.style.vertical_span - y_lb = y0 + 0.5 - self.style.vertical_span - - self._draw_snapshots(ax, snapshot_channels, y_lb) - - ax.set_xlim(t0, tf) - tick_labels = np.linspace(t0, tf, 5) - ax.set_xticks(tick_labels) - ax.set_xticklabels( - [self.style.axis_formatter % label for label in tick_labels * dt], - fontsize=self.style.axis_font_size, - ) - ax.set_ylim(y_lb, y_ub) - ax.set_yticklabels([]) - - if tb is not None: - bbox = tb.get_position() - else: - bbox = ax.get_position() - - # This check is here for backwards compatibility. Before, the check was around - # the suptitle line, however since the font style can take on a type of None - # we need to unfortunately check both the type and the value of the object. - if isinstance(self.style.title_font_size, int) and self.style.title_font_size > 0: - if draw_title: - figure.suptitle( - schedule.name, - fontsize=self.style.title_font_size, - y=bbox.y1 + 0.02, - va="bottom", - ) - return figure diff --git a/qiskit/visualization/pulse/qcstyle.py b/qiskit/visualization/pulse/qcstyle.py deleted file mode 100644 index e466774e895d..000000000000 --- a/qiskit/visualization/pulse/qcstyle.py +++ /dev/null @@ -1,226 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Deprecated. - -Style sheets for pulse visualization. -""" -from __future__ import annotations -import logging -from collections import namedtuple - -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) -ComplexColors = namedtuple("ComplexColors", ["real", "imaginary"]) -SchedTableColors = namedtuple("SchedTableColors", ["time", "channel", "event"]) - - -class SchedStyle: - """Style sheet for Qiskit-Pulse schedule drawer.""" - - @deprecate_func( - additional_msg=( - "In addition to this stylesheet being deprecated, the legacy pulse drawer is too. " - "Instead, use the new drawer ``qiskit.visualization.pulse_drawer`` " - "with new stylesheet classes provided by ``qiskit.visualization.pulse_v2``. " - "You can choose one of ``IQXStandard``, ``IQXSimple``, ``IQXDebugging``." - ), - since="0.23.0", - package_name="qiskit-terra", - ) - def __init__( - self, - figsize: tuple[float, float] | None = (10.0, 12.0), - fig_unit_h_table: float = 0.4, - use_table: bool = True, - table_columns: int = 2, - table_font_size: int = 10, - axis_font_size: int = 18, - label_font_size: int = 10, - icon_font_size: int = 18, - title_font_size: int | None = 25, - label_ch_linestyle: str = "--", - label_ch_color: str = "#222222", - label_ch_alpha: float = 0.3, - d_ch_color: ComplexColors = ComplexColors("#648fff", "#002999"), - u_ch_color: ComplexColors = ComplexColors("#ffb000", "#994A00"), - m_ch_color: ComplexColors = ComplexColors("#dc267f", "#760019"), - s_ch_color: str = "#7da781", - s_ch_linestyle: str = "-", - table_color: SchedTableColors = SchedTableColors("#e0e0e0", "#f6f6f6", "#f6f6f6"), - bg_color: str = "#f2f3f4", - num_points: int = 1000, - dpi: int | None = 150, - remove_spacing: bool = True, - max_table_ratio: float = 0.5, - vertical_span: float = 0.2, - axis_formatter: str = "%s", - ): - """Deprecated. - - Create new style sheet. - - For any of the Optional fields, if that field is None then it will revert to its - matplotlib.rcParams counterpart. See for usage on rcParams. Each argument that - is optional also describes it's mapped rcParam key below. - - Args: - figsize: Size of the figure. - If ``None``, will default to the figure size of the drawing backend. - If the output is ``matplotlib``, the default - parameter is ``rcParams['figure.figsize']``. - fig_unit_h_table: Height of row of event table. See Example. - use_table: When set `True` use event table. - table_columns: Number of event table columns. - table_font_size: Font size of event table. - axis_font_size: Font size of channel aliases. - If ``None``, will revert to the axis label size of the drawing backend. - If the output is ``matplotlib``, the default parameter is - ``rcParams['axes.titlesize']``. - label_font_size: Font size of labels in canvas. - icon_font_size: Size of symbols. - title_font_size: Font size of schedule name in title. - If ``None``, will default to the title font size of the drawing backend. - If the output is ``matplotlib``, the default - parameter is ``rcParams['figure.titlesize']``. - label_ch_linestyle: Line style for channel pulse label line. - label_ch_color: Color code or name of color for channel pulse label line. - label_ch_alpha: Transparency for channel pulse label line from 0 to 1. - d_ch_color: Color code or name of colors for real and imaginary part - of waveform at d channels. - u_ch_color: Color code or name of colors for real and imaginary part - of waveform at u channels. - m_ch_color: Color code or name of colors for real and imaginary part - of waveform at m channels. - s_ch_color: Color code or name of color for snapshot channel line. - s_ch_linestyle: Line style for snapshot line. - table_color: Color code or name of color for event table columns of - time, channel, event information. - bg_color: Color code or name of color for canvas background. - num_points: Number of points for interpolation of each channel. - dpi: Resolution in the unit of dot per inch to save image. - If ``None``, will revert to the DPI setting of the drawing backend. - If the output is ``matplotlib``, the default - parameter is ``rcParams['figure.dpi']``. - remove_spacing: Remove redundant spacing - when the waveform has no negative values. - max_table_ratio: Maximum portion of the plot the table can take up. - Limited to range between 0 and 1. - vertical_span: Spacing on top and bottom of pulse canvas. - axis_formatter: Format of horizontal axis of the plot. This is convenient when - you set ``dt`` option for the drawer. For example, formatter of ``%.3e`` gives you - horizontal axis values in the scientific notation with 3 digits. - - Example: - Height of the event table is decided by multiple parameters.:: - - figsize = (10.0, 12.0) - fig_unit_h_table = 0.4 - table_columns = 2 - max_table_ratio = 0.5 - - With this setup, events are shown in double-column style with - each line height of 0.4 inch and the table cannot exceed 5 inch. - Thus 12 lines are maximum and up to 24 events can be shown. - If you want to show more events, increase figure height or - reduce size of line height and table font size. - """ - self.figsize = figsize - self.fig_unit_h_table = fig_unit_h_table - self.use_table = use_table - self.table_columns = table_columns - self.table_font_size = table_font_size - self.axis_font_size = axis_font_size - self.label_font_size = label_font_size - self.icon_font_size = icon_font_size - self.title_font_size = title_font_size - self.d_ch_color = d_ch_color - self.label_ch_linestyle = label_ch_linestyle - self.label_ch_color = label_ch_color - self.label_ch_alpha = label_ch_alpha - self.u_ch_color = u_ch_color - self.m_ch_color = m_ch_color - self.a_ch_color = m_ch_color - self.s_ch_color = s_ch_color - self.s_ch_linestyle = s_ch_linestyle - self.table_color = table_color - self.bg_color = bg_color - self.num_points = num_points - self.dpi = dpi - self.remove_spacing = remove_spacing - - if max_table_ratio < 0.0 or max_table_ratio > 1.0: - logger.warning( - "max_table_ratio of %.2f is not in range [0.0, 1.0], clamping...", max_table_ratio - ) - - self.max_table_ratio = max(min(max_table_ratio, 0.0), 1.0) - self.vertical_span = vertical_span - self.axis_formatter = axis_formatter - - -class PulseStyle: - """Deprecated. - - Style sheet for Qiskit-Pulse sample pulse drawer.""" - - @deprecate_func( - additional_msg=( - "In addition to this stylesheet being deprecated, the legacy pulse drawer is too. " - "Instead, use the new drawer ``qiskit.visualization.pulse_drawer`` " - "with new stylesheet classes provided by ``qiskit.visualization.pulse_v2``. " - "You can choose one of ``IQXStandard``, ``IQXSimple``, ``IQXDebugging``." - ), - since="0.23.0", - package_name="qiskit-terra", - ) - def __init__( - self, - figsize: tuple[float, float] | None = (7.0, 5.0), - title_font_size: int | None = 18, - wave_color: ComplexColors = ComplexColors("#ff0000", "#0000ff"), - bg_color: str = "#f2f3f4", - num_points: int = 1000, - dpi: int | None = None, - ): - """Create new style sheet. - - For any of the Optional fields, if that field is None then it will revert to its - matplotlib.rcParams counterpart. See for usage on rcParams. Each argument that - is optional also describes it's mapped rcParam key below. - - Args: - figsize: Size of the figure. - If ``None``, will default to the figure size of the drawing backend. - If the output is ``matplotlib``, the default - parameter is ``rcParams['figure.figsize']``. - title_font_size: Font size of schedule name in title. - If ``None``, will default to the title font size of the drawing backend. - If the output is ``matplotlib``, the default - parameter is ``rcParams['figure.titlesize']``. - wave_color: Color code or name of colors for - the real and imaginary parts of the waveform. - bg_color: Color code or name of color for pulse canvas background. - num_points: Number of points for interpolation. - dpi: Resolution in the unit of dot per inch to save image. - If ``None``, will revert to the DPI setting of the drawing backend. - If the output is ``matplotlib``, the default - parameter is ``rcParams['figure.dpi']``. - """ - self.figsize = figsize - self.title_font_size = title_font_size - self.wave_color = wave_color - self.bg_color = bg_color - self.num_points = num_points - self.dpi = dpi diff --git a/qiskit/visualization/state_visualization.py b/qiskit/visualization/state_visualization.py index 5eea8bfe8e33..26d3c792986c 100644 --- a/qiskit/visualization/state_visualization.py +++ b/qiskit/visualization/state_visualization.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (C) 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 @@ -17,7 +17,7 @@ Visualization functions for quantum states. """ -from typing import Optional, List, Union +from typing import List, Union from functools import reduce import colorsys @@ -27,7 +27,6 @@ from qiskit.quantum_info.operators.operator import Operator from qiskit.quantum_info.operators.symplectic import PauliList, SparsePauliOp from qiskit.quantum_info.states.densitymatrix import DensityMatrix -from qiskit.utils.deprecation import deprecate_func from qiskit.utils import optionals as _optionals from qiskit.circuit.tools.pi_check import pi_check @@ -1280,50 +1279,6 @@ def state_to_latex( return prefix + latex_str + suffix -@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 - - Args: - raw_value: Value to convert - first_term: If True then generate latex code for the first term in an expression - decimals: Number of decimal places to round to (default: 10). - Returns: - String with latex code or None if no term is required - """ - if np.around(np.abs(raw_value), decimals=decimals) == 0: - return None - return _num_to_latex(raw_value, first_term=first_term, decimals=decimals, coefficient=True) - - -@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 - The first non-zero term is treated differently. For this term a leading + is suppressed. - Args: - numbers: List of numbers to format - decimals: Number of decimal places to round to (default: 10). - Returns: - List of formatted terms - """ - first_term = True - terms = [] - for number in numbers: - term = num_to_latex_ket(number, first_term, decimals) - if term is not None: - first_term = False - terms.append(term) - return terms - - 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/LICENSE.txt b/qiskit_pkg/LICENSE.txt deleted file mode 120000 index 4ab43736a839..000000000000 --- a/qiskit_pkg/LICENSE.txt +++ /dev/null @@ -1 +0,0 @@ -../LICENSE.txt \ No newline at end of file diff --git a/qiskit_pkg/MANIFEST.in b/qiskit_pkg/MANIFEST.in deleted file mode 100644 index 42eb4101e514..000000000000 --- a/qiskit_pkg/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include LICENSE.txt diff --git a/qiskit_pkg/README.md b/qiskit_pkg/README.md deleted file mode 120000 index 32d46ee883b5..000000000000 --- a/qiskit_pkg/README.md +++ /dev/null @@ -1 +0,0 @@ -../README.md \ No newline at end of file diff --git a/qiskit_pkg/setup.py b/qiskit_pkg/setup.py deleted file mode 100644 index 4846b2d0b9f6..000000000000 --- a/qiskit_pkg/setup.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 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. - -# This file is the setup.py file for the qiskit package. Because python -# packaging doesn't offer a mechanism to have qiskit supersede qiskit-terra -# and cleanly upgrade from one to the other, there needs to be a separate -# package shim to ensure no matter how people installed qiskit < 0.45.0 the -# upgrade works. - -import os - -from setuptools import setup - -README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), "README.md") -with open(README_PATH) as readme_file: - README = readme_file.read() - -requirements = ["qiskit-terra==1.0.0"] - -setup( - name="qiskit", - version="1.0.0", - description="Software for developing quantum computing programs", - long_description=README, - long_description_content_type="text/markdown", - url="https://qiskit.org/", - author="Qiskit Development Team", - author_email="hello@qiskit.org", - license="Apache 2.0", - py_modules=[], - packages=[], - classifiers=[ - "Environment :: Console", - "License :: OSI Approved :: Apache Software License", - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "Operating System :: Microsoft :: Windows", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Topic :: Scientific/Engineering", - ], - keywords="qiskit sdk quantum", - install_requires=requirements, - project_urls={ - "Bug Tracker": "https://github.com/Qiskit/qiskit/issues", - "Documentation": "https://qiskit.org/documentation/", - "Source Code": "https://github.com/Qiskit/qiskit", - }, - include_package_data=True, - python_requires=">=3.8", - extras_require={ - "qasm3-import": ["qiskit-terra[qasm3-import]"], - "visualization": ["qiskit-terra[visualization]"], - "crosstalk-pass": ["qiskit-terra[crosstalk-pass]"], - "csp-layout-pass": ["qiskit-terra[csp-layout-pass]"], - "all": ["qiskit-terra[all]"], - }, -) diff --git a/releasenotes/notes/5079_IntegerComparator_num_ancilla_qubits-bd1cff3366c345ae.yaml b/releasenotes/notes/5079_IntegerComparator_num_ancilla_qubits-bd1cff3366c345ae.yaml new file mode 100644 index 000000000000..9364ffd605b3 --- /dev/null +++ b/releasenotes/notes/5079_IntegerComparator_num_ancilla_qubits-bd1cff3366c345ae.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + The property ``IntegerComparator.num_ancilla_qubits`` is removed, which was + deprecated in Qiskit 0.23 (released on Oct 2020). Its functionality is fully covered + by :attr:`.IntegerComparator.num_ancilla`. diff --git a/releasenotes/notes/__qiskit_version__11305-bcf134513641462b.yaml b/releasenotes/notes/__qiskit_version__11305-bcf134513641462b.yaml new file mode 100644 index 000000000000..c1cae7fde91b --- /dev/null +++ b/releasenotes/notes/__qiskit_version__11305-bcf134513641462b.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + The variable ``qiskit.__qiskit_version__`` is removed as it was deprecated since + Qiskit 0.44 (released on July 2023). + Instead, you should use ``qiskit.__version__``. The other packages listed in the + former ``qiskit.__qiskit_version__`` have their own ``__version__`` module level dunder, + as standard in PEP 8. diff --git a/releasenotes/notes/add-filter-op-nodes-aa024a0f1058e4b7.yaml b/releasenotes/notes/add-filter-op-nodes-aa024a0f1058e4b7.yaml new file mode 100644 index 000000000000..9c5a0a5c43f8 --- /dev/null +++ b/releasenotes/notes/add-filter-op-nodes-aa024a0f1058e4b7.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + Added a new transpiler pass :class:`.FilterOpNodes` which is used to filter + :class:`.DAGOpNode`\s in a :class:`.DAGCircuit`. + - | + Added a new keyword argument, ``label``, to the constructor on the + :class:`.BarrierBeforeFinalMeasurements` transpiler pass. If specified the + inserted barrier will be assigned the specified label. This also prevents + the inserted barrier from being merged with any any other pre-existing + adjacent barriers. +other: + - | + The preset pass managers used by :func:`.transpile` and returned with + :class:`.generate_preset_pass_manager` will no longer insert barriers + before final measurements in the output circuits. diff --git a/releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml b/releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml new file mode 100644 index 000000000000..094266071990 --- /dev/null +++ b/releasenotes/notes/add-parameter-pow-ff5f8d10813f5733.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + :class:`~qiskit.circuit.ParameterExpression` (and thus also + :class:`~qiskit.circuit.Parameter`) now support powering: :code:`x**y` + where :code:`x` and :code:`y` can be any combination of + :class:`~qiskit.circuit.ParameterExpression` and number types. diff --git a/releasenotes/notes/add-py312-support-7077426af34ac5da.yaml b/releasenotes/notes/add-py312-support-7077426af34ac5da.yaml new file mode 100644 index 000000000000..4e2be1e92983 --- /dev/null +++ b/releasenotes/notes/add-py312-support-7077426af34ac5da.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Added support for using Qiskit with Python 3.12. diff --git a/releasenotes/notes/fix-instruction-condition-bits-17694f98628b30ad.yaml b/releasenotes/notes/fix-instruction-condition-bits-17694f98628b30ad.yaml new file mode 100644 index 000000000000..4f044b6a468f --- /dev/null +++ b/releasenotes/notes/fix-instruction-condition-bits-17694f98628b30ad.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + The property :attr:`.Instruction.condition_bits` will now correctly handle runtime classical + expressions (:mod:`qiskit.circuit.classical`). diff --git a/releasenotes/notes/fix_11143-d32a262538873a9d.yaml b/releasenotes/notes/fix_11143-d32a262538873a9d.yaml new file mode 100644 index 000000000000..ed04a2520e68 --- /dev/null +++ b/releasenotes/notes/fix_11143-d32a262538873a9d.yaml @@ -0,0 +1,4 @@ +upgrade: + - | + The functions ``qiskit.visualization.state_visualization.num_to_latex_ket`` and ``qiskit.visualization.state_visualization.numbers_to_latex_terms`` + have been removed, as they were removed in Qiskit 0.40 (released in Jan 2023). For similar functionality, see sympy's ``nsimplify`` and ``latex`` functions. \ No newline at end of file diff --git a/releasenotes/notes/platform-support-f7f693aaf5dec044.yaml b/releasenotes/notes/platform-support-f7f693aaf5dec044.yaml new file mode 100644 index 000000000000..f52aef82c80c --- /dev/null +++ b/releasenotes/notes/platform-support-f7f693aaf5dec044.yaml @@ -0,0 +1,29 @@ +--- +upgrade: + - | + The ``symengine`` library is now a hard requirement for all platforms. + Previously, ``symengine`` was required only on platforms that had + pre-compiled packages available and ``sympy`` would be used as a fallback + if it wasn't installed. These split requirements were resulting in increased + complexity, as it was necessary to determine which libraries were installed + to debug an issue. Requiring ``symengine`` for all systems greatly decreases + the complexity and optimizes Qiskit for higher performance. However, + users on i686 Linux, 32 bit Windows, and s390x Linux (the platforms without + precompiled packages on PyPI) will need to build symengine from source. + - | + Support for 32 bit platforms, i686 Linux and 32 bit Windows, on + Python < 3.10 has been downgraded from Tier 2 to Tier 3, as documented in + the :ref:`platform_support` page. This is a consequence of making + ``symengine`` required for all users, as there is a lack of pre-compiled packages + available for these platforms, so users will need to build symengine from + source. + - | + For macOS users, the minimum version of macOS is now 10.12. Previously, the + precompiled binary wheel packages for macOS x86_64 were published with + support for >=10.9. However, because of changes in the + `support policy `__ + for the Rust programming language the minimum version needed to raised + to macOS 10.12. If you're using Qiskit on macOS 10.9 you can probably + build Qiskit from source while the Qiskit MSRV (minimum supported Rust + version) is < 1.74, but the precompiled binaries published to PyPI will + only be compatible with macOS >= 10.12. diff --git a/releasenotes/notes/remove-algorithm-utils-707648b69af439dc.yaml b/releasenotes/notes/remove-algorithm-utils-707648b69af439dc.yaml new file mode 100644 index 000000000000..67c551dad70f --- /dev/null +++ b/releasenotes/notes/remove-algorithm-utils-707648b69af439dc.yaml @@ -0,0 +1,27 @@ +--- +upgrade: + - | + The following tools in :mod:`qiskit.utils` have been removed from the codebase: + + * Utils in ``qiskit.utils.arithmetic`` + * Utils in ``qiskit.utils.circuit_utils`` + * Utils in ``qiskit.utils.entangler_map`` + * Utils in ``qiskit.utils.name_unnamed_args`` + + These functions were used exclusively in the context of ``qiskit.algorithms`` and + ``qiskit.opflow``, and were deprecated in Qiskit 0.46. ``qiskit.algorithms`` and + ``qiskit.opflow`` have been deprecated since Qiskit 0.45 and Qiskit Terra 0.24 + respectively. + + The following utilities have also been removed: + + * Utils in ``qiskit.utils.validation`` + * ``algorithm_globals`` + + These were deprecated in Qiskit 0.45 as a consequence of the migration + of ``qiskit.algorithms`` to a standalone + `package `_, where + these utils have also been migrated. They can be found in the new package + under ``qiskit_algorithms.utils``. + + diff --git a/releasenotes/notes/remove-deprecated-visualization-pulse-8adf40ff1a69df63.yaml b/releasenotes/notes/remove-deprecated-visualization-pulse-8adf40ff1a69df63.yaml new file mode 100644 index 000000000000..ab3ba5517880 --- /dev/null +++ b/releasenotes/notes/remove-deprecated-visualization-pulse-8adf40ff1a69df63.yaml @@ -0,0 +1,15 @@ +--- +upgrade: + - | + The classes and methods in :mod:`qiskit.visualization.pulse`: + + * :class:`~qiskit.visualization.pulse.SchedStyle` + * :class:`~qiskit.visualization.pulse.PulseStyle` + * :class:`~qiskit.visualization.pulse.matplotlib.EventsOutputChannels` + * :class:`~qiskit.visualization.pulse.matplotlib.WaveformDrawer` + * :class:`~qiskit.visualization.pulse.matplotlib.ScheduleDrawer` + * :func:`~qiskit.visualization.pulse.interpolation.interp1d` + * :func:`~qiskit.visualization.interpolation.pulse.step_wise` + + Have been removed following their deprecation in Qiskit Terra 0.23. + Use the new interface :func:`qiskit.visualization.pulse_drawer` for pulse visualization. diff --git a/releasenotes/notes/remove-legacy-qasm2-parser-53ad3f1817fd68cc.yaml b/releasenotes/notes/remove-legacy-qasm2-parser-53ad3f1817fd68cc.yaml new file mode 100644 index 000000000000..d3a581761060 --- /dev/null +++ b/releasenotes/notes/remove-legacy-qasm2-parser-53ad3f1817fd68cc.yaml @@ -0,0 +1,34 @@ +--- +upgrade: + - | + The legacy OpenQASM 2 parser module previously present in ``qiskit.qasm`` has + been removed. It was marked as deprecated in Qiskit 0.46.0 release. The + OpenQASM 2 parser has been superseded by the :mod:`qiskit.qasm2` module which + provides a faster more correct parser for QASM2. + - | + The ``qiskit.converters.ast_to_dag`` function has been removed. It + previously was used to convert the abstract syntax tree generated by the + legacy OpenQASM 2 parser (in the ``qiskit.qasm`` module which no longer exists) + and convert that directly to a :class:`.DAGCircuit`. This function was + marked as deprecated in the Qiskit 0.46.0 release. As the legacy + OpenQASM 2 parser has been removed this function no longer serves a purpose. + If you were previously using this, you can instead parse your OpenQASM 2 files + into a :class:`.QuantumCircuit` using the + :meth:`.QuantumCircuit.from_qasm_file` or + :meth:`.QuantumCircuit.from_qasm_str` constructor methods and then + converting that :class:`.QuantumCircuit` into a :class:`.DAGCircuit` with + :func:`.circuit_to_dag`. + - | + Removed the ``QuantumCircuit.qasm()`` method to generate a OpenQASM 2 + representation of the :class:`.QuantumCircuit` object. Instead the + :func:`.qasm2.dump` or :func:`.qasm2.dumps` functions should be used. This + function was marked as deprecated in the 0.46.0 release. If you were using + the ``QuantumCircuit.qasm()`` method to generate pygments formatted output + you should instead look at the standalone ``openqasm-pygments`` package + to provide this functionality. + - | + Removed the ``qiskit.tools.jupyter.library.qasm_widget`` function which + was used to visualize qasm strings in a jupyter notebook. This function + was marked as deprecated as part of the Qiskit 0.44.0 release. The function + was originally used for building documentation but hasn't been used in some + time and has been removed from Qiskit. diff --git a/releasenotes/notes/remove-opflow-qi-utils-3debd943c65b17da.yaml b/releasenotes/notes/remove-opflow-qi-utils-3debd943c65b17da.yaml new file mode 100644 index 000000000000..8157db5a5e46 --- /dev/null +++ b/releasenotes/notes/remove-opflow-qi-utils-3debd943c65b17da.yaml @@ -0,0 +1,20 @@ +--- +upgrade: + - | + The ``qiskit.opflow`` module has been removed, following its deprecation in + Qiskit 0.44. For more context on the deprecation and + detailed migration instructions, you can read the + `Opflow Migration Guide `__. + + + - | + A series of legacy quantum execution utils have been removed, following their deprecation in Qiskit 0.44. + These include the ``qiskit.utils.QuantumInstance`` class, as well the modules: + + - ``qiskit.utils.backend_utils`` + - ``qiskit.utils.mitigation`` + - ``qiskit.utils.measurement_error_mitigation`` + - ``qiskit.utils.run_circuits`` + + Their functionality has been superseded by the use of primitives. For more context on the deprecation and + detailed migration instructions, you can read the `QuantumInstance Migration Guide `__. diff --git a/releasenotes/notes/rr-decomposition-synthesis-70eb88ada9305916.yaml b/releasenotes/notes/rr-decomposition-synthesis-70eb88ada9305916.yaml new file mode 100644 index 000000000000..e4ec6a970a3c --- /dev/null +++ b/releasenotes/notes/rr-decomposition-synthesis-70eb88ada9305916.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fixed an issue in :func:`.unitary_to_gate_sequence` which caused unitary + decomposition in RR basis to emit two R gates in some cases where the + matrix can be expressed as a single R gate. Previously, in those cases you + would get two R gates with the same phi parameter. Now, they are combined + into one. diff --git a/releasenotes/notes/sabre-no-route-barrier-bc82fecb65d3ab9a.yaml b/releasenotes/notes/sabre-no-route-barrier-bc82fecb65d3ab9a.yaml new file mode 100644 index 000000000000..fe77e9c51ef0 --- /dev/null +++ b/releasenotes/notes/sabre-no-route-barrier-bc82fecb65d3ab9a.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixed an issue in the :class:`.SabreSwap` and :class:`.SabreLayout` + transpiler passes where it would incorrectly treat 2 qubit + :class:`.Barrier` instructions as an instruction that needs to be + routed according the transpiler :class:`.Target`. When this occured the + output was still correct but would potentially include unecessary + :class:`.SwapGate` instructions. diff --git a/releasenotes/notes/terra-nullius-7ef598626d8118c1.yaml b/releasenotes/notes/terra-nullius-7ef598626d8118c1.yaml new file mode 100644 index 000000000000..562929a17b9f --- /dev/null +++ b/releasenotes/notes/terra-nullius-7ef598626d8118c1.yaml @@ -0,0 +1,30 @@ +--- +critical: + - | + You cannot upgrade in place to Qiskit 1.0. You must begin a new virtual environment. + + From Qiskit 1.0, Qiskit is comprised of exactly one Python package: ``qiskit``. Previously, + as a legacy of the "component elements" of early Qiskit, the ``qiskit`` package was a + dependency-only "metapackage", and the core code of Qiskit was in a package called ``qiskit-terra``. + As Qiskit grew, the other elements split off into their own packages (such as ``qiskit-aer``) + until only the core was left in the metapackage. For Qiskit 1.0, we are removing the metapackage + entirely, and replacing it with the actual Qiskit code. + + This means that you cannot upgrade an existing installation to Qiskit 1.0. Instead, you must + create a new Python virtual environment. Using the built-in ``venv`` module, you can do (Linux + and Mac): + + .. code-block:: bash + + # Create the new environment (only once). + python -m venv ~/qiskit-1.0-venv + # Activate the environment (every session). + source ~/qiskit-1.0-venv/bin/activate + # Install Qiskit (only once). + pip install 'qiskit>=1.0' + + For other platforms, or more unusual shells, refer to `the Python standard-library documentation + on activating virtual environments `__. + + If you are a library author, or have code that depends on Qiskit, you should update any old + dependencies on ``qiskit-terra`` to instead depend on ``qiskit``. diff --git a/requirements-optional.txt b/requirements-optional.txt index 6afa25271ab9..6c523a4937f3 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -17,7 +17,6 @@ ipywidgets>=7.3.0 matplotlib>=3.3 pillow>=4.2.1 pydot -pygments>=2.4 pylatexenc>=1.4 seaborn>=0.9.0 diff --git a/requirements.txt b/requirements.txt index d41eee50ce95..6402e9a6549f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ rustworkx>=0.13.0 numpy>=1.17,<2 -ply>=3.10 psutil>=5 scipy>=1.5 sympy>=1.3 @@ -8,7 +7,4 @@ dill>=0.3 python-dateutil>=2.8.0 stevedore>=3.0.0 typing-extensions; python_version<'3.11' -# symengine pinning needed due lowered precision handling complex -# multiplication in version 0.10 wich breaks parameter assignment test -# (can be removed once issue is fix) -symengine>=0.9, <0.10; platform_machine == 'x86_64' or platform_machine == 'aarch64' or platform_machine == 'ppc64le' or platform_machine == 'amd64' or platform_machine == 'arm64' +symengine>=0.9,!=0.10.0 diff --git a/setup.py b/setup.py index 97ca604fd757..62e6f43bcbc6 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,6 @@ "pillow>=4.2.1", "pylatexenc>=1.4", "seaborn>=0.9.0", - "pygments>=2.4", ] z3_requirements = [ "z3-solver>=4.7", @@ -56,7 +55,7 @@ setup( - name="qiskit-terra", + name="qiskit", version="1.0.0", description="Software for developing quantum computing programs", long_description=README, @@ -78,6 +77,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", ], keywords="qiskit sdk quantum", diff --git a/test/benchmarks/converters.py b/test/benchmarks/converters.py index 041e40dd6de1..d99822fa8016 100644 --- a/test/benchmarks/converters.py +++ b/test/benchmarks/converters.py @@ -14,7 +14,6 @@ # pylint: disable=attribute-defined-outside-init,unsubscriptable-object from qiskit import converters -from qiskit import qasm from .utils import random_circuit @@ -38,7 +37,6 @@ def setup(self, n_qubits, depth): raise NotImplementedError self.qc = random_circuit(n_qubits, depth, measure=True, conditional=True, seed=seed) self.dag = converters.circuit_to_dag(self.qc) - self.qasm = qasm.Qasm(data=self.qc.qasm()).parse() def time_circuit_to_dag(self, *_): converters.circuit_to_dag(self.qc) @@ -48,6 +46,3 @@ def time_circuit_to_instruction(self, *_): def time_dag_to_circuit(self, *_): converters.dag_to_circuit(self.dag) - - def time_ast_to_circuit(self, *_): - converters.ast_to_dag(self.qasm) diff --git a/test/python/basicaer/test_qasm_simulator.py b/test/python/basicaer/test_qasm_simulator.py index 90bb10149943..6deb874d9c82 100644 --- a/test/python/basicaer/test_qasm_simulator.py +++ b/test/python/basicaer/test_qasm_simulator.py @@ -25,6 +25,7 @@ from qiskit.compiler import transpile, assemble from qiskit.providers.basicaer import QasmSimulatorPy from qiskit.test import providers +from qiskit.qasm2 import dumps class StreamHandlerRaiseException(StreamHandler): @@ -306,7 +307,7 @@ def test_teleport(self): "1": data["1 0 0"] + data["1 1 0"] + data["1 0 1"] + data["1 1 1"], } self.log.info("test_teleport: circuit:") - self.log.info(circuit.qasm()) + self.log.info(dumps(circuit)) self.log.info("test_teleport: data %s", data) self.log.info("test_teleport: alice %s", alice) self.log.info("test_teleport: bob %s", bob) diff --git a/test/python/circuit/classical/test_expr_properties.py b/test/python/circuit/classical/test_expr_properties.py index a6873153c0eb..efda6ba37758 100644 --- a/test/python/circuit/classical/test_expr_properties.py +++ b/test/python/circuit/classical/test_expr_properties.py @@ -56,3 +56,45 @@ def test_expr_can_be_cloned(self, obj): self.assertEqual(obj, copy.copy(obj)) self.assertEqual(obj, copy.deepcopy(obj)) self.assertEqual(obj, pickle.loads(pickle.dumps(obj))) + + def test_var_equality(self): + """Test that various types of :class:`.expr.Var` equality work as expected both in equal and + unequal cases.""" + var_a_bool = expr.Var.new("a", types.Bool()) + self.assertEqual(var_a_bool, var_a_bool) + + # Allocating a new variable should not compare equal, despite the name match. A semantic + # equality checker can choose to key these variables on only their names and types, if it + # knows that that check is valid within the semantic context. + self.assertNotEqual(var_a_bool, expr.Var.new("a", types.Bool())) + + # Manually constructing the same object with the same UUID should cause it compare equal, + # though, for serialisation ease. + self.assertEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="a")) + + # This is a badly constructed variable because it's using a different type to refer to the + # same storage location (the UUID) as another variable. It is an IR error to generate this + # sort of thing, but we can't fully be responsible for that and a pass would need to go out + # of its way to do this incorrectly, but we can still ensure that the direct equality check + # would spot the error. + self.assertNotEqual( + var_a_bool, expr.Var(var_a_bool.var, types.Uint(8), name=var_a_bool.name) + ) + + # This is also badly constructed because it uses a different name to refer to the "same" + # storage location. + self.assertNotEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="b")) + + # Obviously, two variables of different types and names should compare unequal. + self.assertNotEqual(expr.Var.new("a", types.Bool()), expr.Var.new("b", types.Uint(8))) + # As should two variables of the same name but different storage locations and types. + self.assertNotEqual(expr.Var.new("a", types.Bool()), expr.Var.new("a", types.Uint(8))) + + def test_var_uuid_clone(self): + """Test that :class:`.expr.Var` instances that have an associated UUID and name roundtrip + through pickle and copy operations to produce values that compare equal.""" + var_a_u8 = expr.Var.new("a", types.Uint(8)) + + self.assertEqual(var_a_u8, pickle.loads(pickle.dumps(var_a_u8))) + self.assertEqual(var_a_u8, copy.copy(var_a_u8)) + self.assertEqual(var_a_u8, copy.deepcopy(var_a_u8)) diff --git a/test/python/circuit/library/test_evolution_gate.py b/test/python/circuit/library/test_evolution_gate.py index adf0e189db32..fb13f5de88ae 100644 --- a/test/python/circuit/library/test_evolution_gate.py +++ b/test/python/circuit/library/test_evolution_gate.py @@ -22,9 +22,13 @@ from qiskit.synthesis import LieTrotter, SuzukiTrotter, MatrixExponential, QDrift from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase -from qiskit.opflow import I, X, Y, Z, PauliSumOp from qiskit.quantum_info import Operator, SparsePauliOp, Pauli, Statevector +X = SparsePauliOp("X") +Y = SparsePauliOp("Y") +Z = SparsePauliOp("Z") +I = SparsePauliOp("I") + @ddt class TestEvolutionGate(QiskitTestCase): @@ -37,8 +41,7 @@ def setUp(self): def test_matrix_decomposition(self): """Test the default decomposition.""" - with self.assertWarns(DeprecationWarning): - op = (X ^ 3) + (Y ^ 3) + (Z ^ 3) + op = (X ^ X ^ X) + (Y ^ Y ^ Y) + (Z ^ Z ^ Z) time = 0.123 matrix = op.to_matrix() @@ -50,8 +53,7 @@ def test_matrix_decomposition(self): def test_lie_trotter(self): """Test constructing the circuit with Lie Trotter decomposition.""" - with self.assertWarns(DeprecationWarning): - op = (X ^ 3) + (Y ^ 3) + (Z ^ 3) + op = (X ^ X ^ X) + (Y ^ Y ^ Y) + (Z ^ Z ^ Z) time = 0.123 reps = 4 evo_gate = PauliEvolutionGate(op, time, synthesis=LieTrotter(reps=reps)) @@ -60,32 +62,30 @@ def test_lie_trotter(self): def test_rzx_order(self): """Test ZX and XZ is mapped onto the correct qubits.""" - with self.assertWarns(DeprecationWarning): - op = (X ^ 3) + (Y ^ 3) + (Z ^ 3) - for op, indices in zip([X ^ Z, Z ^ X], [(0, 1), (1, 0)]): - with self.subTest(op=op, indices=indices): - evo_gate = PauliEvolutionGate(op) - decomposed = evo_gate.definition.decompose() - - # ┌───┐┌───────┐┌───┐ - # q_0: ─────┤ X ├┤ Rz(2) ├┤ X ├───── - # ┌───┐└─┬─┘└───────┘└─┬─┘┌───┐ - # q_1: ┤ H ├──■─────────────■──┤ H ├ - # └───┘ └───┘ - ref = QuantumCircuit(2) - ref.h(indices[1]) - ref.cx(indices[1], indices[0]) - ref.rz(2.0, indices[0]) - ref.cx(indices[1], indices[0]) - ref.h(indices[1]) - - # don't use circuit equality since RZX here decomposes with RZ on the bottom - self.assertTrue(Operator(decomposed).equiv(ref)) + + for op, indices in zip([X ^ Z, Z ^ X], [(0, 1), (1, 0)]): + with self.subTest(op=op, indices=indices): + evo_gate = PauliEvolutionGate(op) + decomposed = evo_gate.definition.decompose() + + # ┌───┐┌───────┐┌───┐ + # q_0: ─────┤ X ├┤ Rz(2) ├┤ X ├───── + # ┌───┐└─┬─┘└───────┘└─┬─┘┌───┐ + # q_1: ┤ H ├──■─────────────■──┤ H ├ + # └───┘ └───┘ + ref = QuantumCircuit(2) + ref.h(indices[1]) + ref.cx(indices[1], indices[0]) + ref.rz(2.0, indices[0]) + ref.cx(indices[1], indices[0]) + ref.h(indices[1]) + + # don't use circuit equality since RZX here decomposes with RZ on the bottom + self.assertTrue(Operator(decomposed).equiv(ref)) def test_suzuki_trotter(self): """Test constructing the circuit with Lie Trotter decomposition.""" - with self.assertWarns(DeprecationWarning): - op = (X ^ 3) + (Y ^ 3) + (Z ^ 3) + op = (X ^ X ^ X) + (Y ^ Y ^ Y) + (Z ^ Z ^ Z) time = 0.123 reps = 4 for order in [2, 4, 6]: @@ -107,8 +107,7 @@ def test_suzuki_trotter(self): def test_suzuki_trotter_manual(self): """Test the evolution circuit of Suzuki Trotter against a manually constructed circuit.""" - with self.assertWarns(DeprecationWarning): - op = X + Y + op = X + Y time = 0.1 reps = 1 evo_gate = PauliEvolutionGate(op, time, synthesis=SuzukiTrotter(order=4, reps=reps)) @@ -156,8 +155,7 @@ def test_qdrift_manual(self, op, time, reps, sampled_ops): def test_qdrift_evolution(self): """Test QDrift on an example.""" - with self.assertWarns(DeprecationWarning): - op = 0.1 * (Z ^ Z) + (X ^ I) + (I ^ X) + 0.2 * (X ^ X) + op = 0.1 * (Z ^ Z) + (X ^ I) + (I ^ X) + 0.2 * (X ^ X) reps = 20 qdrift = PauliEvolutionGate( op, time=0.5 / reps, synthesis=QDrift(reps=reps, seed=self.seed) @@ -171,8 +169,7 @@ def energy(evo): def test_passing_grouped_paulis(self): """Test passing a list of already grouped Paulis.""" - with self.assertWarns(DeprecationWarning): - grouped_ops = [(X ^ Y) + (Y ^ X), (Z ^ I) + (Z ^ Z) + (I ^ Z), (X ^ X)] + grouped_ops = [(X ^ Y) + (Y ^ X), (Z ^ I) + (Z ^ Z) + (I ^ Z), (X ^ X)] evo_gate = PauliEvolutionGate(grouped_ops, time=0.12, synthesis=LieTrotter()) decomposed = evo_gate.definition.decompose() self.assertEqual(decomposed.count_ops()["rz"], 4) @@ -181,8 +178,7 @@ def test_passing_grouped_paulis(self): def test_list_from_grouped_paulis(self): """Test getting a string representation from grouped Paulis.""" - with self.assertWarns(DeprecationWarning): - grouped_ops = [(X ^ Y) + (Y ^ X), (Z ^ I) + (Z ^ Z) + (I ^ Z), (X ^ X)] + grouped_ops = [(X ^ Y) + (Y ^ X), (Z ^ I) + (Z ^ Z) + (I ^ Z), (X ^ X)] evo_gate = PauliEvolutionGate(grouped_ops, time=0.12, synthesis=LieTrotter()) pauli_strings = [] @@ -202,8 +198,7 @@ def test_list_from_grouped_paulis(self): def test_dag_conversion(self): """Test constructing a circuit with evolutions yields a DAG with evolution blocks.""" time = Parameter("t") - with self.assertWarns(DeprecationWarning): - evo = PauliEvolutionGate((Z ^ 2) + (X ^ 2), time=time) + evo = PauliEvolutionGate((Z ^ Z) + (X ^ X), time=time) circuit = QuantumCircuit(2) circuit.h(circuit.qubits) @@ -221,8 +216,7 @@ def test_dag_conversion(self): def test_cnot_chain_options(self, option): """Test selecting different kinds of CNOT chains.""" - with self.assertWarns(DeprecationWarning): - op = Z ^ Z ^ Z + op = Z ^ Z ^ Z synthesis = LieTrotter(reps=1, cx_structure=option) evo = PauliEvolutionGate(op, synthesis=synthesis) @@ -247,9 +241,7 @@ def test_cnot_chain_options(self, option): @data( Pauli("XI"), - X ^ I, # PauliOp SparsePauliOp(Pauli("XI")), - PauliSumOp(SparsePauliOp("XI")), ) def test_different_input_types(self, op): """Test all different supported input types and that they yield the same.""" @@ -266,16 +258,14 @@ def test_different_input_types(self, op): def test_pauliop_coefficients_respected(self): """Test that global ``PauliOp`` coefficients are being taken care of.""" - with self.assertWarns(DeprecationWarning): - evo = PauliEvolutionGate(5 * (Z ^ I), time=1, synthesis=LieTrotter()) + evo = PauliEvolutionGate(5 * (Z ^ I), time=1, synthesis=LieTrotter()) circuit = evo.definition.decompose() rz_angle = circuit.data[0].operation.params[0] self.assertEqual(rz_angle, 10) def test_paulisumop_coefficients_respected(self): """Test that global ``PauliSumOp`` coefficients are being taken care of.""" - with self.assertWarns(DeprecationWarning): - evo = PauliEvolutionGate(5 * (2 * X + 3 * Y - Z), time=1, synthesis=LieTrotter()) + evo = PauliEvolutionGate(5 * (2 * X + 3 * Y - Z), time=1, synthesis=LieTrotter()) circuit = evo.definition.decompose() rz_angles = [ circuit.data[0].operation.params[0], # X @@ -289,8 +279,7 @@ def test_lie_trotter_two_qubit_correct_order(self): Regression test of Qiskit/qiskit-terra#7544. """ - with self.assertWarns(DeprecationWarning): - operator = I ^ Z ^ Z + operator = I ^ Z ^ Z time = 0.5 exact = scipy.linalg.expm(-1j * time * operator.to_matrix()) lie_trotter = PauliEvolutionGate(operator, time, synthesis=LieTrotter()) @@ -310,8 +299,7 @@ def test_paramtrized_op_raises(self): @data(LieTrotter, MatrixExponential) def test_inverse(self, synth_cls): """Test calculating the inverse is correct.""" - with self.assertWarns(DeprecationWarning): - evo = PauliEvolutionGate(X + Y, time=0.12, synthesis=synth_cls()) + evo = PauliEvolutionGate(X + Y, time=0.12, synthesis=synth_cls()) circuit = QuantumCircuit(1) circuit.append(evo, circuit.qubits) @@ -321,8 +309,7 @@ def test_inverse(self, synth_cls): def test_labels_and_name(self): """Test the name and labels are correct.""" - with self.assertWarns(DeprecationWarning): - operators = [X, (X + Y), ((I ^ Z) + (Z ^ I) - 0.2 * (X ^ X))] + operators = [X, (X + Y), ((I ^ Z) + (Z ^ I) - 0.2 * (X ^ X))] # note: the labels do not show coefficients! expected_labels = ["X", "(X + Y)", "(IZ + ZI + XX)"] diff --git a/test/python/circuit/library/test_evolved_op_ansatz.py b/test/python/circuit/library/test_evolved_op_ansatz.py index 06bcc206d8a4..000eb8cfb9df 100644 --- a/test/python/circuit/library/test_evolved_op_ansatz.py +++ b/test/python/circuit/library/test_evolved_op_ansatz.py @@ -12,11 +12,9 @@ """Test the evolved operator ansatz.""" -from ddt import ddt, data import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.opflow import X, Y, Z, I, MatrixEvolution from qiskit.quantum_info import SparsePauliOp, Operator, Pauli from qiskit.circuit.library import EvolvedOperatorAnsatz, HamiltonianGate @@ -24,24 +22,15 @@ from qiskit.test import QiskitTestCase -@ddt class TestEvolvedOperatorAnsatz(QiskitTestCase): """Test the evolved operator ansatz.""" - @data(True, False) - def test_evolved_op_ansatz(self, use_opflow): + def test_evolved_op_ansatz(self): """Test the default evolution.""" num_qubits = 3 - if use_opflow: - with self.assertWarns(DeprecationWarning): - ops = [Z ^ num_qubits, Y ^ num_qubits, X ^ num_qubits] - evo = EvolvedOperatorAnsatz(ops, 2) - parameters = evo.parameters - - else: - ops = [Pauli("Z" * num_qubits), Pauli("Y" * num_qubits), Pauli("X" * num_qubits)] - evo = EvolvedOperatorAnsatz(ops, 2) - parameters = evo.parameters + ops = [Pauli("Z" * num_qubits), Pauli("Y" * num_qubits), Pauli("X" * num_qubits)] + evo = EvolvedOperatorAnsatz(ops, 2) + parameters = evo.parameters reference = QuantumCircuit(num_qubits) strings = ["z" * num_qubits, "y" * num_qubits, "x" * num_qubits] * 2 @@ -50,62 +39,47 @@ def test_evolved_op_ansatz(self, use_opflow): self.assertEqual(evo.decompose().decompose(), reference) - @data(True, False) - def test_custom_evolution(self, use_opflow): + def test_custom_evolution(self): """Test using another evolution than the default (e.g. matrix evolution).""" - if use_opflow: - with self.assertWarns(DeprecationWarning): - op = X ^ I ^ Z - matrix = op.to_matrix() - evolution = MatrixEvolution() - evo = EvolvedOperatorAnsatz(op, evolution=evolution) - parameters = evo.parameters - - else: - op = SparsePauliOp(["ZIX"]) - matrix = np.array(op) - evolution = MatrixExponential() - evo = EvolvedOperatorAnsatz(op, evolution=evolution) - parameters = evo.parameters + op = SparsePauliOp(["ZIX"]) + matrix = np.array(op) + evolution = MatrixExponential() + evo = EvolvedOperatorAnsatz(op, evolution=evolution) + parameters = evo.parameters reference = QuantumCircuit(3) reference.append(HamiltonianGate(matrix, parameters[0]), [0, 1, 2]) - decomposed = evo.decompose() - if not use_opflow: - decomposed = decomposed.decompose() - + decomposed = evo.decompose().decompose() self.assertEqual(decomposed, reference) def test_changing_operators(self): """Test rebuilding after the operators changed.""" - ops = [X, Y, Z] - with self.assertWarns(DeprecationWarning): - evo = EvolvedOperatorAnsatz(ops) - evo.operators = [X, Y] - parameters = evo.parameters + ops = [Pauli("X"), Pauli("Y"), Pauli("Z")] + evo = EvolvedOperatorAnsatz(ops) + evo.operators = [Pauli("X"), Pauli("Y")] + parameters = evo.parameters reference = QuantumCircuit(1) reference.rx(2 * parameters[0], 0) reference.ry(2 * parameters[1], 0) - self.assertEqual(evo.decompose(), reference) + self.assertEqual(evo.decompose(reps=2), reference) def test_invalid_reps(self): """Test setting an invalid number of reps.""" with self.assertRaises(ValueError): - _ = EvolvedOperatorAnsatz(X, reps=-1) + _ = EvolvedOperatorAnsatz(Pauli("X"), reps=-1) def test_insert_barriers(self): """Test using insert_barriers.""" - with self.assertWarns(DeprecationWarning): - evo = EvolvedOperatorAnsatz(Z, reps=4, insert_barriers=True) - ref = QuantumCircuit(1) - for parameter in evo.parameters: - ref.rz(2.0 * parameter, 0) - ref.barrier() - self.assertEqual(evo.decompose(), ref) + evo = EvolvedOperatorAnsatz(Pauli("Z"), reps=4, insert_barriers=True) + ref = QuantumCircuit(1) + for parameter in evo.parameters: + ref.rz(2.0 * parameter, 0) + ref.barrier() + self.assertEqual(evo.decompose(reps=2), ref) def test_empty_build_fails(self): """Test setting no operators to evolve raises the appropriate error.""" diff --git a/test/python/circuit/library/test_permutation.py b/test/python/circuit/library/test_permutation.py index 25bfa0bfae49..aacc5425d74b 100644 --- a/test/python/circuit/library/test_permutation.py +++ b/test/python/circuit/library/test_permutation.py @@ -25,6 +25,7 @@ from qiskit.circuit.library import Permutation, PermutationGate from qiskit.quantum_info import Operator from qiskit.qpy import dump, load +from qiskit.qasm2 import dumps class TestPermutationLibrary(QiskitTestCase): @@ -160,9 +161,9 @@ def test_qasm(self): "gate permutation__2_4_3_0_1_ q0,q1,q2,q3,q4 { swap q2,q3; swap q1,q4; swap q0,q3; }\n" "qreg q0[5];\n" "permutation__2_4_3_0_1_ q0[0],q0[1],q0[2],q0[3],q0[4];\n" - "h q0[0];\n" + "h q0[0];" ) - self.assertEqual(expected_qasm, circuit.qasm()) + self.assertEqual(expected_qasm, dumps(circuit)) def test_qpy(self): """Test qpy for circuits with permutations.""" diff --git a/test/python/circuit/library/test_qaoa_ansatz.py b/test/python/circuit/library/test_qaoa_ansatz.py index dfbf25da69d4..fcd062152bfa 100644 --- a/test/python/circuit/library/test_qaoa_ansatz.py +++ b/test/python/circuit/library/test_qaoa_ansatz.py @@ -18,7 +18,6 @@ from qiskit.circuit import QuantumCircuit, Parameter from qiskit.circuit.library import HGate, RXGate, YGate, RYGate, RZGate from qiskit.circuit.library.n_local.qaoa_ansatz import QAOAAnsatz -from qiskit.opflow import I from qiskit.quantum_info import Pauli, SparsePauliOp from qiskit.test import QiskitTestCase @@ -29,8 +28,7 @@ class TestQAOAAnsatz(QiskitTestCase): def test_default_qaoa(self): """Test construction of the default circuit.""" - # To be changed once QAOAAnsatz drops support for opflow - circuit = QAOAAnsatz(I, 1) + circuit = QAOAAnsatz(Pauli("I"), 1) parameters = circuit.parameters circuit = circuit.decompose() diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index 4f8523358dbd..5695a298f782 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -55,7 +55,6 @@ from qiskit.quantum_info.random import random_unitary from qiskit.circuit.controlledgate import ControlledGate from qiskit.utils import optionals -from qiskit.exceptions import MissingOptionalLibraryError @ddt.ddt @@ -1747,22 +1746,3 @@ def test_symengine_full_path(self): new_circ = load(qpy_file)[0] self.assertEqual(self.qc, new_circ) self.assertDeprecatedBitProperties(self.qc, new_circ) - - @unittest.skipIf(not optionals.HAS_SYMENGINE, "Install symengine to run this test.") - def test_dump_no_symengine(self): - """Test dump fails if symengine is not installed and use_symengine==True.""" - qpy_file = io.BytesIO() - with optionals.HAS_SYMENGINE.disable_locally(): - with self.assertRaises(MissingOptionalLibraryError): - dump(self.qc, qpy_file, use_symengine=True) - - @unittest.skipIf(not optionals.HAS_SYMENGINE, "Install symengine to run this test.") - def test_load_no_symengine(self): - """Test that load fails if symengine is not installed and the - file was created with use_symengine==True.""" - qpy_file = io.BytesIO() - dump(self.qc, qpy_file, use_symengine=True) - qpy_file.seek(0) - with optionals.HAS_SYMENGINE.disable_locally(): - with self.assertRaises(MissingOptionalLibraryError): - _ = load(qpy_file)[0] diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py index 5b27f968d021..7102df18bf68 100644 --- a/test/python/circuit/test_circuit_qasm.py +++ b/test/python/circuit/test_circuit_qasm.py @@ -20,7 +20,8 @@ from qiskit.test import QiskitTestCase from qiskit.circuit import Parameter, Qubit, Clbit, Gate from qiskit.circuit.library import C3SXGate, CCZGate, CSGate, CSdgGate, PermutationGate -from qiskit.qasm.exceptions import QasmError +from qiskit.qasm2.exceptions import QASM2Error as QasmError +from qiskit.qasm2 import dumps # Regex pattern to match valid OpenQASM identifiers VALID_QASM2_IDENTIFIER = re.compile("[a-z][a-zA-Z_0-9]*") @@ -69,8 +70,8 @@ def test_circuit_qasm(self): barrier qr1[0],qr2[0],qr2[1]; measure qr1[0] -> cr[0]; measure qr2[0] -> cr[1]; -measure qr2[1] -> cr[2];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +measure qr2[1] -> cr[2];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_composite_circuit(self): """Test circuit qasm() method when a composite circuit instruction @@ -103,8 +104,8 @@ def test_circuit_qasm_with_composite_circuit(self): barrier qr[0],qr[1]; composite_circ qr[0],qr[1]; measure qr[0] -> cr[0]; -measure qr[1] -> cr[1];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +measure qr[1] -> cr[1];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_multiple_same_composite_circuits(self): """Test circuit qasm() method when a composite circuit is added @@ -139,8 +140,8 @@ def test_circuit_qasm_with_multiple_same_composite_circuits(self): composite_circ qr[0],qr[1]; composite_circ qr[0],qr[1]; measure qr[0] -> cr[0]; -measure qr[1] -> cr[1];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +measure qr[1] -> cr[1];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_multiple_composite_circuits_with_same_name(self): """Test circuit qasm() method when multiple composite circuit instructions @@ -175,10 +176,10 @@ def test_circuit_qasm_with_multiple_composite_circuits_with_same_name(self): qreg qr[1]; my_gate qr[0]; my_gate_{1} qr[0]; -my_gate_{0} qr[0];\n""".format( +my_gate_{0} qr[0];""".format( my_gate_inst3_id, my_gate_inst2_id ) - self.assertEqual(circuit.qasm(), expected_qasm) + self.assertEqual(dumps(circuit), expected_qasm) def test_circuit_qasm_with_composite_circuit_with_children_composite_circuit(self): """Test circuit qasm() method when composite circuits with children @@ -205,16 +206,16 @@ def test_circuit_qasm_with_composite_circuit_with_children_composite_circuit(sel gate parent_circ q0,q1,q2 { child_circ q0,q1; h q2; } gate grandparent_circ q0,q1,q2,q3 { parent_circ q0,q1,q2; x q3; } qreg q[4]; -grandparent_circ q[0],q[1],q[2],q[3];\n""" +grandparent_circ q[0],q[1],q[2],q[3];""" - self.assertEqual(qc.qasm(), expected_qasm) + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_pi(self): """Test circuit qasm() method with pi params.""" circuit = QuantumCircuit(2) circuit.cz(0, 1) circuit.u(2 * pi, 3 * pi, -5 * pi, 0) - qasm_str = circuit.qasm() + qasm_str = dumps(circuit) circuit2 = QuantumCircuit.from_qasm_str(qasm_str) self.assertEqual(circuit, circuit2) @@ -227,10 +228,10 @@ def test_circuit_qasm_with_composite_circuit_with_one_param(self): gate nG0(param0) q0 { h q0; } qreg q[3]; creg c[3]; -nG0(pi) q[0];\n""" +nG0(pi) q[0];""" qc = QuantumCircuit.from_qasm_str(original_str) - self.assertEqual(original_str, qc.qasm()) + self.assertEqual(original_str, dumps(qc)) def test_circuit_qasm_with_composite_circuit_with_many_params_and_qubits(self): """Test circuit qasm() method when a composite circuit instruction @@ -243,10 +244,10 @@ def test_circuit_qasm_with_composite_circuit_with_many_params_and_qubits(self): qreg r[3]; creg c[3]; creg d[3]; -nG0(pi,pi/2) q[0],r[0];\n""" +nG0(pi,pi/2) q[0],r[0];""" qc = QuantumCircuit.from_qasm_str(original_str) - self.assertEqual(original_str, qc.qasm()) + self.assertEqual(original_str, dumps(qc)) def test_c3sxgate_roundtrips(self): """Test that C3SXGate correctly round trips. @@ -256,12 +257,11 @@ def test_c3sxgate_roundtrips(self): resolution issues.""" qc = QuantumCircuit(4) qc.append(C3SXGate(), qc.qubits, []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; -c3sqrtx q[0],q[1],q[2],q[3]; -""" +c3sqrtx q[0],q[1],q[2],q[3];""" self.assertEqual(qasm, expected) parsed = QuantumCircuit.from_qasm_str(qasm) self.assertIsInstance(parsed.data[0].operation, C3SXGate) @@ -275,39 +275,36 @@ def test_cczgate_qasm(self): """Test that CCZ dumps definition as a non-qelib1 gate.""" qc = QuantumCircuit(3) qc.append(CCZGate(), qc.qubits, []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate ccz q0,q1,q2 { h q2; ccx q0,q1,q2; h q2; } qreg q[3]; -ccz q[0],q[1],q[2]; -""" +ccz q[0],q[1],q[2];""" self.assertEqual(qasm, expected) def test_csgate_qasm(self): """Test that CS dumps definition as a non-qelib1 gate.""" qc = QuantumCircuit(2) qc.append(CSGate(), qc.qubits, []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate cs q0,q1 { p(pi/4) q0; cx q0,q1; p(-pi/4) q1; cx q0,q1; p(pi/4) q1; } qreg q[2]; -cs q[0],q[1]; -""" +cs q[0],q[1];""" self.assertEqual(qasm, expected) def test_csdggate_qasm(self): """Test that CSdg dumps definition as a non-qelib1 gate.""" qc = QuantumCircuit(2) qc.append(CSdgGate(), qc.qubits, []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate csdg q0,q1 { p(-pi/4) q0; cx q0,q1; p(pi/4) q1; cx q0,q1; p(-pi/4) q1; } qreg q[2]; -csdg q[0],q[1]; -""" +csdg q[0],q[1];""" self.assertEqual(qasm, expected) def test_rzxgate_qasm(self): @@ -315,14 +312,13 @@ def test_rzxgate_qasm(self): qc = QuantumCircuit(2) qc.rzx(0, 0, 1) qc.rzx(pi / 2, 1, 0) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate rzx(param0) q0,q1 { h q1; cx q0,q1; rz(param0) q1; cx q0,q1; h q1; } qreg q[2]; rzx(0) q[0],q[1]; -rzx(pi/2) q[1],q[0]; -""" +rzx(pi/2) q[1],q[0];""" self.assertEqual(qasm, expected) def test_ecrgate_qasm(self): @@ -330,28 +326,26 @@ def test_ecrgate_qasm(self): qc = QuantumCircuit(2) qc.ecr(0, 1) qc.ecr(1, 0) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate rzx(param0) q0,q1 { h q1; cx q0,q1; rz(param0) q1; cx q0,q1; h q1; } gate ecr q0,q1 { rzx(pi/4) q0,q1; x q0; rzx(-pi/4) q0,q1; } qreg q[2]; ecr q[0],q[1]; -ecr q[1],q[0]; -""" +ecr q[1],q[0];""" self.assertEqual(qasm, expected) def test_unitary_qasm(self): """Test that UnitaryGate can be dumped to OQ2 correctly.""" qc = QuantumCircuit(1) qc.unitary([[1, 0], [0, 1]], 0) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate unitary q0 { u(0,0,0) q0; } qreg q[1]; -unitary q[0]; -""" +unitary q[0];""" self.assertEqual(qasm, expected) def test_multiple_unitary_qasm(self): @@ -363,7 +357,7 @@ def test_multiple_unitary_qasm(self): qc.unitary([[1, 0], [0, 1]], 0) qc.unitary([[0, 1], [1, 0]], 1) qc.append(custom.to_gate(), [0], []) - qasm = qc.qasm() + qasm = dumps(qc) expected = re.compile( r"""OPENQASM 2.0; include "qelib1.inc"; @@ -374,8 +368,7 @@ def test_multiple_unitary_qasm(self): qreg q\[2\]; unitary q\[0\]; (?P=u1) q\[1\]; -custom q\[0\]; -""", +custom q\[0\];""", re.MULTILINE, ) self.assertRegex(qasm, expected) @@ -386,7 +379,7 @@ def test_unbound_circuit_raises(self): theta = Parameter("θ") qc.rz(theta, 0) with self.assertRaises(QasmError): - qc.qasm() + dumps(qc) def test_gate_qasm_with_ctrl_state(self): """Test gate qasm() with controlled gate that has ctrl_state setting.""" @@ -394,7 +387,7 @@ def test_gate_qasm_with_ctrl_state(self): qc = QuantumCircuit(2) qc.ch(0, 1, ctrl_state=0) - qasm_str = qc.qasm() + qasm_str = dumps(qc) self.assertEqual(Operator(qc), Operator(QuantumCircuit.from_qasm_str(qasm_str))) def test_circuit_qasm_with_mcx_gate(self): @@ -410,8 +403,8 @@ def test_circuit_qasm_with_mcx_gate(self): include "qelib1.inc"; gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; } qreg q[4]; -mcx q[0],q[1],q[2],q[3];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +mcx q[0],q[1],q[2],q[3];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_mcx_gate_variants(self): """Test circuit qasm() method with MCXGrayCode, MCXRecursive, MCXVChain""" @@ -435,9 +428,9 @@ def test_circuit_qasm_with_mcx_gate_variants(self): qreg q[9]; mcx_gray q[0],q[1],q[2],q[3],q[4],q[5]; mcx_recursive q[0],q[1],q[2],q[3],q[4],q[5],q[6]; -mcx_vchain q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];\n""" +mcx_vchain q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];""" - self.assertEqual(qc.qasm(), expected_qasm) + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_registerless_bits(self): """Test that registerless bits do not have naming collisions in their registers.""" @@ -446,7 +439,7 @@ def test_circuit_qasm_with_registerless_bits(self): # Match a 'qreg identifier[3];'-like QASM register declaration. register_regex = re.compile(r"\s*[cq]reg\s+(\w+)\s*\[\d+\]\s*", re.M) qasm_register_names = set() - for statement in qc.qasm().split(";"): + for statement in dumps(qc).split(";"): match = register_regex.match(statement) if match: qasm_register_names.add(match.group(1)) @@ -462,7 +455,7 @@ def test_circuit_qasm_with_registerless_bits(self): for generated_name in generated_names: qc.add_register(QuantumRegister(1, name=generated_name)) qasm_register_names = set() - for statement in qc.qasm().split(";"): + for statement in dumps(qc).split(";"): match = register_regex.match(statement) if match: qasm_register_names.add(match.group(1)) @@ -498,9 +491,9 @@ def test_circuit_qasm_with_repeated_instruction_names(self): h q[0]; x q[1]; custom q[0]; -custom_{id(gate2)} q[1],q[0];\n""" +custom_{id(gate2)} q[1],q[0];""" # Check qasm() produced the correct string - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) # Check instruction names were not changed by qasm() names = ["h", "x", "custom", "custom"] for idx, instruction in enumerate(qc._data): @@ -539,12 +532,11 @@ def test_circuit_qasm_with_invalid_identifiers(self): "qreg q[2];", "gate_A___ q[0];", "invalid_name_ q[1],q[0];", - "", ] ) # Check qasm() produces the correct string - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) # Check instruction names were not changed by qasm() names = ["A[$]", "invalid[name]"] @@ -568,7 +560,7 @@ def test_circuit_qasm_with_duplicate_invalid_identifiers(self): # Check qasm is correctly produced names = set() - for match in re.findall(r"gate (\S+)", base.qasm()): + for match in re.findall(r"gate (\S+)", dumps(base)): self.assertTrue(VALID_QASM2_IDENTIFIER.fullmatch(match)) names.add(match) self.assertEqual(len(names), 2) @@ -584,15 +576,14 @@ def test_circuit_qasm_escapes_register_names(self): qc = QuantumCircuit(QuantumRegister(2, "?invalid"), QuantumRegister(2, "!invalid")) qc.cx(0, 1) qc.cx(2, 3) - qasm = qc.qasm() + qasm = dumps(qc) match = re.fullmatch( rf"""OPENQASM 2.0; include "qelib1.inc"; qreg ({VALID_QASM2_IDENTIFIER.pattern})\[2\]; qreg ({VALID_QASM2_IDENTIFIER.pattern})\[2\]; cx \1\[0\],\1\[1\]; -cx \2\[0\],\2\[1\]; -""", +cx \2\[0\],\2\[1\];""", qasm, ) self.assertTrue(match) @@ -604,14 +595,13 @@ def test_circuit_qasm_escapes_reserved(self): gate = Gate("gate", 1, []) gate.definition = QuantumCircuit(1) qc.append(gate, [qc.qubits[0]]) - qasm = qc.qasm() + qasm = dumps(qc) match = re.fullmatch( rf"""OPENQASM 2.0; include "qelib1.inc"; gate ({VALID_QASM2_IDENTIFIER.pattern}) q0 {{ }} qreg ({VALID_QASM2_IDENTIFIER.pattern})\[1\]; -\1 \2\[0\]; -""", +\1 \2\[0\];""", qasm, ) self.assertTrue(match) @@ -632,8 +622,8 @@ def test_circuit_qasm_with_double_precision_rotation_angle(self): qreg q[1]; p(0.123456789) q[0]; p(9.869604401089358) q[0]; -p(51.26548245743669) q[0];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +p(51.26548245743669) q[0];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_qasm_with_rotation_angles_close_to_pi(self): """Test that qasm() properly rounds values closer than 1e-12 to pi.""" @@ -645,8 +635,8 @@ def test_circuit_qasm_with_rotation_angles_close_to_pi(self): include "qelib1.inc"; qreg q[1]; p(3.141592653599793) q[0]; -p(pi) q[0];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +p(pi) q[0];""" + self.assertEqual(dumps(qc), expected_qasm) def test_circuit_raises_on_single_bit_condition(self): """OpenQASM 2 can't represent single-bit conditions, so test that a suitable error is @@ -655,7 +645,7 @@ def test_circuit_raises_on_single_bit_condition(self): qc.x(0).c_if(0, True) with self.assertRaisesRegex(QasmError, "OpenQASM 2 can only condition on registers"): - qc.qasm() + dumps(qc) def test_circuit_raises_invalid_custom_gate_no_qubits(self): """OpenQASM 2 exporter of custom gates with no qubits. @@ -665,7 +655,7 @@ def test_circuit_raises_invalid_custom_gate_no_qubits(self): legit_circuit.append(empty_circuit) with self.assertRaisesRegex(QasmError, "acts on zero qubits"): - legit_circuit.qasm() + dumps(legit_circuit) def test_circuit_raises_invalid_custom_gate_clbits(self): """OpenQASM 2 exporter of custom instruction. @@ -679,7 +669,7 @@ def test_circuit_raises_invalid_custom_gate_clbits(self): qc.append(custom_instruction, [0, 1], [0, 1]) with self.assertRaisesRegex(QasmError, "acts on 2 classical bits"): - qc.qasm() + dumps(qc) def test_circuit_qasm_with_permutations(self): """Test circuit qasm() method with Permutation gates.""" @@ -691,8 +681,8 @@ def test_circuit_qasm_with_permutations(self): include "qelib1.inc"; gate permutation__2_1_0_ q0,q1,q2 { swap q0,q2; } qreg q[4]; -permutation__2_1_0_ q[0],q[1],q[2];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +permutation__2_1_0_ q[0],q[1],q[2];""" + self.assertEqual(dumps(qc), expected_qasm) def test_multiple_permutation(self): """Test that multiple PermutationGates can be added to a circuit.""" @@ -704,7 +694,7 @@ def test_multiple_permutation(self): qc.append(PermutationGate([2, 1, 0]), [0, 1, 2], []) qc.append(PermutationGate([1, 2, 0]), [0, 1, 2], []) qc.append(custom.to_gate(), [1, 3, 2], []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; gate permutation__2_1_0_ q0,q1,q2 { swap q0,q2; } @@ -714,8 +704,7 @@ def test_multiple_permutation(self): qreg q[4]; permutation__2_1_0_ q[0],q[1],q[2]; permutation__1_2_0_ q[0],q[1],q[2]; -custom q[1],q[3],q[2]; -""" +custom q[1],q[3],q[2];""" self.assertEqual(qasm, expected) def test_circuit_qasm_with_reset(self): @@ -727,8 +716,8 @@ def test_circuit_qasm_with_reset(self): include "qelib1.inc"; qreg q[2]; reset q[0]; -reset q[1];\n""" - self.assertEqual(qc.qasm(), expected_qasm) +reset q[1];""" + self.assertEqual(dumps(qc), expected_qasm) def test_nested_gate_naming_clashes(self): """Test that gates that have naming clashes but only appear in the body of another gate @@ -755,7 +744,7 @@ def _define(self): qc = QuantumCircuit(1) qc.append(Outer(1.0), [0], []) qc.append(Outer(2.0), [0], []) - qasm = qc.qasm() + qasm = dumps(qc) expected = re.compile( r"""OPENQASM 2\.0; @@ -766,8 +755,7 @@ def _define(self): gate (?Pouter_[0-9]*)\(param0\) q0 { (?P=inner1)\(2\.0\) q0; } qreg q\[1\]; outer\(1\.0\) q\[0\]; -(?P=outer1)\(2\.0\) q\[0\]; -""", +(?P=outer1)\(2\.0\) q\[0\];""", re.MULTILINE, ) self.assertRegex(qasm, expected) @@ -782,7 +770,7 @@ def test_opaque_output(self): qc.append(Gate("my_a", 1, []), [1]) qc.append(Gate("my_b", 2, [1.0]), [1, 0]) qc.append(custom.to_gate(), [0], []) - qasm = qc.qasm() + qasm = dumps(qc) expected = """OPENQASM 2.0; include "qelib1.inc"; opaque my_a q0; @@ -793,8 +781,7 @@ def test_opaque_output(self): my_a q[0]; my_a q[1]; my_b(1.0) q[1],q[0]; -custom q[0]; -""" +custom q[0];""" self.assertEqual(qasm, expected) def test_sequencial_inner_gates_with_same_name(self): @@ -824,10 +811,9 @@ def test_sequencial_inner_gates_with_same_name(self): a_{gate_a_id} q[0],q[1],q[2]; z q[0]; z q[1]; -z q[2]; -""" +z q[2];""" - self.assertEqual(qc.qasm(), expected_output) + self.assertEqual(dumps(qc), expected_output) def test_empty_barrier(self): """Test that a blank barrier statement in _Qiskit_ acts over all qubits, while an explicitly @@ -842,9 +828,8 @@ def test_empty_barrier(self): include "qelib1.inc"; qreg qr1[2]; qreg qr2[3]; -barrier qr1[0],qr1[1],qr2[0],qr2[1],qr2[2]; -""" - self.assertEqual(qc.qasm(), expected) +barrier qr1[0],qr1[1],qr2[0],qr2[1],qr2[2];""" + self.assertEqual(dumps(qc), expected) def test_small_angle_valid(self): """Test that small angles do not get converted to invalid OQ2 floating-point values.""" @@ -856,9 +841,8 @@ def test_small_angle_valid(self): OPENQASM 2.0; include "qelib1.inc"; qreg q[1]; -rx(1.e-06) q[0]; -""" - self.assertEqual(qc.qasm(), expected) +rx(1.e-06) q[0];""" + self.assertEqual(dumps(qc), expected) if __name__ == "__main__": diff --git a/test/python/circuit/test_circuit_registers.py b/test/python/circuit/test_circuit_registers.py index d6a1a63cb469..ca0ce6d0bed3 100644 --- a/test/python/circuit/test_circuit_registers.py +++ b/test/python/circuit/test_circuit_registers.py @@ -26,6 +26,7 @@ ) from qiskit.circuit.exceptions import CircuitError from qiskit.test import QiskitTestCase +from qiskit.qasm2 import dumps class TestCircuitRegisters(QiskitTestCase): @@ -284,7 +285,7 @@ def test_apply_barrier_to_slice(self): num_qubits = 2 qc = QuantumCircuit(qr, cr) qc.barrier(qr[0:num_qubits]) - self.log.info(qc.qasm()) + self.log.info(dumps(qc)) self.assertEqual(len(qc.data), 1) self.assertEqual(qc.data[0].operation.name, "barrier") self.assertEqual(len(qc.data[0].qubits), num_qubits) diff --git a/test/python/circuit/test_instructions.py b/test/python/circuit/test_instructions.py index fc60be60fdc1..d111d2d21181 100644 --- a/test/python/circuit/test_instructions.py +++ b/test/python/circuit/test_instructions.py @@ -18,16 +18,20 @@ import numpy as np -from qiskit.circuit import Gate -from qiskit.circuit import Parameter -from qiskit.circuit import Instruction, InstructionSet -from qiskit.circuit import QuantumCircuit -from qiskit.circuit import QuantumRegister, ClassicalRegister, Qubit, Clbit -from qiskit.circuit.library.standard_gates.h import HGate -from qiskit.circuit.library.standard_gates.rz import RZGate -from qiskit.circuit.library.standard_gates.x import CXGate -from qiskit.circuit.library.standard_gates.s import SGate -from qiskit.circuit.library.standard_gates.t import TGate +from qiskit.circuit import ( + Gate, + Parameter, + Instruction, + InstructionSet, + QuantumCircuit, + QuantumRegister, + ClassicalRegister, + Qubit, + Clbit, + IfElseOp, +) +from qiskit.circuit.library import HGate, RZGate, CXGate, SGate, TGate +from qiskit.circuit.classical import expr from qiskit.test import QiskitTestCase from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.random import random_circuit @@ -426,6 +430,26 @@ def test_repr_of_instructions(self): ), ) + def test_instruction_condition_bits(self): + """Test that the ``condition_bits`` property behaves correctly until it is deprecated and + removed.""" + bits = [Clbit(), Clbit()] + cr1 = ClassicalRegister(2, "cr1") + cr2 = ClassicalRegister(2, "cr2") + body = QuantumCircuit(cr1, cr2, bits) + + def key(bit): + return body.find_bit(bit).index + + op = IfElseOp((bits[0], False), body) + self.assertEqual(op.condition_bits, [bits[0]]) + + op = IfElseOp((cr1, 3), body) + self.assertEqual(op.condition_bits, list(cr1)) + + op = IfElseOp(expr.logic_and(bits[1], expr.equal(cr2, 3)), body) + self.assertEqual(sorted(op.condition_bits, key=key), sorted([bits[1]] + list(cr2), key=key)) + def test_instructionset_c_if_direct_resource(self): """Test that using :meth:`.InstructionSet.c_if` with an exact classical resource always works, and produces the expected condition.""" diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index c694b39e6e7a..6a5f780a96af 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -1332,7 +1332,14 @@ def _paramvec_names(prefix, length): class TestParameterExpressions(QiskitTestCase): """Test expressions of Parameters.""" - supported_operations = [add, sub, mul, truediv] + # supported operations dictionary operation : accuracy (0=exact match) + supported_operations = { + add: 0, + sub: 0, + mul: 0, + truediv: 0, + pow: 1e-12, + } def setUp(self): super().setUp() @@ -1504,12 +1511,19 @@ def test_expressions_of_parameter_with_constant(self): x = Parameter("x") - for op in self.supported_operations: + for op, rel_tol in self.supported_operations.items(): for const in good_constants: expr = op(const, x) bound_expr = expr.bind({x: 2.3}) - self.assertEqual(complex(bound_expr), op(const, 2.3)) + res = complex(bound_expr) + expected = op(const, 2.3) + if rel_tol > 0: + self.assertTrue( + cmath.isclose(res, expected, rel_tol=rel_tol), f"{res} != {expected}" + ) + else: + self.assertEqual(res, expected) # Division by zero will raise. Tested elsewhere. if const == 0 and op == truediv: @@ -1954,6 +1968,21 @@ def test_parameter_expression_grad(self): self.assertEqual(expr.gradient(x), 2 * x) self.assertEqual(expr.gradient(x).gradient(x), 2) + def test_parameter_expression_exp_log_vs_pow(self): + """Test exp, log, pow for ParameterExpressions by asserting x**y = exp(y log(x)).""" + + x = Parameter("x") + y = Parameter("y") + pow1 = x**y + pow2 = (y * x.log()).exp() + for x_val in [2, 1.3, numpy.pi]: + for y_val in [2, 1.3, 0, -1, -1.0, numpy.pi, 1j]: + with self.subTest(msg="with x={x_val}, y={y_val}"): + vals = {x: x_val, y: y_val} + pow1_val = pow1.bind(vals) + pow2_val = pow2.bind(vals) + self.assertTrue(cmath.isclose(pow1_val, pow2_val), f"{pow1_val} != {pow2_val}") + def test_bound_expression_is_real(self): """Test is_real on bound parameters.""" x = Parameter("x") diff --git a/test/python/circuit/test_unitary.py b/test/python/circuit/test_unitary.py index 27367372f329..26f3eb3fea2e 100644 --- a/test/python/circuit/test_unitary.py +++ b/test/python/circuit/test_unitary.py @@ -25,6 +25,7 @@ from qiskit.quantum_info.random import random_unitary from qiskit.quantum_info.operators import Operator from qiskit.transpiler.passes import CXCancellation +from qiskit.qasm2 import dumps class TestUnitaryGate(QiskitTestCase): @@ -79,7 +80,7 @@ def test_1q_unitary(self): qc.x(qr[0]) qc.append(UnitaryGate(matrix), [qr[0]]) # test of qasm output - self.log.info(qc.qasm()) + self.log.info(dumps(qc)) # test of text drawer self.log.info(qc) dag = circuit_to_dag(qc) @@ -105,7 +106,7 @@ def test_2q_unitary(self): passman.append(CXCancellation()) qc2 = passman.run(qc) # test of qasm output - self.log.info(qc2.qasm()) + self.log.info(dumps(qc2)) # test of text drawer self.log.info(qc2) dag = circuit_to_dag(qc) @@ -221,9 +222,9 @@ def test_qasm_unitary_only_one_def(self): "qreg q0[2];\ncreg c0[1];\n" "x q0[0];\n" "unitary q0[0];\n" - "unitary q0[1];\n" + "unitary q0[1];" ) - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) def test_qasm_unitary_twice(self): """test that a custom unitary can be converted to qasm and that if @@ -245,10 +246,10 @@ def test_qasm_unitary_twice(self): "qreg q0[2];\ncreg c0[1];\n" "x q0[0];\n" "unitary q0[0];\n" - "unitary q0[1];\n" + "unitary q0[1];" ) - self.assertEqual(expected_qasm, qc.qasm()) - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) + self.assertEqual(expected_qasm, dumps(qc)) def test_qasm_2q_unitary(self): """test that a 2 qubit custom unitary can be converted to qasm""" @@ -270,9 +271,9 @@ def test_qasm_2q_unitary(self): "creg c0[1];\n" "x q0[0];\n" "unitary q0[0],q0[1];\n" - "unitary q0[1],q0[0];\n" + "unitary q0[1],q0[0];" ) - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) def test_qasm_unitary_noop(self): """Test that an identity unitary can be converted to OpenQASM 2""" @@ -283,9 +284,9 @@ def test_qasm_unitary_noop(self): 'include "qelib1.inc";\n' "gate unitary q0,q1,q2 { }\n" "qreg q0[3];\n" - "unitary q0[0],q0[1],q0[2];\n" + "unitary q0[0],q0[1],q0[2];" ) - self.assertEqual(expected_qasm, qc.qasm()) + self.assertEqual(expected_qasm, dumps(qc)) def test_unitary_decomposition(self): """Test decomposition for unitary gates over 2 qubits.""" diff --git a/test/python/compiler/test_compiler.py b/test/python/compiler/test_compiler.py index 910d1e5866fe..627d5310531f 100644 --- a/test/python/compiler/test_compiler.py +++ b/test/python/compiler/test_compiler.py @@ -24,6 +24,7 @@ from qiskit.test import QiskitTestCase from qiskit.providers.fake_provider import FakeRueschlikon, FakeTenerife from qiskit.qobj import QasmQobj +from qiskit.qasm2 import dumps class TestCompiler(QiskitTestCase): @@ -100,8 +101,8 @@ def test_compile_coupling_map(self): ) job = backend.run(qc_b, shots=shots, seed_simulator=88) result = job.result() - qasm_to_check = qc.qasm() - self.assertEqual(len(qasm_to_check), 173) + qasm_to_check = dumps(qc) + self.assertEqual(len(qasm_to_check), 172) counts = result.get_counts(qc) target = {"000": shots / 2, "111": shots / 2} diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 98db50a28b08..2a13f35adfb9 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1696,6 +1696,38 @@ def test_paulis_to_constrained_1q_basis(self, opt_level, basis): self.assertGreaterEqual(set(basis) | {"barrier"}, transpiled.count_ops().keys()) self.assertEqual(Operator(qc), Operator(transpiled)) + @data(0, 1, 2, 3) + def test_barrier_not_output(self, opt_level): + """Test that barriers added as part internal transpiler operations do not leak out.""" + qc = QuantumCircuit(2, 2) + qc.cx(0, 1) + qc.measure(range(2), range(2)) + tqc = transpile( + qc, + initial_layout=[1, 4], + coupling_map=[[1, 2], [2, 3], [3, 4]], + optimization_level=opt_level, + ) + self.assertNotIn("barrier", tqc.count_ops()) + + @data(0, 1, 2, 3) + def test_barrier_not_output_input_preservered(self, opt_level): + """Test that barriers added as part internal transpiler operations do not leak out.""" + qc = QuantumCircuit(2, 2) + qc.cx(0, 1) + qc.measure_all() + tqc = transpile( + qc, + initial_layout=[1, 4], + coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4]], + optimization_level=opt_level, + ) + op_counts = tqc.count_ops() + self.assertEqual(op_counts["barrier"], 1) + for inst in tqc.data: + if inst.operation.name == "barrier": + self.assertEqual(len(inst.qubits), 2) + @combine(opt_level=[0, 1, 2, 3]) def test_transpile_annotated_ops(self, opt_level): """Test transpilation of circuits with annotated operations.""" diff --git a/test/python/converters/test_ast_to_dag.py b/test/python/converters/test_ast_to_dag.py deleted file mode 100644 index 490465b5967b..000000000000 --- a/test/python/converters/test_ast_to_dag.py +++ /dev/null @@ -1,67 +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. - -"""Tests for the converters.""" - -import os -import unittest - -from qiskit.converters import ast_to_dag, circuit_to_dag -from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit -from qiskit import qasm -from qiskit.test import QiskitTestCase - - -class TestAstToDag(QiskitTestCase): - """Test AST to DAG.""" - - def setUp(self): - super().setUp() - qr = QuantumRegister(3) - cr = ClassicalRegister(3) - self.circuit = QuantumCircuit(qr, cr) - self.circuit.ccx(qr[0], qr[1], qr[2]) - self.circuit.measure(qr, cr) - self.dag = circuit_to_dag(self.circuit) - - def test_from_ast_to_dag(self): - """Test Unroller.execute()""" - qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") - ast = qasm.Qasm(os.path.join(qasm_dir, "example.qasm")).parse() - dag_circuit = ast_to_dag(ast) - expected_result = """\ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[3]; -qreg r[3]; -creg c[3]; -creg d[3]; -h q[0]; -h q[1]; -h q[2]; -cx q[0],r[0]; -cx q[1],r[1]; -cx q[2],r[2]; -barrier q[0],q[1],q[2]; -measure q[0] -> c[0]; -measure q[1] -> c[1]; -measure q[2] -> c[2]; -measure r[0] -> d[0]; -measure r[1] -> d[1]; -measure r[2] -> d[2]; -""" - expected_dag = circuit_to_dag(QuantumCircuit.from_qasm_str(expected_result)) - self.assertEqual(dag_circuit, expected_dag) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/test/python/opflow/__init__.py b/test/python/opflow/__init__.py deleted file mode 100644 index 16d56b668594..000000000000 --- a/test/python/opflow/__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. - -"""Opflow test module""" - -from .opflow_test_case import QiskitOpflowTestCase - -__all__ = ["QiskitOpflowTestCase"] diff --git a/test/python/opflow/opflow_test_case.py b/test/python/opflow/opflow_test_case.py deleted file mode 100644 index 142fb1db76b5..000000000000 --- a/test/python/opflow/opflow_test_case.py +++ /dev/null @@ -1,30 +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. - -"""Opflow Test Case""" - -import warnings -from qiskit.test import QiskitTestCase - - -class QiskitOpflowTestCase(QiskitTestCase): - """Opflow test Case""" - - def setUp(self): - super().setUp() - # ignore opflow msgs - warnings.filterwarnings("ignore", category=DeprecationWarning, message=r".*opflow.*") - - def tearDown(self): - super().tearDown() - # restore opflow msgs - warnings.filterwarnings("error", category=DeprecationWarning, message=r".*opflow.*") diff --git a/test/python/opflow/test_abelian_grouper.py b/test/python/opflow/test_abelian_grouper.py deleted file mode 100644 index 4fd6cf2a85ba..000000000000 --- a/test/python/opflow/test_abelian_grouper.py +++ /dev/null @@ -1,133 +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. - -"""Test Abelian Grouper""" - -import random -import unittest -from itertools import combinations, product -from test.python.opflow import QiskitOpflowTestCase - -from ddt import data, ddt, unpack - -from qiskit.opflow import AbelianGrouper, commutator, I, OpflowError, Plus, SummedOp, X, Y, Z, Zero - - -@ddt -class TestAbelianGrouper(QiskitOpflowTestCase): - """Abelian Grouper tests.""" - - @data(*product(["h2_op", "generic"], [True, False])) - @unpack - def test_abelian_grouper(self, pauli_op, is_summed_op): - """Abelian grouper test""" - if pauli_op == "h2_op": - paulis = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) - + (0.18093119978423156 * X ^ X) - ) - num_groups = 2 - else: - paulis = ( - (I ^ I ^ X ^ X * 0.2) - + (Z ^ Z ^ X ^ X * 0.3) - + (Z ^ Z ^ Z ^ Z * 0.4) - + (X ^ X ^ Z ^ Z * 0.5) - + (X ^ X ^ X ^ X * 0.6) - + (I ^ X ^ X ^ X * 0.7) - ) - num_groups = 4 - if is_summed_op: - paulis = paulis.to_pauli_op() - grouped_sum = AbelianGrouper().convert(paulis) - self.assertEqual(len(grouped_sum.oplist), num_groups) - for group in grouped_sum: - for op_1, op_2 in combinations(group, 2): - if is_summed_op: - self.assertEqual(op_1 @ op_2, op_2 @ op_1) - else: - self.assertTrue(commutator(op_1, op_2).is_zero()) - - def test_ablian_grouper_no_commute(self): - """Abelian grouper test when non-PauliOp is given""" - ops = Zero ^ Plus + X ^ Y - with self.assertRaises(OpflowError): - _ = AbelianGrouper.group_subops(ops) - - @data(True, False) - def test_group_subops(self, is_summed_op): - """grouper subroutine test""" - paulis = (I ^ X) + (2 * X ^ X) + (3 * Z ^ Y) - if is_summed_op: - paulis = paulis.to_pauli_op() - grouped_sum = AbelianGrouper.group_subops(paulis) - self.assertEqual(len(grouped_sum), 2) - with self.subTest("test group subops 1"): - if is_summed_op: - expected = SummedOp( - [ - SummedOp([I ^ X, 2.0 * X ^ X], abelian=True), - SummedOp([3.0 * Z ^ Y], abelian=True), - ] - ) - self.assertEqual(grouped_sum, expected) - else: - self.assertSetEqual( - frozenset(frozenset(grouped_sum[i].primitive.to_list()) for i in range(2)), - frozenset({frozenset({("ZY", 3)}), frozenset({("IX", 1), ("XX", 2)})}), - ) - - paulis = X + (2 * Y) + (3 * Z) - if is_summed_op: - paulis = paulis.to_pauli_op() - grouped_sum = AbelianGrouper.group_subops(paulis) - self.assertEqual(len(grouped_sum), 3) - with self.subTest("test group subops 2"): - if is_summed_op: - self.assertEqual(grouped_sum, paulis) - else: - self.assertSetEqual( - frozenset(sum((grouped_sum[i].primitive.to_list() for i in range(3)), [])), - frozenset([("X", 1), ("Y", 2), ("Z", 3)]), - ) - - @data(True, False) - def test_abelian_grouper_random(self, is_summed_op): - """Abelian grouper test with random paulis""" - random.seed(1234) - k = 10 # size of pauli operators - n = 100 # number of pauli operators - num_tests = 20 # number of tests - for _ in range(num_tests): - paulis = [] - for _ in range(n): - pauliop = 1 - for eachop in random.choices([I] * 5 + [X, Y, Z], k=k): - pauliop ^= eachop - paulis.append(pauliop) - pauli_sum = sum(paulis) - if is_summed_op: - pauli_sum = pauli_sum.to_pauli_op() - grouped_sum = AbelianGrouper().convert(pauli_sum) - for group in grouped_sum: - for op_1, op_2 in combinations(group, 2): - if is_summed_op: - self.assertEqual(op_1 @ op_2, op_2 @ op_1) - else: - self.assertTrue(commutator(op_1, op_2).is_zero()) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_aer_pauli_expectation.py b/test/python/opflow/test_aer_pauli_expectation.py deleted file mode 100644 index 808f7bab8716..000000000000 --- a/test/python/opflow/test_aer_pauli_expectation.py +++ /dev/null @@ -1,297 +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 AerPauliExpectation""" - -import itertools -import unittest -from test.python.opflow import QiskitOpflowTestCase -import numpy as np - -from qiskit.circuit.library import RealAmplitudes -from qiskit.opflow import ( - CX, - AerPauliExpectation, - CircuitSampler, - CircuitStateFn, - H, - I, - ListOp, - Minus, - One, - PauliExpectation, - PauliSumOp, - Plus, - S, - StateFn, - X, - Y, - Z, - Zero, - MatrixOp, -) -from qiskit.utils import QuantumInstance, optionals - - -class TestAerPauliExpectation(QiskitOpflowTestCase): - """Pauli Change of Basis Expectation tests.""" - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") - def setUp(self) -> None: - super().setUp() - from qiskit_aer import AerSimulator - - self.seed = 97 - self.backend = AerSimulator() - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - self.backend, seed_simulator=self.seed, seed_transpiler=self.seed - ) - self.sampler = CircuitSampler(q_instance, attach_results=True) - self.expect = AerPauliExpectation() - - def test_pauli_expect_pair(self): - """pauli expect pair test""" - op = Z ^ Z - # wvf = (Pl^Pl) + (Ze^Ze) - wvf = CX @ (H ^ I) @ Zero - converted_meas = self.expect.convert(~StateFn(op) @ wvf) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - self.assertAlmostEqual(sampled.eval(), 0, delta=0.1) - - def test_pauli_expect_single(self): - """pauli expect single test""" - paulis = [Z, X, Y, I] - states = [Zero, One, Plus, Minus, S @ Plus, S @ Minus] - for pauli, state in itertools.product(paulis, states): - converted_meas = self.expect.convert(~StateFn(pauli) @ state) - matmulmean = state.adjoint().to_matrix() @ pauli.to_matrix() @ state.to_matrix() - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - self.assertAlmostEqual(sampled.eval(), matmulmean, delta=0.1) - - def test_pauli_expect_op_vector(self): - """pauli expect op vector test""" - paulis_op = ListOp([X, Y, Z, I]) - converted_meas = self.expect.convert(~StateFn(paulis_op)) - - with self.assertWarns(DeprecationWarning): - plus_mean = converted_meas @ Plus - sampled_plus = self.sampler.convert(plus_mean) - np.testing.assert_array_almost_equal(sampled_plus.eval(), [1, 0, 0, 1], decimal=1) - - minus_mean = converted_meas @ Minus - sampled_minus = self.sampler.convert(minus_mean) - np.testing.assert_array_almost_equal(sampled_minus.eval(), [-1, 0, 0, 1], decimal=1) - - zero_mean = converted_meas @ Zero - sampled_zero = self.sampler.convert(zero_mean) - np.testing.assert_array_almost_equal(sampled_zero.eval(), [0, 0, 1, 1], decimal=1) - - sum_zero = (Plus + Minus) * (0.5**0.5) - sum_zero_mean = converted_meas @ sum_zero - sampled_zero_mean = self.sampler.convert(sum_zero_mean) - - # !!NOTE!!: Depolarizing channel (Sampling) means interference - # does not happen between circuits in sum, so expectation does - # not equal expectation for Zero!! - np.testing.assert_array_almost_equal(sampled_zero_mean.eval(), [0, 0, 0, 1]) - - def test_pauli_expect_state_vector(self): - """pauli expect state vector test""" - states_op = ListOp([One, Zero, Plus, Minus]) - paulis_op = X - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - - # Small test to see if execution results are accessible - for composed_op in sampled: - self.assertTrue(hasattr(composed_op[0], "execution_results")) - - np.testing.assert_array_almost_equal(sampled.eval(), [0, 0, 1, -1], decimal=1) - - def test_pauli_expect_non_hermitian_matrixop(self): - """pauli expect state vector with non hermitian operator test""" - states_op = ListOp([One, Zero, Plus, Minus]) - op_mat = np.array([[0, 1], [2, 3]]) - op = MatrixOp(op_mat) - converted_meas = self.expect.convert(StateFn(op, is_measurement=True) @ states_op) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - - np.testing.assert_array_almost_equal(sampled.eval(), [3, 0, 3, 0], decimal=1) - - def test_pauli_expect_non_hermitian_pauliop(self): - """pauli expect state vector with non hermitian operator test""" - states_op = ListOp([One, Zero, Plus, Minus]) - op = 1j * X - converted_meas = self.expect.convert(StateFn(op, is_measurement=True) @ states_op) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - np.testing.assert_array_almost_equal(sampled.eval(), [0, 0, 1j, -1j], decimal=1) - - def test_pauli_expect_op_vector_state_vector(self): - """pauli expect op vector state vector test""" - paulis_op = ListOp([X, Y, Z, I]) - states_op = ListOp([One, Zero, Plus, Minus]) - - valids = [ - [+0, 0, 1, -1], - [+0, 0, 0, 0], - [-1, 1, 0, -0], - [+1, 1, 1, 1], - ] - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - np.testing.assert_array_almost_equal(sampled.eval(), valids, decimal=1) - - def test_multi_representation_ops(self): - """Test observables with mixed representations""" - - mixed_ops = ListOp([X.to_matrix_op(), H, H + I, X]) - converted_meas = self.expect.convert(~StateFn(mixed_ops)) - - plus_mean = converted_meas @ Plus - with self.assertWarns(DeprecationWarning): - sampled_plus = self.sampler.convert(plus_mean) - - np.testing.assert_array_almost_equal( - sampled_plus.eval(), [1, 0.5**0.5, (1 + 0.5**0.5), 1], decimal=1 - ) - - def test_parameterized_qobj(self): - """grouped pauli expectation test""" - - two_qubit_h2 = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) - + (0.18093119978423156 * X ^ X) - ) - with self.assertWarns(DeprecationWarning): - aer_sampler = CircuitSampler( - self.sampler.quantum_instance, param_qobj=True, attach_results=True - ) - ansatz = RealAmplitudes() - ansatz.num_qubits = 2 - - observable_meas = self.expect.convert(StateFn(two_qubit_h2, is_measurement=True)) - ansatz_circuit_op = CircuitStateFn(ansatz) - expect_op = observable_meas.compose(ansatz_circuit_op).reduce() - - def generate_parameters(num): - param_bindings = {} - for param in ansatz.parameters: - values = [] - for _ in range(num): - values.append(np.random.rand()) - param_bindings[param] = values - return param_bindings - - def validate_sampler(ideal, sut, param_bindings): - with self.assertWarns(DeprecationWarning): - expect_sampled = ideal.convert(expect_op, params=param_bindings).eval() - actual_sampled = sut.convert(expect_op, params=param_bindings).eval() - self.assertTrue( - np.allclose(actual_sampled, expect_sampled), - f"{actual_sampled} != {expect_sampled}", - ) - - def get_circuit_templates(sampler): - return sampler._transpiled_circ_templates - - def validate_aer_binding_used(templates): - self.assertIsNotNone(templates) - - def validate_aer_templates_reused(prev_templates, cur_templates): - self.assertIs(prev_templates, cur_templates) - - validate_sampler(self.sampler, aer_sampler, generate_parameters(1)) - cur_templates = get_circuit_templates(aer_sampler) - - validate_aer_binding_used(cur_templates) - - prev_templates = cur_templates - validate_sampler(self.sampler, aer_sampler, generate_parameters(2)) - cur_templates = get_circuit_templates(aer_sampler) - - validate_aer_templates_reused(prev_templates, cur_templates) - - prev_templates = cur_templates - validate_sampler(self.sampler, aer_sampler, generate_parameters(2)) # same num of params - cur_templates = get_circuit_templates(aer_sampler) - - validate_aer_templates_reused(prev_templates, cur_templates) - - def test_pauli_expectation_param_qobj(self): - """Test PauliExpectation with param_qobj""" - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - self.backend, seed_simulator=self.seed, seed_transpiler=self.seed, shots=10000 - ) - qubit_op = (0.1 * I ^ I) + (0.2 * I ^ Z) + (0.3 * Z ^ I) + (0.4 * Z ^ Z) + (0.5 * X ^ X) - ansatz = RealAmplitudes(qubit_op.num_qubits) - ansatz_circuit_op = CircuitStateFn(ansatz) - observable = PauliExpectation().convert(~StateFn(qubit_op)) - expect_op = observable.compose(ansatz_circuit_op).reduce() - params1 = {} - params2 = {} - for param in ansatz.parameters: - params1[param] = [0] - params2[param] = [0, 0] - - with self.assertWarns(DeprecationWarning): - sampler1 = CircuitSampler(backend=q_instance, param_qobj=False) - samples1 = sampler1.convert(expect_op, params=params1) - val1 = np.real(samples1.eval())[0] - samples2 = sampler1.convert(expect_op, params=params2) - val2 = np.real(samples2.eval()) - sampler2 = CircuitSampler(backend=q_instance, param_qobj=True) - samples3 = sampler2.convert(expect_op, params=params1) - val3 = np.real(samples3.eval()) - samples4 = sampler2.convert(expect_op, params=params2) - val4 = np.real(samples4.eval()) - - np.testing.assert_array_almost_equal([val1] * 2, val2, decimal=2) - np.testing.assert_array_almost_equal(val1, val3, decimal=2) - np.testing.assert_array_almost_equal([val1] * 2, val4, decimal=2) - - def test_list_pauli_sum(self): - """Test AerPauliExpectation for ListOp[PauliSumOp]""" - test_op = ListOp([PauliSumOp.from_list([("XX", 1), ("ZI", 3), ("ZZ", 5)])]) - observable = AerPauliExpectation().convert(~StateFn(test_op)) - self.assertIsInstance(observable, ListOp) - self.assertIsInstance(observable[0], CircuitStateFn) - self.assertTrue(observable[0].is_measurement) - - def test_expectation_with_coeff(self): - """Test AerPauliExpectation with coefficients.""" - with self.subTest("integer coefficients"): - exp = 3 * ~StateFn(X) @ (2 * Minus) - with self.assertWarns(DeprecationWarning): - target = self.sampler.convert(self.expect.convert(exp)).eval() - self.assertAlmostEqual(target, -12) - - with self.subTest("complex coefficients"): - exp = 3j * ~StateFn(X) @ (2j * Minus) - with self.assertWarns(DeprecationWarning): - target = self.sampler.convert(self.expect.convert(exp)).eval() - self.assertAlmostEqual(target, -12j) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_cvar.py b/test/python/opflow/test_cvar.py deleted file mode 100644 index 4d38356f2718..000000000000 --- a/test/python/opflow/test_cvar.py +++ /dev/null @@ -1,261 +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. - -"""The Conditional Value at Risk (CVaR) measurement.""" - -import unittest -import warnings - -from test.python.opflow import QiskitOpflowTestCase -import numpy as np -from ddt import ddt, data - -from qiskit import QuantumCircuit -from qiskit.utils import algorithm_globals -from qiskit.opflow import ( - CVaRMeasurement, - StateFn, - Z, - I, - X, - Y, - Plus, - PauliSumOp, - PauliExpectation, - MatrixExpectation, - CVaRExpectation, - ListOp, - CircuitOp, - AerPauliExpectation, - MatrixOp, - OpflowError, -) - - -class TestCVaRMeasurement(QiskitOpflowTestCase): - """Test the CVaR measurement.""" - - def expected_cvar(self, statevector, operator, alpha): - """Compute the expected CVaR expected value.""" - - probabilities = statevector * np.conj(statevector) - - # get energies - num_bits = int(np.log2(len(statevector))) - energies = [] - for i, _ in enumerate(probabilities): - basis_state = np.binary_repr(i, num_bits) - energies += [operator.eval(basis_state).eval(basis_state)] - - # sort ascending - i_sorted = np.argsort(energies) - energies = [energies[i] for i in i_sorted] - probabilities = [probabilities[i] for i in i_sorted] - - # add up - result = 0 - accumulated_probabilities = 0 - for energy, probability in zip(energies, probabilities): - accumulated_probabilities += probability - if accumulated_probabilities <= alpha: - result += probability * energy - else: # final term - result += (alpha - accumulated_probabilities + probability) * energy - break - - return result / alpha - - def cleanup_algorithm_globals(self, massive): - """Method used to reset the values of algorithm_globals.""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.massive = massive - - def test_cvar_simple(self): - """Test a simple case with a single Pauli.""" - theta = 1.2 - qc = QuantumCircuit(1) - qc.ry(theta, 0) - statefn = StateFn(qc) - - for alpha in [0.2, 0.4, 1]: - with self.subTest(alpha=alpha): - cvar = (CVaRMeasurement(Z, alpha) @ statefn).eval() - ref = self.expected_cvar(statefn.to_matrix(), Z, alpha) - self.assertAlmostEqual(cvar, ref) - - def test_cvar_simple_with_coeff(self): - """Test a simple case with a non-unity coefficient""" - theta = 2.2 - qc = QuantumCircuit(1) - qc.ry(theta, 0) - statefn = StateFn(qc) - - alpha = 0.2 - cvar = ((-1 * CVaRMeasurement(Z, alpha)) @ statefn).eval() - ref = self.expected_cvar(statefn.to_matrix(), Z, alpha) - self.assertAlmostEqual(cvar, -1 * ref) - - def test_add(self): - """Test addition.""" - theta = 2.2 - qc = QuantumCircuit(1) - qc.ry(theta, 0) - statefn = StateFn(qc) - - alpha = 0.2 - cvar = -1 * CVaRMeasurement(Z, alpha) - ref = self.expected_cvar(statefn.to_matrix(), Z, alpha) - - other = ~StateFn(I) - - # test add in both directions - res1 = ((cvar + other) @ statefn).eval() - res2 = ((other + other) @ statefn).eval() - - self.assertAlmostEqual(res1, 1 - ref) - self.assertAlmostEqual(res2, 1 - ref) - - def invalid_input(self): - """Test invalid input raises an error.""" - op = Z - - with self.subTest("alpha < 0"): - with self.assertRaises(ValueError): - _ = CVaRMeasurement(op, alpha=-0.2) - - with self.subTest("alpha > 1"): - with self.assertRaises(ValueError): - _ = CVaRMeasurement(op, alpha=12.3) - - with self.subTest("Single pauli operator not diagonal"): - op = Y - with self.assertRaises(OpflowError): - _ = CVaRMeasurement(op) - - with self.subTest("Summed pauli operator not diagonal"): - op = X ^ Z + Z ^ I - with self.assertRaises(OpflowError): - _ = CVaRMeasurement(op) - - with self.subTest("List operator not diagonal"): - op = ListOp([X ^ Z, Z ^ I]) - with self.assertRaises(OpflowError): - _ = CVaRMeasurement(op) - - with self.subTest("Matrix operator not diagonal"): - op = MatrixOp([[1, 1], [0, 1]]) - with self.assertRaises(OpflowError): - _ = CVaRMeasurement(op) - - def test_unsupported_operations(self): - """Assert unsupported operations raise an error.""" - cvar = CVaRMeasurement(Z) - - attrs = ["to_matrix", "to_matrix_op", "to_density_matrix", "to_circuit_op", "sample"] - for attr in attrs: - with self.subTest(attr): - with self.assertRaises(NotImplementedError): - _ = getattr(cvar, attr)() - - with self.subTest("adjoint"): - with self.assertRaises(OpflowError): - cvar.adjoint() - - def test_cvar_on_paulisumop(self): - """Test a large PauliSumOp is checked for diagonality efficiently. - - Regression test for Qiskit/qiskit-terra#7573. - """ - op = PauliSumOp.from_list([("Z" * 30, 1)]) - # assert global algorithm settings do not have massive calculations turned on - # -- which is the default, but better to be sure in the test! - # also add a cleanup so we're sure to reset to the original value after the test, even if - # the test would fail - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - self.addCleanup(self.cleanup_algorithm_globals, algorithm_globals.massive) - algorithm_globals.massive = False - - cvar = CVaRMeasurement(op, alpha=0.1) - fake_probabilities = [0.2, 0.8] - fake_energies = [1, 2] - - expectation = cvar.compute_cvar(fake_energies, fake_probabilities) - self.assertEqual(expectation, 1) - - -@ddt -class TestCVaRExpectation(QiskitOpflowTestCase): - """Test the CVaR expectation object.""" - - def test_construction(self): - """Test the correct operator expression is constructed.""" - - alpha = 0.5 - base_expecation = PauliExpectation() - cvar_expecation = CVaRExpectation(alpha=alpha, expectation=base_expecation) - - with self.subTest("single operator"): - op = ~StateFn(Z) @ Plus - expected = CVaRMeasurement(Z, alpha) @ Plus - cvar = cvar_expecation.convert(op) - self.assertEqual(cvar, expected) - - with self.subTest("list operator"): - op = ~StateFn(ListOp([Z ^ Z, I ^ Z])) @ (Plus ^ Plus) - expected = ListOp( - [ - CVaRMeasurement((Z ^ Z), alpha) @ (Plus ^ Plus), - CVaRMeasurement((I ^ Z), alpha) @ (Plus ^ Plus), - ] - ) - cvar = cvar_expecation.convert(op) - self.assertEqual(cvar, expected) - - def test_unsupported_expectation(self): - """Assert passing an AerPauliExpectation raises an error.""" - expecation = AerPauliExpectation() - with self.assertRaises(NotImplementedError): - _ = CVaRExpectation(alpha=1, expectation=expecation) - - @data(PauliExpectation(), MatrixExpectation()) - def test_underlying_expectation(self, base_expecation): - """Test the underlying expectation works correctly.""" - - cvar_expecation = CVaRExpectation(alpha=0.3, expectation=base_expecation) - circuit = QuantumCircuit(2) - circuit.z(0) - circuit.cp(0.5, 0, 1) - circuit.t(1) - op = ~StateFn(CircuitOp(circuit)) @ (Plus ^ 2) - - cvar = cvar_expecation.convert(op) - expected = base_expecation.convert(op) - - # test if the operators have been transformed in the same manner - self.assertEqual(cvar.oplist[0].primitive, expected.oplist[0].primitive) - - def test_compute_variance(self): - """Test if the compute_variance method works""" - alphas = [0, 0.3, 0.5, 0.7, 1] - correct_vars = [0, 0, 0, 0.8163, 1] - for i, alpha in enumerate(alphas): - base_expecation = PauliExpectation() - cvar_expecation = CVaRExpectation(alpha=alpha, expectation=base_expecation) - op = ~StateFn(Z ^ Z) @ (Plus ^ Plus) - cvar_var = cvar_expecation.compute_variance(op) - np.testing.assert_almost_equal(cvar_var, correct_vars[i], decimal=3) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_evolution.py b/test/python/opflow/test_evolution.py deleted file mode 100644 index 55810dd7a114..000000000000 --- a/test/python/opflow/test_evolution.py +++ /dev/null @@ -1,384 +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. - -"""Test Evolution""" - -import unittest - -from test.python.opflow import QiskitOpflowTestCase -import numpy as np -import scipy.linalg - -import qiskit -from qiskit.circuit import Parameter, ParameterVector -from qiskit.circuit.library import UnitaryGate -from qiskit.opflow import ( - CX, - CircuitOp, - EvolutionFactory, - EvolvedOp, - H, - I, - ListOp, - PauliTrotterEvolution, - QDrift, - SummedOp, - Suzuki, - Trotter, - X, - Y, - Z, - Zero, -) - - -class TestEvolution(QiskitOpflowTestCase): - """Evolution tests.""" - - def test_exp_i(self): - """exponential of Pauli test""" - op = Z.exp_i() - gate = op.to_circuit().data[0].operation - self.assertIsInstance(gate, qiskit.circuit.library.RZGate) - self.assertEqual(gate.params[0], 2) - - def test_trotter_with_identity(self): - """trotterization of operator with identity term""" - op = (2.0 * I ^ I) + (Z ^ Y) - exact_matrix = scipy.linalg.expm(-1j * op.to_matrix()) - evo = PauliTrotterEvolution(trotter_mode="suzuki", reps=2) - with self.subTest("all PauliOp terms"): - circ_op = evo.convert(EvolvedOp(op)) - circuit_matrix = qiskit.quantum_info.Operator(circ_op.to_circuit()).data - np.testing.assert_array_almost_equal(exact_matrix, circuit_matrix) - - with self.subTest("MatrixOp identity term"): - op = (2.0 * I ^ I).to_matrix_op() + (Z ^ Y) - circ_op = evo.convert(EvolvedOp(op)) - circuit_matrix = qiskit.quantum_info.Operator(circ_op.to_circuit()).data - np.testing.assert_array_almost_equal(exact_matrix, circuit_matrix) - - with self.subTest("CircuitOp identity term"): - op = (2.0 * I ^ I).to_circuit_op() + (Z ^ Y) - circ_op = evo.convert(EvolvedOp(op)) - circuit_matrix = qiskit.quantum_info.Operator(circ_op.to_circuit()).data - np.testing.assert_array_almost_equal(exact_matrix, circuit_matrix) - - def test_pauli_evolution(self): - """pauli evolution test""" - op = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (0.18093119978423156 * X ^ X) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) - ) - evolution = EvolutionFactory.build(operator=op) - # wf = (Pl^Pl) + (Ze^Ze) - wf = ((np.pi / 2) * op).exp_i() @ CX @ (H ^ I) @ Zero - mean = evolution.convert(wf) - self.assertIsNotNone(mean) - - def test_summedop_pauli_evolution(self): - """SummedOp[PauliOp] evolution test""" - op = SummedOp( - [ - (-1.052373245772859 * I ^ I), - (0.39793742484318045 * I ^ Z), - (0.18093119978423156 * X ^ X), - (-0.39793742484318045 * Z ^ I), - (-0.01128010425623538 * Z ^ Z), - ] - ) - evolution = EvolutionFactory.build(operator=op) - # wf = (Pl^Pl) + (Ze^Ze) - wf = ((np.pi / 2) * op).exp_i() @ CX @ (H ^ I) @ Zero - mean = evolution.convert(wf) - self.assertIsNotNone(mean) - - def test_parameterized_evolution(self): - """parameterized evolution test""" - thetas = ParameterVector("θ", length=7) - op = ( - (thetas[0] * I ^ I) - + (thetas[1] * I ^ Z) - + (thetas[2] * X ^ X) - + (thetas[3] * Z ^ I) - + (thetas[4] * Y ^ Z) - + (thetas[5] * Z ^ Z) - ) - op = op * thetas[6] - evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) - # wf = (Pl^Pl) + (Ze^Ze) - wf = (op).exp_i() @ CX @ (H ^ I) @ Zero - mean = evolution.convert(wf) - circuit = mean.to_circuit() - # Check that all parameters are in the circuit - for p in thetas: - self.assertIn(p, circuit.parameters) - # Check that the identity-parameters only exist as global phase - self.assertNotIn(thetas[0], circuit._parameter_table.get_keys()) - - def test_bind_parameters(self): - """bind parameters test""" - thetas = ParameterVector("θ", length=6) - op = ( - (thetas[1] * I ^ Z) - + (thetas[2] * X ^ X) - + (thetas[3] * Z ^ I) - + (thetas[4] * Y ^ Z) - + (thetas[5] * Z ^ Z) - ) - op = thetas[0] * op - evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) - # wf = (Pl^Pl) + (Ze^Ze) - wf = (op).exp_i() @ CX @ (H ^ I) @ Zero - wf = wf.assign_parameters({thetas: np.arange(10, 16)}) - mean = evolution.convert(wf) - circuit_params = mean.to_circuit().parameters - # Check that the no parameters are in the circuit - for p in thetas[1:]: - self.assertNotIn(p, circuit_params) - - def test_bind_circuit_parameters(self): - """bind circuit parameters test""" - thetas = ParameterVector("θ", length=6) - op = ( - (thetas[1] * I ^ Z) - + (thetas[2] * X ^ X) - + (thetas[3] * Z ^ I) - + (thetas[4] * Y ^ Z) - + (thetas[5] * Z ^ Z) - ) - op = thetas[0] * op - evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) - # wf = (Pl^Pl) + (Ze^Ze) - wf = (op).exp_i() @ CX @ (H ^ I) @ Zero - evo = evolution.convert(wf) - mean = evo.assign_parameters({thetas: np.arange(10, 16)}) - # Check that the no parameters are in the circuit - for p in thetas[1:]: - self.assertNotIn(p, mean.to_circuit().parameters) - # Check that original circuit is unchanged - for p in thetas: - self.assertIn(p, evo.to_circuit().parameters) - - # TODO test with other Op types than CircuitStateFn - def test_bind_parameter_list(self): - """bind parameters list test""" - thetas = ParameterVector("θ", length=6) - op = ( - (thetas[1] * I ^ Z) - + (thetas[2] * X ^ X) - + (thetas[3] * Z ^ I) - + (thetas[4] * Y ^ Z) - + (thetas[5] * Z ^ Z) - ) - op = thetas[0] * op - evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) - # wf = (Pl^Pl) + (Ze^Ze) - wf = (op).exp_i() @ CX @ (H ^ I) @ Zero - evo = evolution.convert(wf) - param_list = np.transpose([np.arange(10, 16), np.arange(2, 8), np.arange(30, 36)]).tolist() - means = evo.assign_parameters({thetas: param_list}) - self.assertIsInstance(means, ListOp) - # Check that the no parameters are in the circuit - for p in thetas[1:]: - for circop in means.oplist: - self.assertNotIn(p, circop.to_circuit().parameters) - # Check that original circuit is unchanged - for p in thetas: - self.assertIn(p, evo.to_circuit().parameters) - - def test_bind_parameters_complex(self): - """bind parameters with a complex value test""" - th1 = Parameter("th1") - th2 = Parameter("th2") - - operator = th1 * X + th2 * Y - bound_operator = operator.bind_parameters({th1: 3j, th2: 2}) - - expected_bound_operator = SummedOp([3j * X, (2 + 0j) * Y]) - self.assertEqual(bound_operator, expected_bound_operator) - - def test_qdrift(self): - """QDrift test""" - op = (2 * Z ^ Z) + (3 * X ^ X) - (4 * Y ^ Y) + (0.5 * Z ^ I) - trotterization = QDrift().convert(op) - self.assertGreater(len(trotterization.oplist), 150) - last_coeff = None - # Check that all types are correct and all coefficients are equals - for op in trotterization.oplist: - self.assertIsInstance(op, (EvolvedOp, CircuitOp)) - if isinstance(op, EvolvedOp): - if last_coeff: - self.assertEqual(op.primitive.coeff, last_coeff) - else: - last_coeff = op.primitive.coeff - - def test_qdrift_summed_op(self): - """QDrift test for SummedOp""" - op = SummedOp( - [ - (2 * Z ^ Z), - (3 * X ^ X), - (-4 * Y ^ Y), - (0.5 * Z ^ I), - ] - ) - trotterization = QDrift().convert(op) - self.assertGreater(len(trotterization.oplist), 150) - last_coeff = None - # Check that all types are correct and all coefficients are equals - for op in trotterization.oplist: - self.assertIsInstance(op, (EvolvedOp, CircuitOp)) - if isinstance(op, EvolvedOp): - if last_coeff: - self.assertEqual(op.primitive.coeff, last_coeff) - else: - last_coeff = op.primitive.coeff - - def test_matrix_op_evolution(self): - """MatrixOp evolution test""" - op = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (0.18093119978423156 * X ^ X) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) * np.pi / 2 - ) - exp_mat = op.to_matrix_op().exp_i().to_matrix() - ref_mat = scipy.linalg.expm(-1j * op.to_matrix()) - np.testing.assert_array_almost_equal(ref_mat, exp_mat) - - def test_log_i(self): - """MatrixOp.log_i() test""" - op = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (0.18093119978423156 * X ^ X) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) * np.pi / 2 - ) - # Test with CircuitOp - log_exp_op = op.to_matrix_op().exp_i().log_i().to_pauli_op() - np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) - - # Test with MatrixOp - log_exp_op = op.to_matrix_op().exp_i().to_matrix_op().log_i().to_pauli_op() - np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) - - # Test with PauliOp - log_exp_op = op.to_matrix_op().exp_i().to_pauli_op().log_i().to_pauli_op() - np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) - - # Test with EvolvedOp - log_exp_op = op.exp_i().to_pauli_op().log_i().to_pauli_op() - np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) - - # Test with proper ListOp - op = ListOp( - [ - (0.39793742484318045 * I ^ Z), - (0.18093119978423156 * X ^ X), - (-0.39793742484318045 * Z ^ I), - (-0.01128010425623538 * Z ^ Z) * np.pi / 2, - ] - ) - log_exp_op = op.to_matrix_op().exp_i().to_matrix_op().log_i().to_pauli_op() - np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) - - def test_matrix_op_parameterized_evolution(self): - """parameterized MatrixOp evolution test""" - theta = Parameter("θ") - op = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (0.18093119978423156 * X ^ X) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) - ) - op = op * theta - wf = (op.to_matrix_op().exp_i()) @ CX @ (H ^ I) @ Zero - self.assertIn(theta, wf.to_circuit().parameters) - - op = op.assign_parameters({theta: 1}) - exp_mat = op.to_matrix_op().exp_i().to_matrix() - ref_mat = scipy.linalg.expm(-1j * op.to_matrix()) - np.testing.assert_array_almost_equal(ref_mat, exp_mat) - - wf = wf.assign_parameters({theta: 3}) - self.assertNotIn(theta, wf.to_circuit().parameters) - - def test_mixed_evolution(self): - """bind parameters test""" - thetas = ParameterVector("θ", length=6) - op = ( - (thetas[1] * (I ^ Z).to_matrix_op()) - + (thetas[2] * (X ^ X)).to_matrix_op() - + (thetas[3] * Z ^ I) - + (thetas[4] * Y ^ Z).to_circuit_op() - + (thetas[5] * (Z ^ I).to_circuit_op()) - ) - op = thetas[0] * op - evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) - # wf = (Pl^Pl) + (Ze^Ze) - wf = (op).exp_i() @ CX @ (H ^ I) @ Zero - wf = wf.assign_parameters({thetas: np.arange(10, 16)}) - mean = evolution.convert(wf) - circuit_params = mean.to_circuit().parameters - # Check that the no parameters are in the circuit - for p in thetas[1:]: - self.assertNotIn(p, circuit_params) - - def test_reps(self): - """Test reps and order params in Trotterization""" - reps = 7 - trotter = Trotter(reps=reps) - self.assertEqual(trotter.reps, reps) - - order = 5 - suzuki = Suzuki(reps=reps, order=order) - self.assertEqual(suzuki.reps, reps) - self.assertEqual(suzuki.order, order) - - qdrift = QDrift(reps=reps) - self.assertEqual(qdrift.reps, reps) - - def test_suzuki_directly(self): - """Test for Suzuki converter""" - operator = X + Z - - evo = Suzuki() - evolution = evo.convert(operator) - - matrix = np.array( - [[0.29192658 - 0.45464871j, -0.84147098j], [-0.84147098j, 0.29192658 + 0.45464871j]] - ) - np.testing.assert_array_almost_equal(evolution.to_matrix(), matrix) - - def test_evolved_op_to_instruction(self): - """Test calling `to_instruction` on a plain EvolvedOp. - - Regression test of Qiskit/qiskit-terra#8025. - """ - op = EvolvedOp(0.5 * X) - circuit = op.to_instruction() - - unitary = scipy.linalg.expm(-0.5j * X.to_matrix()) - expected = UnitaryGate(unitary) - - self.assertEqual(circuit, expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_expectation_factory.py b/test/python/opflow/test_expectation_factory.py deleted file mode 100644 index f03a8517733d..000000000000 --- a/test/python/opflow/test_expectation_factory.py +++ /dev/null @@ -1,39 +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 expectation factory.""" - -import unittest -from test.python.opflow import QiskitOpflowTestCase - -from qiskit.opflow import PauliExpectation, AerPauliExpectation, ExpectationFactory, Z, I, X -from qiskit.utils import optionals - - -class TestExpectationFactory(QiskitOpflowTestCase): - """Tests for the expectation factory.""" - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") - def test_aer_simulator_pauli_sum(self): - """Test expectation selection with Aer's qasm_simulator.""" - from qiskit_aer import AerSimulator - - backend = AerSimulator() - op = 0.2 * (X ^ X) + 0.1 * (Z ^ I) - with self.assertWarns(DeprecationWarning): - with self.subTest("Defaults"): - expectation = ExpectationFactory.build(op, backend, include_custom=False) - self.assertIsInstance(expectation, PauliExpectation) - - with self.subTest("Include custom"): - expectation = ExpectationFactory.build(op, backend, include_custom=True) - self.assertIsInstance(expectation, AerPauliExpectation) diff --git a/test/python/opflow/test_gradients.py b/test/python/opflow/test_gradients.py deleted file mode 100644 index fc82475313a0..000000000000 --- a/test/python/opflow/test_gradients.py +++ /dev/null @@ -1,1571 +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 Quantum Gradient Framework""" - -import unittest -import warnings -from test.python.opflow import QiskitOpflowTestCase -from itertools import product -import numpy as np -from ddt import ddt, data, idata, unpack - -from qiskit import QuantumCircuit, QuantumRegister, BasicAer -from qiskit.utils import QuantumInstance -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.utils import algorithm_globals - -from qiskit.opflow import ( - I, - X, - Y, - Z, - StateFn, - CircuitStateFn, - ListOp, - CircuitSampler, - TensoredOp, - SummedOp, -) -from qiskit.opflow.gradients import Gradient, NaturalGradient, Hessian -from qiskit.opflow.gradients.qfi import QFI -from qiskit.opflow.gradients.circuit_gradients import LinComb -from qiskit.opflow.gradients.circuit_qfis import LinCombFull, OverlapBlockDiag, OverlapDiag -from qiskit.circuit import Parameter -from qiskit.circuit import ParameterVector -from qiskit.circuit.library import RealAmplitudes, EfficientSU2 -from qiskit.utils import optionals - -if optionals.HAS_JAX: - import jax.numpy as jnp - - -@ddt -class TestGradients(QiskitOpflowTestCase): - """Test Qiskit Gradient Framework""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 50 - - @data("lin_comb", "param_shift", "fin_diff") - def test_gradient_p(self, method): - """Test the state gradient for p - |psi> = 1/sqrt(2)[[1, exp(ia)]] - Tr(|psi>/da = - 0.5 sin(a) - """ - ham = 0.5 * X - 1 * Z - a = Parameter("a") - params = a - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.p(a, q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}] - correct_values = [-0.5 / np.sqrt(2), 0, -0.5] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_gradient_u(self, method): - """Test the state gradient for U - Tr(|psi>/da = - 0.5 sin(a) - 1 cos(a)sin(b) - d/db = - 1 sin(a)cos(b) - """ - - ham = 0.5 * X - 1 * Z - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [ - {a: np.pi / 4, b: np.pi}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - {params[0]: np.pi / 2, params[1]: np.pi / 4}, - ] - correct_values = [ - [-0.5 / np.sqrt(2), 1 / np.sqrt(2)], - [-0.5 / np.sqrt(2) - 0.5, -1 / 2.0], - [-0.5, -1 / np.sqrt(2)], - ] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_state_gradient2(self, method): - """Test the state gradient 2 - - Tr(|psi>/da = - 0.5 sin(a) - 2 cos(a)sin(a) - """ - ham = 0.5 * X - 1 * Z - a = Parameter("a") - # b = Parameter('b') - params = [a] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(a, q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}] - correct_values = [-1.353553, -0, -0.5] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_state_gradient3(self, method): - """Test the state gradient 3 - - Tr(|psi>/da = - 0.5 sin(a) - 1 cos(a)sin(cos(a)+1) + 1 sin^2(a)cos(cos(a)+1) - """ - ham = 0.5 * X - 1 * Z - a = Parameter("a") - # b = Parameter('b') - params = a - c = np.cos(a) + 1 - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(c, q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}] - correct_values = [-1.1220, -0.9093, 0.0403] - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_state_gradient4(self, method): - """Test the state gradient 4 - Tr(|psi>/da0 = - 0.5 sin(a0) - 1 cos(a0)sin(a1) - d/da1 = - 1 sin(a0)cos(a1) - """ - - ham = 0.5 * X - 1 * Z - a = ParameterVector("a", 2) - params = a - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [ - {a: [np.pi / 4, np.pi]}, - {a: [np.pi / 4, np.pi / 4]}, - {a: [np.pi / 2, np.pi / 4]}, - ] - correct_values = [ - [-0.5 / np.sqrt(2), 1 / np.sqrt(2)], - [-0.5 / np.sqrt(2) - 0.5, -1 / 2.0], - [-0.5, -1 / np.sqrt(2)], - ] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_state_hessian(self, method): - """Test the state Hessian - - Tr(|psi>/da^2 = - 0.5 cos(a) + 1 sin(a)sin(b) - d^2/dbda = - 1 cos(a)cos(b) - d^2/dbda = - 1 cos(a)cos(b) - d^2/db^2 = + 1 sin(a)sin(b) - """ - - ham = 0.5 * X - 1 * Z - params = ParameterVector("a", 2) - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - state_hess = Hessian(hess_method=method).convert(operator=op) - - values_dict = [ - {params[0]: np.pi / 4, params[1]: np.pi}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - ] - correct_values = [ - [[-0.5 / np.sqrt(2), 1 / np.sqrt(2)], [1 / np.sqrt(2), 0]], - [[-0.5 / np.sqrt(2) + 0.5, -1 / 2.0], [-1 / 2.0, 0.5]], - ] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_hess.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @unittest.skipIf(not optionals.HAS_JAX, "Skipping test due to missing jax module.") - @data("lin_comb", "param_shift", "fin_diff") - def test_state_hessian_custom_combo_fn(self, method): - """Test the state Hessian with on an operator which includes - a user-defined combo_fn. - - Tr(|psi>/da^2 = - 0.5 cos(a) + 1 sin(a)sin(b) - d^2/dbda = - 1 cos(a)cos(b) - d^2/dbda = - 1 cos(a)cos(b) - d^2/db^2 = + 1 sin(a)sin(b) - """ - - ham = 0.5 * X - 1 * Z - a = Parameter("a") - b = Parameter("b") - params = [(a, a), (a, b), (b, b)] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(b, q[0]) - - op = ListOp( - [~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0)], - combo_fn=lambda x: x[0] ** 3 + 4 * x[0], - ) - state_hess = Hessian(hess_method=method).convert(operator=op, params=params) - - values_dict = [ - {a: np.pi / 4, b: np.pi}, - {a: np.pi / 4, b: np.pi / 4}, - {a: np.pi / 2, b: np.pi / 4}, - ] - - correct_values = [ - [-1.28163104, 2.56326208, 1.06066017], - [-0.04495626, -2.40716991, 1.8125], - [2.82842712, -1.5, 1.76776695], - ] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_hess.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_prob_grad(self, method): - """Test the probability gradient - - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - prob_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [ - {a: np.pi / 4, b: 0}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - {params[0]: np.pi / 2, params[1]: np.pi}, - ] - correct_values = [ - [[0, 0], [1 / (2 * np.sqrt(2)), -1 / (2 * np.sqrt(2))]], - [[1 / 4, -1 / 4], [1 / 4, -1 / 4]], - [[0, 0], [-1 / 2, 1 / 2]], - ] - for i, value_dict in enumerate(values_dict): - for j, prob_grad_result in enumerate(prob_grad.assign_parameters(value_dict).eval()): - np.testing.assert_array_almost_equal( - prob_grad_result, correct_values[i][j], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_prob_hess(self, method): - """Test the probability Hessian using linear combination of unitaries method - - d^2p0/da^2 = - sin(a)sin(b) / 2 - d^2p1/da^2 = sin(a)sin(b) / 2 - d^2p0/dadb = cos(a)cos(b) / 2 - d^2p1/dadb = - cos(a)cos(b) / 2 - """ - - a = Parameter("a") - b = Parameter("b") - params = [(a, a), (a, b)] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(b, q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - prob_hess = Hessian(hess_method=method).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4, b: 0}, {a: np.pi / 4, b: np.pi / 4}, {a: np.pi / 2, b: np.pi}] - correct_values = [ - [[0, 0], [1 / (2 * np.sqrt(2)), -1 / (2 * np.sqrt(2))]], - [[-1 / 4, 1 / 4], [1 / 4, -1 / 4]], - [[0, 0], [0, 0]], - ] - for i, value_dict in enumerate(values_dict): - for j, prob_hess_result in enumerate(prob_hess.assign_parameters(value_dict).eval()): - np.testing.assert_array_almost_equal( - prob_hess_result, correct_values[i][j], decimal=1 - ) - - @idata( - product( - ["lin_comb", "param_shift", "fin_diff"], - [None, "lasso", "ridge", "perturb_diag", "perturb_diag_elements"], - ) - ) - @unpack - def test_natural_gradient(self, method, regularization): - """Test the natural gradient""" - try: - for params in (ParameterVector("a", 2), [Parameter("a"), Parameter("b")]): - ham = 0.5 * X - 1 * Z - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - nat_grad = NaturalGradient( - grad_method=method, regularization=regularization - ).convert(operator=op) - values_dict = [{params[0]: np.pi / 4, params[1]: np.pi / 2}] - - # reference values obtained by classically computing the natural gradients - correct_values = [[-3.26, 1.63]] if regularization == "ridge" else [[-4.24, 0]] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - nat_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - def test_natural_gradient2(self): - """Test the natural gradient 2""" - with self.assertRaises(TypeError): - _ = NaturalGradient().convert(None, None) - - @idata( - zip( - ["lin_comb_full", "overlap_block_diag", "overlap_diag"], - [LinCombFull, OverlapBlockDiag, OverlapDiag], - ) - ) - @unpack - def test_natural_gradient3(self, qfi_method, circuit_qfi): - """Test the natural gradient 3""" - nat_grad = NaturalGradient(qfi_method=qfi_method) - self.assertIsInstance(nat_grad.qfi_method, circuit_qfi) - - @idata( - product( - ["lin_comb", "param_shift", "fin_diff"], - ["lin_comb_full", "overlap_block_diag", "overlap_diag"], - [None, "ridge", "perturb_diag", "perturb_diag_elements"], - ) - ) - @unpack - def test_natural_gradient4(self, grad_method, qfi_method, regularization): - """Test the natural gradient 4""" - - # Avoid regularization = lasso intentionally because it does not converge - try: - ham = 0.5 * X - 1 * Z - a = Parameter("a") - params = a - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - nat_grad = NaturalGradient( - grad_method=grad_method, qfi_method=qfi_method, regularization=regularization - ).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4}] - correct_values = [[0.0]] if regularization == "ridge" else [[-1.41421342]] - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - nat_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=3 - ) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - def test_gradient_p_imag(self): - """Test the imaginary state gradient for p - |psi(a)> = 1/sqrt(2)[[1, exp(ia)]] - = iexp(-ia)/2 <1|H(|0>+exp(ia)|1>) - Im() = 0.5 cos(a). - """ - ham = X - a = Parameter("a") - params = a - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.p(a, q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - state_grad = LinComb(aux_meas_op=(-1) * Y).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}] - correct_values = [1 / np.sqrt(2), 1, 0] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - def test_qfi_p_imag(self): - """Test the imaginary state QFI for RXRY""" - x = Parameter("x") - y = Parameter("y") - circuit = QuantumCircuit(1) - circuit.ry(y, 0) - circuit.rx(x, 0) - state = StateFn(circuit) - - dx = ( - lambda x, y: (-1) - * 0.5j - * np.array( - [ - [ - -1j * np.sin(x / 2) * np.cos(y / 2) + np.cos(x / 2) * np.sin(y / 2), - np.cos(x / 2) * np.cos(y / 2) - 1j * np.sin(x / 2) * np.sin(y / 2), - ] - ] - ) - ) - dy = ( - lambda x, y: (-1) - * 0.5j - * np.array( - [ - [ - -1j * np.cos(x / 2) * np.sin(y / 2) + np.sin(x / 2) * np.cos(y / 2), - 1j * np.cos(x / 2) * np.cos(y / 2) - 1 * np.sin(x / 2) * np.sin(y / 2), - ] - ] - ) - ) - - state_grad = LinCombFull(aux_meas_op=-1 * Y, phase_fix=False).convert( - operator=state, params=[x, y] - ) - values_dict = [{x: 0, y: np.pi / 4}, {x: 0, y: np.pi / 2}, {x: np.pi / 2, y: 0}] - - for value_dict in values_dict: - x_ = list(value_dict.values())[0] - y_ = list(value_dict.values())[1] - correct_values = [ - [ - 4 * np.imag(np.dot(dx(x_, y_), np.conj(np.transpose(dx(x_, y_))))[0][0]), - 4 * np.imag(np.dot(dy(x_, y_), np.conj(np.transpose(dx(x_, y_))))[0][0]), - ], - [ - 4 * np.imag(np.dot(dy(x_, y_), np.conj(np.transpose(dx(x_, y_))))[0][0]), - 4 * np.imag(np.dot(dy(x_, y_), np.conj(np.transpose(dy(x_, y_))))[0][0]), - ], - ] - - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values, decimal=3 - ) - - @unittest.skipIf(not optionals.HAS_JAX, "Skipping test due to missing jax module.") - @idata(product(["lin_comb", "param_shift", "fin_diff"], [True, False])) - @unpack - def test_jax_chain_rule(self, method: str, autograd: bool): - """Test the chain rule functionality using Jax - - d/d = 2 - d/d = - sin() - = Tr(|psi> = Tr(|psi>/da = d/d d/da + d/d d/da = - 2 cos(a)sin(a) - - sin(sin(a)sin(b)) * cos(a)sin(b) - d/db = d/d d/db + d/d d/db = - sin(sin(a)sin(b)) * sin(a)cos(b) - """ - - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - def combo_fn(x): - return jnp.power(x[0], 2) + jnp.cos(x[1]) - - def grad_combo_fn(x): - return np.array([2 * x[0], -np.sin(x[1])]) - - op = ListOp( - [ - ~StateFn(X) @ CircuitStateFn(primitive=qc, coeff=1.0), - ~StateFn(Z) @ CircuitStateFn(primitive=qc, coeff=1.0), - ], - combo_fn=combo_fn, - grad_combo_fn=None if autograd else grad_combo_fn, - ) - - state_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [ - {a: np.pi / 4, b: np.pi}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - {params[0]: np.pi / 2, params[1]: np.pi / 4}, - ] - correct_values = [[-1.0, 0.0], [-1.2397, -0.2397], [0, -0.45936]] - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_grad_combo_fn_chain_rule(self, method): - """Test the chain rule for a custom gradient combo function.""" - np.random.seed(2) - - def combo_fn(x): - amplitudes = x[0].primitive.data - pdf = np.multiply(amplitudes, np.conj(amplitudes)) - return np.sum(np.log(pdf)) / (-len(amplitudes)) - - def grad_combo_fn(x): - amplitudes = x[0].primitive.data - pdf = np.multiply(amplitudes, np.conj(amplitudes)) - grad = [] - for prob in pdf: - grad += [-1 / prob] - return grad - - qc = RealAmplitudes(2, reps=1) - grad_op = ListOp([StateFn(qc.decompose())], combo_fn=combo_fn, grad_combo_fn=grad_combo_fn) - grad = Gradient(grad_method=method).convert(grad_op) - - value_dict = dict(zip(qc.ordered_parameters, np.random.rand(len(qc.ordered_parameters)))) - correct_values = [ - [(-0.16666259133549044 + 0j)], - [(-7.244949702732864 + 0j)], - [(-2.979791752749964 + 0j)], - [(-5.310186078432614 + 0j)], - ] - np.testing.assert_array_almost_equal( - grad.assign_parameters(value_dict).eval(), correct_values - ) - - def test_grad_combo_fn_chain_rule_nat_grad(self): - """Test the chain rule for a custom gradient combo function.""" - np.random.seed(2) - - def combo_fn(x): - amplitudes = x[0].primitive.data - pdf = np.multiply(amplitudes, np.conj(amplitudes)) - return np.sum(np.log(pdf)) / (-len(amplitudes)) - - def grad_combo_fn(x): - amplitudes = x[0].primitive.data - pdf = np.multiply(amplitudes, np.conj(amplitudes)) - grad = [] - for prob in pdf: - grad += [-1 / prob] - return grad - - try: - qc = RealAmplitudes(2, reps=1) - grad_op = ListOp( - [StateFn(qc.decompose())], combo_fn=combo_fn, grad_combo_fn=grad_combo_fn - ) - grad = NaturalGradient(grad_method="lin_comb", regularization="ridge").convert( - grad_op, qc.ordered_parameters - ) - value_dict = dict( - zip(qc.ordered_parameters, np.random.rand(len(qc.ordered_parameters))) - ) - correct_values = [[0.20777236], [-18.92560338], [-15.89005475], [-10.44002031]] - np.testing.assert_array_almost_equal( - grad.assign_parameters(value_dict).eval(), correct_values, decimal=3 - ) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - @data("lin_comb", "param_shift", "fin_diff") - def test_operator_coefficient_gradient(self, method): - """Test the operator coefficient gradient - - Tr( | psi > < psi | Z) = sin(a)sin(b) - Tr( | psi > < psi | X) = cos(a) - """ - a = Parameter("a") - b = Parameter("b") - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(b, q[0]) - - coeff_0 = Parameter("c_0") - coeff_1 = Parameter("c_1") - ham = coeff_0 * X + coeff_1 * Z - op = StateFn(ham, is_measurement=True) @ CircuitStateFn(primitive=qc, coeff=1.0) - gradient_coeffs = [coeff_0, coeff_1] - coeff_grad = Gradient(grad_method=method).convert(op, gradient_coeffs) - values_dict = [ - {coeff_0: 0.5, coeff_1: -1, a: np.pi / 4, b: np.pi}, - {coeff_0: 0.5, coeff_1: -1, a: np.pi / 4, b: np.pi / 4}, - ] - correct_values = [[1 / np.sqrt(2), 0], [1 / np.sqrt(2), 1 / 2]] - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - coeff_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_operator_coefficient_hessian(self, method): - """Test the operator coefficient hessian - - = Tr( | psi > < psi | Z) = sin(a)sin(b) - = Tr( | psi > < psi | X) = cos(a) - d/dc_0 = 2 * c_0 * + c_1 * - d/dc_1 = c_0 * - d^2/dc_0^2 = 2 * - d^2/dc_0dc_1 = - d^2/dc_1dc_0 = - d^2/dc_1^2 = 0 - """ - a = Parameter("a") - b = Parameter("b") - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(a, q[0]) - qc.rx(b, q[0]) - - coeff_0 = Parameter("c_0") - coeff_1 = Parameter("c_1") - ham = coeff_0 * coeff_0 * X + coeff_1 * coeff_0 * Z - op = StateFn(ham, is_measurement=True) @ CircuitStateFn(primitive=qc, coeff=1.0) - gradient_coeffs = [(coeff_0, coeff_0), (coeff_0, coeff_1), (coeff_1, coeff_1)] - coeff_grad = Hessian(hess_method=method).convert(op, gradient_coeffs) - values_dict = [ - {coeff_0: 0.5, coeff_1: -1, a: np.pi / 4, b: np.pi}, - {coeff_0: 0.5, coeff_1: -1, a: np.pi / 4, b: np.pi / 4}, - ] - - correct_values = [[2 / np.sqrt(2), 0, 0], [2 / np.sqrt(2), 1 / 2, 0]] - - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal( - coeff_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_circuit_sampler(self, method): - """Test the gradient with circuit sampler - - Tr(|psi>/da = - 0.5 sin(a) - 1 cos(a)sin(b) - d/db = - 1 sin(a)cos(b) - """ - - ham = 0.5 * X - 1 * Z - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) - - shots = 8000 - if method == "fin_diff": - np.random.seed(8) - state_grad = Gradient(grad_method=method, epsilon=shots ** (-1 / 6.0)).convert( - operator=op - ) - else: - state_grad = Gradient(grad_method=method).convert(operator=op) - values_dict = [ - {a: np.pi / 4, b: np.pi}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - {params[0]: np.pi / 2, params[1]: np.pi / 4}, - ] - correct_values = [ - [-0.5 / np.sqrt(2), 1 / np.sqrt(2)], - [-0.5 / np.sqrt(2) - 0.5, -1 / 2.0], - [-0.5, -1 / np.sqrt(2)], - ] - - backend = BasicAer.get_backend("qasm_simulator") - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance(backend=backend, shots=shots) - - with self.assertWarns(DeprecationWarning): - for i, value_dict in enumerate(values_dict): - sampler = CircuitSampler(backend=q_instance).convert( - state_grad, params={k: [v] for k, v in value_dict.items()} - ) - np.testing.assert_array_almost_equal( - sampler.eval()[0], correct_values[i], decimal=1 - ) - - @data("lin_comb", "param_shift", "fin_diff") - def test_circuit_sampler2(self, method): - """Test the probability gradient with the circuit sampler - - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - shots = 8000 - if method == "fin_diff": - np.random.seed(8) - prob_grad = Gradient(grad_method=method, epsilon=shots ** (-1 / 6.0)).convert( - operator=op, params=params - ) - else: - prob_grad = Gradient(grad_method=method).convert(operator=op, params=params) - values_dict = [ - {a: [np.pi / 4], b: [0]}, - {params[0]: [np.pi / 4], params[1]: [np.pi / 4]}, - {params[0]: [np.pi / 2], params[1]: [np.pi]}, - ] - correct_values = [ - [[0, 0], [1 / (2 * np.sqrt(2)), -1 / (2 * np.sqrt(2))]], - [[1 / 4, -1 / 4], [1 / 4, -1 / 4]], - [[0, 0], [-1 / 2, 1 / 2]], - ] - - backend = BasicAer.get_backend("qasm_simulator") - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance(backend=backend, shots=shots) - - with self.assertWarns(DeprecationWarning): - for i, value_dict in enumerate(values_dict): - sampler = CircuitSampler(backend=q_instance).convert(prob_grad, params=value_dict) - result = sampler.eval()[0] - self.assertTrue(np.allclose(result[0].toarray(), correct_values[i][0], atol=0.1)) - self.assertTrue(np.allclose(result[1].toarray(), correct_values[i][1], atol=0.1)) - - @idata(["statevector_simulator", "qasm_simulator"]) - def test_gradient_wrapper(self, backend_type): - """Test the gradient wrapper for probability gradients - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - method = "param_shift" - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - shots = 8000 - backend = BasicAer.get_backend(backend_type) - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend=backend, shots=shots, seed_simulator=2, seed_transpiler=2 - ) - - if method == "fin_diff": - np.random.seed(8) - prob_grad = Gradient(grad_method=method, epsilon=shots ** (-1 / 6.0)).gradient_wrapper( - operator=op, bind_params=params, backend=q_instance - ) - else: - - with self.assertWarns(DeprecationWarning): - prob_grad = Gradient(grad_method=method).gradient_wrapper( - operator=op, bind_params=params, backend=q_instance - ) - - values = [[np.pi / 4, 0], [np.pi / 4, np.pi / 4], [np.pi / 2, np.pi]] - correct_values = [ - [[0, 0], [1 / (2 * np.sqrt(2)), -1 / (2 * np.sqrt(2))]], - [[1 / 4, -1 / 4], [1 / 4, -1 / 4]], - [[0, 0], [-1 / 2, 1 / 2]], - ] - with self.assertWarns(DeprecationWarning): - for i, value in enumerate(values): - result = prob_grad(value) - if backend_type == "qasm_simulator": # sparse result - result = [result[0].toarray(), result[1].toarray()] - - self.assertTrue(np.allclose(result[0], correct_values[i][0], atol=0.1)) - self.assertTrue(np.allclose(result[1], correct_values[i][1], atol=0.1)) - - @data(("statevector_simulator", 1e-7), ("qasm_simulator", 2e-1)) - @unpack - def test_gradient_wrapper2(self, backend_type, atol): - """Test the gradient wrapper for gradients checking that statevector and qasm gives the - same results - - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - method = "lin_comb" - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - qc = QuantumCircuit(2) - qc.h(1) - qc.h(0) - qc.sdg(1) - qc.cz(0, 1) - qc.ry(params[0], 0) - qc.rz(params[1], 0) - qc.h(1) - - obs = (Z ^ X) - (Y ^ Y) - op = StateFn(obs, is_measurement=True) @ CircuitStateFn(primitive=qc) - - shots = 8192 if backend_type == "qasm_simulator" else 1 - - values = [[0, np.pi / 2], [np.pi / 4, np.pi / 4], [np.pi / 3, np.pi / 9]] - correct_values = [[-4.0, 0], [-2.0, -4.82842712], [-0.68404029, -7.01396121]] - for i, value in enumerate(values): - backend = BasicAer.get_backend(backend_type) - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend=backend, shots=shots, seed_simulator=2, seed_transpiler=2 - ) - grad = NaturalGradient(grad_method=method).gradient_wrapper( - operator=op, bind_params=params, backend=q_instance - ) - result = grad(value) - self.assertTrue(np.allclose(result, correct_values[i], atol=atol)) - - def test_qfi_overlap_works_with_bound_parameters(self): - """Test all QFI methods work if the circuit contains a gate with bound parameters.""" - - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.ry(np.pi / 4, 0) - circuit.rx(x, 0) - state = StateFn(circuit) - - methods = ["lin_comb_full", "overlap_diag", "overlap_block_diag"] - reference = 0.5 - - for method in methods: - with self.subTest(method): - qfi = QFI(method) - value = np.real(qfi.convert(state, [x]).bind_parameters({x: 0.12}).eval()) - self.assertAlmostEqual(value[0][0], reference) - - -@ddt -class TestParameterGradients(QiskitOpflowTestCase): - """Test taking the gradient of parameter expressions.""" - - def test_grad(self): - """Test taking the gradient of parameter expressions.""" - x, y = Parameter("x"), Parameter("y") - with self.subTest("linear"): - expr = 2 * x + y - - grad = expr.gradient(x) - self.assertEqual(grad, 2) - - grad = expr.gradient(y) - self.assertEqual(grad, 1) - - with self.subTest("polynomial"): - expr = x * x * x - x * y + y * y - - grad = expr.gradient(x) - self.assertEqual(grad, 3 * x * x - y) - - grad = expr.gradient(y) - self.assertEqual(grad, -1 * x + 2 * y) - - def test_converted_to_float_if_bound(self): - """Test the gradient is a float when no free symbols are left.""" - x = Parameter("x") - expr = 2 * x + 1 - grad = expr.gradient(x) - self.assertIsInstance(grad, float) - - def test_converted_to_complex_if_bound(self): - """Test the gradient is a complex when no free symbols are left.""" - x = Parameter("x") - x2 = 1j * x - expr = 2 * x2 + 1 - grad = expr.gradient(x) - self.assertIsInstance(grad, complex) - - -@ddt -class TestQFI(QiskitOpflowTestCase): - """Tests for the quantum Fisher information.""" - - @data("lin_comb_full", "overlap_block_diag", "overlap_diag") - def test_qfi_simple(self, method): - """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) - - # convert the circuit to a QFI object - op = CircuitStateFn(qc) - qfi = QFI(qfi_method=method).convert(operator=op) - - # test for different values - values_dict = [{a: np.pi / 4, b: 0.1}, {a: np.pi, b: 0.1}, {a: np.pi / 2, b: 0.1}] - correct_values = [[[1, 0], [0, 0.5]], [[1, 0], [0, 0]], [[1, 0], [0, 1]]] - - for i, value_dict in enumerate(values_dict): - actual = qfi.assign_parameters(value_dict).eval() - np.testing.assert_array_almost_equal(actual, correct_values[i], decimal=1) - - def test_qfi_phase_fix(self): - """Test the phase-fix argument in a QFI calculation - - QFI = [[1, 0], [0, 1]]. - """ - # create the circuit - a, b = Parameter("a"), Parameter("b") - qc = QuantumCircuit(1) - qc.h(0) - qc.rz(a, 0) - qc.rx(b, 0) - - # convert the circuit to a QFI object - op = CircuitStateFn(qc) - qfi = LinCombFull(phase_fix=False).convert(operator=op, params=[a, b]) - - # test for different values - value_dict = {a: np.pi / 4, b: 0.1} - correct_values = [[1, 0], [0, 1]] - - actual = qfi.assign_parameters(value_dict).eval() - np.testing.assert_array_almost_equal(actual, correct_values, decimal=2) - - def test_qfi_maxcut(self): - """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) - - point = {x[0]: 0.4, x[1]: 0.69} - - # reference computed via finite difference - reference = np.array([[16.0, -5.551], [-5.551, 18.497]]) - - # QFI from gradient framework - qfi = QFI().convert(CircuitStateFn(ansatz), params=x[:]) - actual = np.array(qfi.bind_parameters(point).eval()).real - np.testing.assert_array_almost_equal(actual, reference, decimal=3) - - def test_qfi_circuit_shared_params(self): - """Test the QFI circuits for parameters shared across some gates.""" - # create the test circuit - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.rx(x, 0) - circuit.rx(x, 0) - - # construct the QFI circuits used in the evaluation - - circuit1 = QuantumCircuit(2) - circuit1.h(1) - circuit1.x(1) - circuit1.cx(1, 0) - circuit1.x(1) - circuit1.cx(1, 0) - # circuit1.rx(x, 0) # trimmed - # circuit1.rx(x, 0) # trimmed - circuit1.h(1) - - circuit2 = QuantumCircuit(2) - circuit2.h(1) - circuit2.x(1) - circuit2.cx(1, 0) - circuit2.x(1) - circuit2.rx(x, 0) - circuit2.cx(1, 0) - # circuit2.rx(x, 0) # trimmed - circuit2.h(1) - - circuit3 = QuantumCircuit(2) - circuit3.h(1) - circuit3.cx(1, 0) - circuit3.x(1) - circuit3.rx(x, 0) - circuit3.cx(1, 0) - # circuit3.rx(x, 0) # trimmed - circuit3.x(1) - circuit3.h(1) - - circuit4 = QuantumCircuit(2) - circuit4.h(1) - circuit4.rx(x, 0) - circuit4.x(1) - circuit4.cx(1, 0) - circuit4.x(1) - circuit4.cx(1, 0) - # circuit4.rx(x, 0) # trimmed - circuit4.h(1) - - # this naming and adding of register is required bc circuit's are only equal if the - # register have the same names - circuit5 = QuantumCircuit(2) - circuit5.h(1) - circuit5.sdg(1) - circuit5.cx(1, 0) - # circuit5.rx(x, 0) # trimmed - circuit5.h(1) - - circuit6 = QuantumCircuit(2) - circuit6.h(1) - circuit6.sdg(1) - circuit6.rx(x, 0) - circuit6.cx(1, 0) - circuit6.h(1) - - # compare - qfi = QFI().convert(StateFn(circuit), params=[x]) - - circuit_sets = ( - [circuit1, circuit2, circuit3, circuit4], - [circuit5, circuit6], - [circuit5, circuit6], - ) - list_ops = ( - qfi.oplist[0].oplist[0].oplist[:-1], - qfi.oplist[0].oplist[0].oplist[-1].oplist[0].oplist, - qfi.oplist[0].oplist[0].oplist[-1].oplist[1].oplist, - ) - - # compose both on the same circuit such that the comparison works - base = QuantumCircuit(2) - - for i, (circuit_set, list_op) in enumerate(zip(circuit_sets, list_ops)): - for j, (reference, composed_op) in enumerate(zip(circuit_set, list_op)): - with self.subTest(f"set {i} circuit {j}"): - primitive = composed_op[1].primitive - self.assertEqual(base.compose(primitive), base.compose(reference)) - - def test_overlap_qfi_bound_parameters(self): - """Test the overlap QFI works on a circuit with multi-parameter bound gates.""" - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.u(1, 2, 3, 0) - circuit.rx(x, 0) - - qfi = QFI("overlap_diag").convert(StateFn(circuit), [x]) - value = qfi.bind_parameters({x: 1}).eval()[0][0] - ref = 0.87737713 - self.assertAlmostEqual(value, ref) - - def test_overlap_qfi_raises_on_multiparam(self): - """Test the overlap QFI raises an appropriate error on multi-param unbound gates.""" - x = ParameterVector("x", 2) - circuit = QuantumCircuit(1) - circuit.u(x[0], x[1], 2, 0) - - with self.assertRaises(NotImplementedError): - _ = QFI("overlap_diag").convert(StateFn(circuit), [x]) - - def test_overlap_qfi_raises_on_unsupported_gate(self): - """Test the overlap QFI raises an appropriate error on multi-param unbound gates.""" - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.p(x, 0) - - with self.assertRaises(NotImplementedError): - _ = QFI("overlap_diag").convert(StateFn(circuit), [x]) - - @data(-Y, Z - 1j * Y) - def test_aux_meas_op(self, aux_meas_op): - """Test various auxiliary measurement operators for probability gradients with LinComb - Gradient. - - """ - - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - shots = 10000 - - prob_grad = LinComb(aux_meas_op=aux_meas_op).convert(operator=op, params=params) - value_dicts = [{a: [np.pi / 4], b: [0]}, {a: [np.pi / 2], b: [np.pi / 4]}] - if aux_meas_op == -Y: - correct_values = [ - [[-0.5, 0.5], [-1 / (np.sqrt(2) * 2), -1 / (np.sqrt(2) * 2)]], - [[-1 / (np.sqrt(2) * 2), 1 / (np.sqrt(2) * 2)], [0, 0]], - ] - else: - correct_values = [ - [[-0.5j, 0.5j], [(1 - 1j) / (np.sqrt(2) * 2), (-1 - 1j) / (np.sqrt(2) * 2)]], - [ - [-1j / (np.sqrt(2) * 2), 1j / (np.sqrt(2) * 2)], - [1 / (np.sqrt(2) * 2), -1 / (np.sqrt(2) * 2)], - ], - ] - - for backend_type in ["qasm_simulator", "statevector_simulator"]: - - for j, value_dict in enumerate(value_dicts): - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend=BasicAer.get_backend(backend_type), shots=shots - ) - result = ( - CircuitSampler(backend=q_instance) - .convert(prob_grad, params=value_dict) - .eval()[0] - ) - if backend_type == "qasm_simulator": # sparse result - result = [result[0].toarray()[0], result[1].toarray()[0]] - for i, item in enumerate(result): - np.testing.assert_array_almost_equal(item, correct_values[j][i], decimal=1) - - def test_unsupported_aux_meas_op(self): - """Test error for unsupported auxiliary measurement operator in LinComb Gradient. - - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.0) - - shots = 8000 - - aux_meas_op = X - - with self.assertRaises(ValueError): - prob_grad = LinComb(aux_meas_op=aux_meas_op).convert(operator=op, params=params) - value_dict = {a: [np.pi / 4], b: [0]} - - backend = BasicAer.get_backend("qasm_simulator") - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance(backend=backend, shots=shots) - CircuitSampler(backend=q_instance).convert(prob_grad, params=value_dict).eval() - - def test_nat_grad_error(self): - """Test the NaturalGradient throws an Error. - - dp0/da = cos(a)sin(b) / 2 - dp1/da = - cos(a)sin(b) / 2 - dp0/db = sin(a)cos(b) / 2 - dp1/db = - sin(a)cos(b) / 2 - """ - method = "lin_comb" - a = Parameter("a") - b = Parameter("b") - params = [a, b] - - qc = QuantumCircuit(2) - qc.h(1) - qc.h(0) - qc.sdg(1) - qc.cz(0, 1) - qc.ry(params[0], 0) - qc.rz(params[1], 0) - qc.h(1) - - obs = (Z ^ X) - (Y ^ Y) - op = StateFn(obs, is_measurement=True) @ CircuitStateFn(primitive=qc) - - backend_type = "qasm_simulator" - shots = 1 - value = [0, np.pi / 2] - - backend = BasicAer.get_backend(backend_type) - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend=backend, shots=shots, seed_simulator=2, seed_transpiler=2 - ) - - with self.assertWarns(DeprecationWarning): - grad = NaturalGradient(grad_method=method).gradient_wrapper( - operator=op, bind_params=params, backend=q_instance - ) - - with self.assertWarns(DeprecationWarning): - with self.assertRaises(ValueError): - grad(value) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_matrix_expectation.py b/test/python/opflow/test_matrix_expectation.py deleted file mode 100644 index 10448c3a64e1..000000000000 --- a/test/python/opflow/test_matrix_expectation.py +++ /dev/null @@ -1,184 +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 MatrixExpectation""" - -import unittest -from test.python.opflow import QiskitOpflowTestCase -import itertools -import numpy as np - -from qiskit.utils import QuantumInstance -from qiskit.opflow import ( - X, - Y, - Z, - I, - CX, - H, - S, - ListOp, - Zero, - One, - Plus, - Minus, - StateFn, - MatrixExpectation, - CircuitSampler, -) -from qiskit import BasicAer - - -class TestMatrixExpectation(QiskitOpflowTestCase): - """Pauli Change of Basis Expectation tests.""" - - def setUp(self) -> None: - super().setUp() - self.seed = 97 - backend = BasicAer.get_backend("statevector_simulator") - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend, seed_simulator=self.seed, seed_transpiler=self.seed - ) - self.sampler = CircuitSampler(q_instance, attach_results=True) - - self.expect = MatrixExpectation() - - def test_pauli_expect_pair(self): - """pauli expect pair test""" - - op = Z ^ Z - # wf = (Pl^Pl) + (Ze^Ze) - wf = CX @ (H ^ I) @ Zero - converted_meas = self.expect.convert(~StateFn(op) @ wf) - self.assertAlmostEqual(converted_meas.eval(), 0, delta=0.1) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - self.assertAlmostEqual(sampled.eval(), 0, delta=0.1) - - def test_pauli_expect_single(self): - """pauli expect single test""" - - paulis = [Z, X, Y, I] - states = [Zero, One, Plus, Minus, S @ Plus, S @ Minus] - for pauli, state in itertools.product(paulis, states): - converted_meas = self.expect.convert(~StateFn(pauli) @ state) - matmulmean = state.adjoint().to_matrix() @ pauli.to_matrix() @ state.to_matrix() - self.assertAlmostEqual(converted_meas.eval(), matmulmean, delta=0.1) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - self.assertAlmostEqual(sampled.eval(), matmulmean, delta=0.1) - - def test_pauli_expect_op_vector(self): - """pauli expect op vector test""" - - paulis_op = ListOp([X, Y, Z, I]) - converted_meas = self.expect.convert(~StateFn(paulis_op)) - - with self.assertWarns(DeprecationWarning): - - plus_mean = converted_meas @ Plus - np.testing.assert_array_almost_equal(plus_mean.eval(), [1, 0, 0, 1], decimal=1) - sampled_plus = self.sampler.convert(plus_mean) - np.testing.assert_array_almost_equal(sampled_plus.eval(), [1, 0, 0, 1], decimal=1) - - minus_mean = converted_meas @ Minus - np.testing.assert_array_almost_equal(minus_mean.eval(), [-1, 0, 0, 1], decimal=1) - sampled_minus = self.sampler.convert(minus_mean) - np.testing.assert_array_almost_equal(sampled_minus.eval(), [-1, 0, 0, 1], decimal=1) - - zero_mean = converted_meas @ Zero - np.testing.assert_array_almost_equal(zero_mean.eval(), [0, 0, 1, 1], decimal=1) - sampled_zero = self.sampler.convert(zero_mean) - np.testing.assert_array_almost_equal(sampled_zero.eval(), [0, 0, 1, 1], decimal=1) - - sum_zero = (Plus + Minus) * (0.5**0.5) - sum_zero_mean = converted_meas @ sum_zero - np.testing.assert_array_almost_equal(sum_zero_mean.eval(), [0, 0, 1, 1], decimal=1) - sampled_zero = self.sampler.convert(sum_zero) - - np.testing.assert_array_almost_equal( - (converted_meas @ sampled_zero).eval(), [0, 0, 1, 1], decimal=1 - ) - - for i, op in enumerate(paulis_op.oplist): - mat_op = op.to_matrix() - np.testing.assert_array_almost_equal( - zero_mean.eval()[i], - Zero.adjoint().to_matrix() @ mat_op @ Zero.to_matrix(), - decimal=1, - ) - np.testing.assert_array_almost_equal( - plus_mean.eval()[i], - Plus.adjoint().to_matrix() @ mat_op @ Plus.to_matrix(), - decimal=1, - ) - np.testing.assert_array_almost_equal( - minus_mean.eval()[i], - Minus.adjoint().to_matrix() @ mat_op @ Minus.to_matrix(), - decimal=1, - ) - - def test_pauli_expect_state_vector(self): - """pauli expect state vector test""" - - states_op = ListOp([One, Zero, Plus, Minus]) - paulis_op = X - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - np.testing.assert_array_almost_equal(converted_meas.eval(), [0, 0, 1, -1], decimal=1) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - np.testing.assert_array_almost_equal(sampled.eval(), [0, 0, 1, -1], decimal=1) - - # Small test to see if execution results are accessible - for composed_op in sampled: - self.assertIn("statevector", composed_op[1].execution_results) - - def test_pauli_expect_op_vector_state_vector(self): - """pauli expect op vector state vector test""" - - paulis_op = ListOp([X, Y, Z, I]) - states_op = ListOp([One, Zero, Plus, Minus]) - - valids = [[+0, 0, 1, -1], [+0, 0, 0, 0], [-1, 1, 0, -0], [+1, 1, 1, 1]] - converted_meas = self.expect.convert(~StateFn(paulis_op)) - np.testing.assert_array_almost_equal((converted_meas @ states_op).eval(), valids, decimal=1) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(states_op) - - np.testing.assert_array_almost_equal((converted_meas @ sampled).eval(), valids, decimal=1) - - def test_multi_representation_ops(self): - """Test observables with mixed representations""" - - mixed_ops = ListOp([X.to_matrix_op(), H, H + I, X]) - converted_meas = self.expect.convert(~StateFn(mixed_ops)) - - plus_mean = converted_meas @ Plus - - with self.assertWarns(DeprecationWarning): - sampled_plus = self.sampler.convert(plus_mean) - - np.testing.assert_array_almost_equal( - sampled_plus.eval(), [1, 0.5**0.5, (1 + 0.5**0.5), 1], decimal=1 - ) - - def test_matrix_expectation_non_hermite_op(self): - """Test MatrixExpectation for non hermitian operator""" - - exp = ~StateFn(1j * Z) @ One - self.assertEqual(self.expect.convert(exp).eval(), 1j) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_op_construction.py b/test/python/opflow/test_op_construction.py deleted file mode 100644 index a3dcb7dd6671..000000000000 --- a/test/python/opflow/test_op_construction.py +++ /dev/null @@ -1,1385 +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. - -"""Test Operator construction, including OpPrimitives and singletons.""" - - -import itertools -import unittest -from math import pi -from test.python.opflow import QiskitOpflowTestCase - -import numpy as np -import scipy -from ddt import data, ddt, unpack -from scipy.sparse import csr_matrix -from scipy.stats import unitary_group - -from qiskit import QiskitError, transpile -from qiskit.circuit import ( - Instruction, - Parameter, - ParameterVector, - QuantumCircuit, - QuantumRegister, -) -from qiskit.circuit.library import CZGate, ZGate -from qiskit.opflow import ( - CX, - CircuitOp, - CircuitStateFn, - ComposedOp, - DictStateFn, - EvolvedOp, - H, - I, - ListOp, - MatrixOp, - Minus, - OperatorBase, - OperatorStateFn, - OpflowError, - PauliOp, - PrimitiveOp, - SparseVectorStateFn, - StateFn, - SummedOp, - T, - TensoredOp, - VectorStateFn, - X, - Y, - Z, - Zero, -) -from qiskit.quantum_info import Operator, Pauli, Statevector - -# pylint: disable=invalid-name - - -@ddt -class TestOpConstruction(QiskitOpflowTestCase): - """Operator Construction tests.""" - - def test_pauli_primitives(self): - """from to file test""" - newop = X ^ Y ^ Z ^ I - self.assertEqual(newop.primitive, Pauli("XYZI")) - - kpower_op = (Y ^ 5) ^ (I ^ 3) - self.assertEqual(kpower_op.primitive, Pauli("YYYYYIII")) - - kpower_op2 = (Y ^ I) ^ 4 - self.assertEqual(kpower_op2.primitive, Pauli("YIYIYIYI")) - - # Check immutability - self.assertEqual(X.primitive, Pauli("X")) - self.assertEqual(Y.primitive, Pauli("Y")) - self.assertEqual(Z.primitive, Pauli("Z")) - self.assertEqual(I.primitive, Pauli("I")) - - def test_composed_eval(self): - """Test eval of ComposedOp""" - self.assertAlmostEqual(Minus.eval("1"), -(0.5**0.5)) - - def test_xz_compose_phase(self): - """Test phase composition""" - self.assertEqual((-1j * Y).eval("0").eval("0"), 0) - self.assertEqual((-1j * Y).eval("0").eval("1"), 1) - self.assertEqual((-1j * Y).eval("1").eval("0"), -1) - self.assertEqual((-1j * Y).eval("1").eval("1"), 0) - self.assertEqual((X @ Z).eval("0").eval("0"), 0) - self.assertEqual((X @ Z).eval("0").eval("1"), 1) - self.assertEqual((X @ Z).eval("1").eval("0"), -1) - self.assertEqual((X @ Z).eval("1").eval("1"), 0) - self.assertEqual((1j * Y).eval("0").eval("0"), 0) - self.assertEqual((1j * Y).eval("0").eval("1"), -1) - self.assertEqual((1j * Y).eval("1").eval("0"), 1) - self.assertEqual((1j * Y).eval("1").eval("1"), 0) - self.assertEqual((Z @ X).eval("0").eval("0"), 0) - self.assertEqual((Z @ X).eval("0").eval("1"), -1) - self.assertEqual((Z @ X).eval("1").eval("0"), 1) - self.assertEqual((Z @ X).eval("1").eval("1"), 0) - - def test_evals(self): - """evals test""" - # TODO: Think about eval names - self.assertEqual(Z.eval("0").eval("0"), 1) - self.assertEqual(Z.eval("1").eval("0"), 0) - self.assertEqual(Z.eval("0").eval("1"), 0) - self.assertEqual(Z.eval("1").eval("1"), -1) - self.assertEqual(X.eval("0").eval("0"), 0) - self.assertEqual(X.eval("1").eval("0"), 1) - self.assertEqual(X.eval("0").eval("1"), 1) - self.assertEqual(X.eval("1").eval("1"), 0) - self.assertEqual(Y.eval("0").eval("0"), 0) - self.assertEqual(Y.eval("1").eval("0"), -1j) - self.assertEqual(Y.eval("0").eval("1"), 1j) - self.assertEqual(Y.eval("1").eval("1"), 0) - - with self.assertRaises(ValueError): - Y.eval("11") - - with self.assertRaises(ValueError): - (X ^ Y).eval("1111") - - with self.assertRaises(ValueError): - Y.eval((X ^ X).to_matrix_op()) - - # Check that Pauli logic eval returns same as matrix logic - self.assertEqual(PrimitiveOp(Z.to_matrix()).eval("0").eval("0"), 1) - self.assertEqual(PrimitiveOp(Z.to_matrix()).eval("1").eval("0"), 0) - self.assertEqual(PrimitiveOp(Z.to_matrix()).eval("0").eval("1"), 0) - self.assertEqual(PrimitiveOp(Z.to_matrix()).eval("1").eval("1"), -1) - self.assertEqual(PrimitiveOp(X.to_matrix()).eval("0").eval("0"), 0) - self.assertEqual(PrimitiveOp(X.to_matrix()).eval("1").eval("0"), 1) - self.assertEqual(PrimitiveOp(X.to_matrix()).eval("0").eval("1"), 1) - self.assertEqual(PrimitiveOp(X.to_matrix()).eval("1").eval("1"), 0) - self.assertEqual(PrimitiveOp(Y.to_matrix()).eval("0").eval("0"), 0) - self.assertEqual(PrimitiveOp(Y.to_matrix()).eval("1").eval("0"), -1j) - self.assertEqual(PrimitiveOp(Y.to_matrix()).eval("0").eval("1"), 1j) - self.assertEqual(PrimitiveOp(Y.to_matrix()).eval("1").eval("1"), 0) - - pauli_op = Z ^ I ^ X ^ Y - mat_op = PrimitiveOp(pauli_op.to_matrix()) - full_basis = list(map("".join, itertools.product("01", repeat=pauli_op.num_qubits))) - for bstr1, bstr2 in itertools.product(full_basis, full_basis): - # print('{} {} {} {}'.format(bstr1, bstr2, pauli_op.eval(bstr1, bstr2), - # mat_op.eval(bstr1, bstr2))) - np.testing.assert_array_almost_equal( - pauli_op.eval(bstr1).eval(bstr2), mat_op.eval(bstr1).eval(bstr2) - ) - - gnarly_op = SummedOp( - [ - (H ^ I ^ Y).compose(X ^ X ^ Z).tensor(Z), - PrimitiveOp(Operator.from_label("+r0I")), - 3 * (X ^ CX ^ T), - ], - coeff=3 + 0.2j, - ) - gnarly_mat_op = PrimitiveOp(gnarly_op.to_matrix()) - full_basis = list(map("".join, itertools.product("01", repeat=gnarly_op.num_qubits))) - for bstr1, bstr2 in itertools.product(full_basis, full_basis): - np.testing.assert_array_almost_equal( - gnarly_op.eval(bstr1).eval(bstr2), gnarly_mat_op.eval(bstr1).eval(bstr2) - ) - - def test_circuit_construction(self): - """circuit construction test""" - hadq2 = H ^ I - cz = hadq2.compose(CX).compose(hadq2) - qc = QuantumCircuit(2) - qc.append(cz.primitive, qargs=range(2)) - - ref_cz_mat = PrimitiveOp(CZGate()).to_matrix() - np.testing.assert_array_almost_equal(cz.to_matrix(), ref_cz_mat) - - def test_io_consistency(self): - """consistency test""" - new_op = X ^ Y ^ I - label = "XYI" - # label = new_op.primitive.to_label() - self.assertEqual(str(new_op.primitive), label) - np.testing.assert_array_almost_equal( - new_op.primitive.to_matrix(), Operator.from_label(label).data - ) - self.assertEqual(new_op.primitive, Pauli(label)) - - x_mat = X.primitive.to_matrix() - y_mat = Y.primitive.to_matrix() - i_mat = np.eye(2, 2) - np.testing.assert_array_almost_equal( - new_op.primitive.to_matrix(), np.kron(np.kron(x_mat, y_mat), i_mat) - ) - - hi = np.kron(H.to_matrix(), I.to_matrix()) - hi2 = Operator.from_label("HI").data - hi3 = (H ^ I).to_matrix() - np.testing.assert_array_almost_equal(hi, hi2) - np.testing.assert_array_almost_equal(hi2, hi3) - - xy = np.kron(X.to_matrix(), Y.to_matrix()) - xy2 = Operator.from_label("XY").data - xy3 = (X ^ Y).to_matrix() - np.testing.assert_array_almost_equal(xy, xy2) - np.testing.assert_array_almost_equal(xy2, xy3) - - # Check if numpy array instantiation is the same as from Operator - matrix_op = Operator.from_label("+r") - np.testing.assert_array_almost_equal( - PrimitiveOp(matrix_op).to_matrix(), PrimitiveOp(matrix_op.data).to_matrix() - ) - # Ditto list of lists - np.testing.assert_array_almost_equal( - PrimitiveOp(matrix_op.data.tolist()).to_matrix(), - PrimitiveOp(matrix_op.data).to_matrix(), - ) - - # TODO make sure this works once we resolve endianness mayhem - # qc = QuantumCircuit(3) - # qc.x(2) - # qc.y(1) - # from qiskit import BasicAer, QuantumCircuit, execute - # unitary = execute(qc, BasicAer.get_backend('unitary_simulator')).result().get_unitary() - # np.testing.assert_array_almost_equal(new_op.primitive.to_matrix(), unitary) - - def test_to_matrix(self): - """to matrix text""" - np.testing.assert_array_equal(X.to_matrix(), Operator.from_label("X").data) - np.testing.assert_array_equal(Y.to_matrix(), Operator.from_label("Y").data) - np.testing.assert_array_equal(Z.to_matrix(), Operator.from_label("Z").data) - - op1 = Y + H - np.testing.assert_array_almost_equal(op1.to_matrix(), Y.to_matrix() + H.to_matrix()) - - op2 = op1 * 0.5 - np.testing.assert_array_almost_equal(op2.to_matrix(), op1.to_matrix() * 0.5) - - op3 = (4 - 0.6j) * op2 - np.testing.assert_array_almost_equal(op3.to_matrix(), op2.to_matrix() * (4 - 0.6j)) - - op4 = op3.tensor(X) - np.testing.assert_array_almost_equal( - op4.to_matrix(), np.kron(op3.to_matrix(), X.to_matrix()) - ) - - op5 = op4.compose(H ^ I) - np.testing.assert_array_almost_equal( - op5.to_matrix(), np.dot(op4.to_matrix(), (H ^ I).to_matrix()) - ) - - op6 = op5 + PrimitiveOp(Operator.from_label("+r").data) - np.testing.assert_array_almost_equal( - op6.to_matrix(), op5.to_matrix() + Operator.from_label("+r").data - ) - - param = Parameter("α") - m = np.array([[0, -1j], [1j, 0]]) - op7 = MatrixOp(m, param) - np.testing.assert_array_equal(op7.to_matrix(), m * param) - - param = Parameter("β") - op8 = PauliOp(primitive=Pauli("Y"), coeff=param) - np.testing.assert_array_equal(op8.to_matrix(), m * param) - - param = Parameter("γ") - qc = QuantumCircuit(1) - qc.h(0) - op9 = CircuitOp(qc, coeff=param) - m = np.array([[1, 1], [1, -1]]) / np.sqrt(2) - np.testing.assert_array_equal(op9.to_matrix(), m * param) - - def test_circuit_op_to_matrix(self): - """test CircuitOp.to_matrix""" - qc = QuantumCircuit(1) - qc.rz(1.0, 0) - qcop = CircuitOp(qc) - np.testing.assert_array_almost_equal( - qcop.to_matrix(), scipy.linalg.expm(-0.5j * Z.to_matrix()) - ) - - def test_matrix_to_instruction(self): - """Test MatrixOp.to_instruction yields an Instruction object.""" - matop = (H ^ 3).to_matrix_op() - with self.subTest("assert to_instruction returns Instruction"): - self.assertIsInstance(matop.to_instruction(), Instruction) - - matop = ((H ^ 3) + (Z ^ 3)).to_matrix_op() - with self.subTest("matrix operator is not unitary"): - with self.assertRaises(ValueError): - matop.to_instruction() - - def test_adjoint(self): - """adjoint test""" - gnarly_op = 3 * (H ^ I ^ Y).compose(X ^ X ^ Z).tensor(T ^ Z) + PrimitiveOp( - Operator.from_label("+r0IX").data - ) - np.testing.assert_array_almost_equal( - np.conj(np.transpose(gnarly_op.to_matrix())), gnarly_op.adjoint().to_matrix() - ) - - def test_primitive_strings(self): - """get primitives test""" - self.assertEqual(X.primitive_strings(), {"Pauli"}) - - gnarly_op = 3 * (H ^ I ^ Y).compose(X ^ X ^ Z).tensor(T ^ Z) + PrimitiveOp( - Operator.from_label("+r0IX").data - ) - self.assertEqual(gnarly_op.primitive_strings(), {"QuantumCircuit", "Matrix"}) - - def test_to_pauli_op(self): - """Test to_pauli_op method""" - gnarly_op = 3 * (H ^ I ^ Y).compose(X ^ X ^ Z).tensor(T ^ Z) + PrimitiveOp( - Operator.from_label("+r0IX").data - ) - mat_op = gnarly_op.to_matrix_op() - pauli_op = gnarly_op.to_pauli_op() - self.assertIsInstance(pauli_op, SummedOp) - for p in pauli_op: - self.assertIsInstance(p, PauliOp) - np.testing.assert_array_almost_equal(mat_op.to_matrix(), pauli_op.to_matrix()) - - def test_circuit_permute(self): - r"""Test the CircuitOp's .permute method""" - perm = range(7)[::-1] - c_op = ( - ((CX ^ 3) ^ X) - @ (H ^ 7) - @ (X ^ Y ^ Z ^ I ^ X ^ X ^ X) - @ (Y ^ (CX ^ 3)) - @ (X ^ Y ^ Z ^ I ^ X ^ X ^ X) - ) - c_op_perm = c_op.permute(perm) - self.assertNotEqual(c_op, c_op_perm) - c_op_id = c_op_perm.permute(perm) - self.assertEqual(c_op, c_op_id) - - def test_summed_op_reduce(self): - """Test SummedOp""" - sum_op = (X ^ X * 2) + (Y ^ Y) # type: PauliSumOp - sum_op = sum_op.to_pauli_op() # type: SummedOp[PauliOp] - with self.subTest("SummedOp test 1"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [2, 1]) - - sum_op = (X ^ X * 2) + (Y ^ Y) - sum_op += Y ^ Y - sum_op = sum_op.to_pauli_op() # type: SummedOp[PauliOp] - with self.subTest("SummedOp test 2-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [2, 1, 1]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 2-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [2, 2]) - - sum_op = (X ^ X * 2) + (Y ^ Y) - sum_op += (Y ^ Y) + (X ^ X * 2) - sum_op = sum_op.to_pauli_op() # type: SummedOp[PauliOp] - with self.subTest("SummedOp test 3-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "YY", "XX"]) - self.assertListEqual([op.coeff for op in sum_op], [2, 1, 1, 2]) - - sum_op = sum_op.reduce().to_pauli_op() - with self.subTest("SummedOp test 3-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2]) - - sum_op = SummedOp([X ^ X * 2, Y ^ Y], 2) - with self.subTest("SummedOp test 4-a"): - self.assertEqual(sum_op.coeff, 2) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [2, 1]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 4-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2]) - - sum_op = SummedOp([X ^ X * 2, Y ^ Y], 2) - sum_op += Y ^ Y - with self.subTest("SummedOp test 5-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2, 1]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 5-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 3]) - - sum_op = SummedOp([X ^ X * 2, Y ^ Y], 2) - sum_op += ((X ^ X) * 2 + (Y ^ Y)).to_pauli_op() - with self.subTest("SummedOp test 6-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2, 2, 1]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 6-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [6, 3]) - - sum_op = SummedOp([X ^ X * 2, Y ^ Y], 2) - sum_op += sum_op - with self.subTest("SummedOp test 7-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2, 4, 2]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 7-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY"]) - self.assertListEqual([op.coeff for op in sum_op], [8, 4]) - - sum_op = SummedOp([X ^ X * 2, Y ^ Y], 2) + SummedOp([X ^ X * 2, Z ^ Z], 3) - with self.subTest("SummedOp test 8-a"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "XX", "ZZ"]) - self.assertListEqual([op.coeff for op in sum_op], [4, 2, 6, 3]) - - sum_op = sum_op.collapse_summands() - with self.subTest("SummedOp test 8-b"): - self.assertEqual(sum_op.coeff, 1) - self.assertListEqual([str(op.primitive) for op in sum_op], ["XX", "YY", "ZZ"]) - self.assertListEqual([op.coeff for op in sum_op], [10, 2, 3]) - - sum_op = SummedOp([]) - with self.subTest("SummedOp test 9"): - self.assertEqual(sum_op.reduce(), sum_op) - - sum_op = ((Z + I) ^ Z) + (Z ^ X) - with self.subTest("SummedOp test 10"): - expected = SummedOp([PauliOp(Pauli("ZZ")), PauliOp(Pauli("IZ")), PauliOp(Pauli("ZX"))]) - self.assertEqual(sum_op.to_pauli_op(), expected) - - def test_compose_op_of_different_dim(self): - """ - Test if smaller operator expands to correct dim when composed with bigger operator. - Test if PrimitiveOps compose methods are consistent. - """ - # PauliOps of different dim - xy_p = X ^ Y - xyz_p = X ^ Y ^ Z - - pauli_op = xy_p @ xyz_p - expected_result = I ^ I ^ Z - self.assertEqual(pauli_op, expected_result) - - # MatrixOps of different dim - xy_m = xy_p.to_matrix_op() - xyz_m = xyz_p.to_matrix_op() - - matrix_op = xy_m @ xyz_m - self.assertEqual(matrix_op, expected_result.to_matrix_op()) - - # CircuitOps of different dim - xy_c = xy_p.to_circuit_op() - xyz_c = xyz_p.to_circuit_op() - - circuit_op = xy_c @ xyz_c - - self.assertTrue(np.array_equal(pauli_op.to_matrix(), matrix_op.to_matrix())) - self.assertTrue(np.allclose(pauli_op.to_matrix(), circuit_op.to_matrix(), rtol=1e-14)) - self.assertTrue(np.allclose(matrix_op.to_matrix(), circuit_op.to_matrix(), rtol=1e-14)) - - def test_permute_on_primitive_op(self): - """Test if permute methods of PrimitiveOps are consistent and work as expected.""" - indices = [1, 2, 4] - - # PauliOp - pauli_op = X ^ Y ^ Z - permuted_pauli_op = pauli_op.permute(indices) - expected_pauli_op = X ^ I ^ Y ^ Z ^ I - - self.assertEqual(permuted_pauli_op, expected_pauli_op) - - # CircuitOp - circuit_op = pauli_op.to_circuit_op() - permuted_circuit_op = circuit_op.permute(indices) - expected_circuit_op = expected_pauli_op.to_circuit_op() - - self.assertEqual( - Operator(permuted_circuit_op.primitive), Operator(expected_circuit_op.primitive) - ) - - # MatrixOp - matrix_op = pauli_op.to_matrix_op() - permuted_matrix_op = matrix_op.permute(indices) - expected_matrix_op = expected_pauli_op.to_matrix_op() - - equal = np.allclose(permuted_matrix_op.to_matrix(), expected_matrix_op.to_matrix()) - self.assertTrue(equal) - - def test_permute_on_list_op(self): - """Test if ListOp permute method is consistent with PrimitiveOps permute methods.""" - - op1 = (X ^ Y ^ Z).to_circuit_op() - op2 = Z ^ X ^ Y - - # ComposedOp - indices = [1, 2, 0] - primitive_op = op1 @ op2 - primitive_op_perm = primitive_op.permute(indices) # CircuitOp.permute - - composed_op = ComposedOp([op1, op2]) - composed_op_perm = composed_op.permute(indices) - - # reduce the ListOp to PrimitiveOp - to_primitive = composed_op_perm.oplist[0] @ composed_op_perm.oplist[1] - # compare resulting PrimitiveOps - equal = np.allclose(primitive_op_perm.to_matrix(), to_primitive.to_matrix()) - self.assertTrue(equal) - - # TensoredOp - indices = [3, 5, 4, 0, 2, 1] - primitive_op = op1 ^ op2 - primitive_op_perm = primitive_op.permute(indices) - - tensored_op = TensoredOp([op1, op2]) - tensored_op_perm = tensored_op.permute(indices) - - # reduce the ListOp to PrimitiveOp - composed_oplist = tensored_op_perm.oplist - to_primitive = ( - composed_oplist[0] - @ (composed_oplist[1].oplist[0] ^ composed_oplist[1].oplist[1]) - @ composed_oplist[2] - ) - - # compare resulting PrimitiveOps - equal = np.allclose(primitive_op_perm.to_matrix(), to_primitive.to_matrix()) - self.assertTrue(equal) - - # SummedOp - primitive_op = X ^ Y ^ Z - summed_op = SummedOp([primitive_op]) - - indices = [1, 2, 0] - primitive_op_perm = primitive_op.permute(indices) # PauliOp.permute - summed_op_perm = summed_op.permute(indices) - - # reduce the ListOp to PrimitiveOp - to_primitive = summed_op_perm.oplist[0] @ primitive_op @ summed_op_perm.oplist[2] - - # compare resulting PrimitiveOps - equal = np.allclose(primitive_op_perm.to_matrix(), to_primitive.to_matrix()) - self.assertTrue(equal) - - def test_expand_on_list_op(self): - """Test if expanded ListOp has expected num_qubits.""" - add_qubits = 3 - - # ComposedOp - composed_op = ComposedOp([(X ^ Y ^ Z), (H ^ T), (Z ^ X ^ Y ^ Z).to_matrix_op()]) - expanded = composed_op._expand_dim(add_qubits) - self.assertEqual(composed_op.num_qubits + add_qubits, expanded.num_qubits) - - # TensoredOp - tensored_op = TensoredOp([(X ^ Y), (Z ^ I)]) - expanded = tensored_op._expand_dim(add_qubits) - self.assertEqual(tensored_op.num_qubits + add_qubits, expanded.num_qubits) - - # SummedOp - summed_op = SummedOp([(X ^ Y), (Z ^ I ^ Z)]) - expanded = summed_op._expand_dim(add_qubits) - self.assertEqual(summed_op.num_qubits + add_qubits, expanded.num_qubits) - - def test_expand_on_state_fn(self): - """Test if expanded StateFn has expected num_qubits.""" - num_qubits = 3 - add_qubits = 2 - - # case CircuitStateFn, with primitive QuantumCircuit - qc2 = QuantumCircuit(num_qubits) - qc2.cx(0, 1) - - cfn = CircuitStateFn(qc2, is_measurement=True) - - cfn_exp = cfn._expand_dim(add_qubits) - self.assertEqual(cfn_exp.num_qubits, add_qubits + num_qubits) - - # case OperatorStateFn, with OperatorBase primitive, in our case CircuitStateFn - osfn = OperatorStateFn(cfn) - osfn_exp = osfn._expand_dim(add_qubits) - - self.assertEqual(osfn_exp.num_qubits, add_qubits + num_qubits) - - # case DictStateFn - dsfn = DictStateFn("1" * num_qubits, is_measurement=True) - self.assertEqual(dsfn.num_qubits, num_qubits) - - dsfn_exp = dsfn._expand_dim(add_qubits) - self.assertEqual(dsfn_exp.num_qubits, num_qubits + add_qubits) - - # case VectorStateFn - vsfn = VectorStateFn(np.ones(2**num_qubits, dtype=complex)) - self.assertEqual(vsfn.num_qubits, num_qubits) - - vsfn_exp = vsfn._expand_dim(add_qubits) - self.assertEqual(vsfn_exp.num_qubits, num_qubits + add_qubits) - - def test_permute_on_state_fn(self): - """Test if StateFns permute are consistent.""" - - num_qubits = 4 - dim = 2**num_qubits - primitive_list = [1.0 / (i + 1) for i in range(dim)] - primitive_dict = {format(i, "b").zfill(num_qubits): 1.0 / (i + 1) for i in range(dim)} - - dict_fn = DictStateFn(primitive=primitive_dict, is_measurement=True) - vec_fn = VectorStateFn(primitive=primitive_list, is_measurement=True) - - # check if dict_fn and vec_fn are equivalent - equivalent = np.allclose(dict_fn.to_matrix(), vec_fn.to_matrix()) - self.assertTrue(equivalent) - - # permute - indices = [2, 3, 0, 1] - permute_dict = dict_fn.permute(indices) - permute_vect = vec_fn.permute(indices) - - equivalent = np.allclose(permute_dict.to_matrix(), permute_vect.to_matrix()) - self.assertTrue(equivalent) - - def test_compose_consistency(self): - """Test if PrimitiveOp @ ComposedOp is consistent with ComposedOp @ PrimitiveOp.""" - - # PauliOp - op1 = X ^ Y ^ Z - op2 = X ^ Y ^ Z - op3 = (X ^ Y ^ Z).to_circuit_op() - - comp1 = op1 @ ComposedOp([op2, op3]) - comp2 = ComposedOp([op3, op2]) @ op1 - self.assertListEqual(comp1.oplist, list(reversed(comp2.oplist))) - - # CircitOp - op1 = op1.to_circuit_op() - op2 = op2.to_circuit_op() - op3 = op3.to_matrix_op() - - comp1 = op1 @ ComposedOp([op2, op3]) - comp2 = ComposedOp([op3, op2]) @ op1 - self.assertListEqual(comp1.oplist, list(reversed(comp2.oplist))) - - # MatrixOp - op1 = op1.to_matrix_op() - op2 = op2.to_matrix_op() - op3 = op3.to_pauli_op() - - comp1 = op1 @ ComposedOp([op2, op3]) - comp2 = ComposedOp([op3, op2]) @ op1 - self.assertListEqual(comp1.oplist, list(reversed(comp2.oplist))) - - def test_compose_with_indices(self): - """Test compose method using its permutation feature.""" - - pauli_op = X ^ Y ^ Z - circuit_op = T ^ H - matrix_op = (X ^ Y ^ H ^ T).to_matrix_op() - evolved_op = EvolvedOp(matrix_op) - - # composition of PrimitiveOps - num_qubits = 4 - primitive_op = pauli_op @ circuit_op @ matrix_op - composed_op = pauli_op @ circuit_op @ evolved_op - self.assertEqual(primitive_op.num_qubits, num_qubits) - self.assertEqual(composed_op.num_qubits, num_qubits) - - # with permutation - num_qubits = 5 - indices = [1, 4] - permuted_primitive_op = evolved_op @ circuit_op.permute(indices) @ pauli_op @ matrix_op - composed_primitive_op = ( - evolved_op @ pauli_op.compose(circuit_op, permutation=indices, front=True) @ matrix_op - ) - - self.assertTrue( - np.allclose(permuted_primitive_op.to_matrix(), composed_primitive_op.to_matrix()) - ) - self.assertEqual(num_qubits, permuted_primitive_op.num_qubits) - - # ListOp - num_qubits = 6 - tensored_op = TensoredOp([pauli_op, circuit_op]) - summed_op = pauli_op + circuit_op.permute([2, 1]) - composed_op = circuit_op @ evolved_op @ matrix_op - - list_op = summed_op @ composed_op.compose( - tensored_op, permutation=[1, 2, 3, 5, 4], front=True - ) - self.assertEqual(num_qubits, list_op.num_qubits) - - num_qubits = 4 - circuit_fn = CircuitStateFn(primitive=circuit_op.primitive, is_measurement=True) - operator_fn = OperatorStateFn(primitive=circuit_op ^ circuit_op, is_measurement=True) - - no_perm_op = circuit_fn @ operator_fn - self.assertEqual(no_perm_op.num_qubits, num_qubits) - - indices = [0, 4] - perm_op = operator_fn.compose(circuit_fn, permutation=indices, front=True) - self.assertEqual(perm_op.num_qubits, max(indices) + 1) - - # StateFn - num_qubits = 3 - dim = 2**num_qubits - vec = [1.0 / (i + 1) for i in range(dim)] - dic = {format(i, "b").zfill(num_qubits): 1.0 / (i + 1) for i in range(dim)} - - is_measurement = True - op_state_fn = OperatorStateFn(matrix_op, is_measurement=is_measurement) # num_qubit = 4 - vec_state_fn = VectorStateFn(vec, is_measurement=is_measurement) # 3 - dic_state_fn = DictStateFn(dic, is_measurement=is_measurement) # 3 - circ_state_fn = CircuitStateFn(circuit_op.to_circuit(), is_measurement=is_measurement) # 2 - - composed_op = op_state_fn @ vec_state_fn @ dic_state_fn @ circ_state_fn - self.assertEqual(composed_op.num_qubits, op_state_fn.num_qubits) - - # with permutation - perm = [2, 4, 6] - composed = ( - op_state_fn - @ dic_state_fn.compose(vec_state_fn, permutation=perm, front=True) - @ circ_state_fn - ) - self.assertEqual(composed.num_qubits, max(perm) + 1) - - def test_summed_op_equals(self): - """Test corner cases of SummedOp's equals function.""" - with self.subTest("multiplicative factor"): - self.assertEqual(2 * X, X + X) - - with self.subTest("commutative"): - self.assertEqual(X + Z, Z + X) - - with self.subTest("circuit and paulis"): - z = CircuitOp(ZGate()) - self.assertEqual(Z + z, z + Z) - - with self.subTest("matrix op and paulis"): - z = MatrixOp([[1, 0], [0, -1]]) - self.assertEqual(Z + z, z + Z) - - with self.subTest("matrix multiplicative"): - z = MatrixOp([[1, 0], [0, -1]]) - self.assertEqual(2 * z, z + z) - - with self.subTest("parameter coefficients"): - expr = Parameter("theta") - z = MatrixOp([[1, 0], [0, -1]]) - self.assertEqual(expr * z, expr * z) - - with self.subTest("different coefficient types"): - expr = Parameter("theta") - z = MatrixOp([[1, 0], [0, -1]]) - self.assertNotEqual(expr * z, 2 * z) - - with self.subTest("additions aggregation"): - z = MatrixOp([[1, 0], [0, -1]]) - a = z + z + Z - b = 2 * z + Z - c = z + Z + z - self.assertEqual(a, b) - self.assertEqual(b, c) - self.assertEqual(a, c) - - def test_circuit_compose_register_independent(self): - """Test that CircuitOp uses combines circuits independent of the register. - - I.e. that is uses ``QuantumCircuit.compose`` over ``combine`` or ``extend``. - """ - op = Z ^ 2 - qr = QuantumRegister(2, "my_qr") - circuit = QuantumCircuit(qr) - composed = op.compose(CircuitOp(circuit)) - - self.assertEqual(composed.num_qubits, 2) - - def test_matrix_op_conversions(self): - """Test to reveal QiskitError when to_instruction or to_circuit method is called on - parameterized matrix op.""" - m = np.array([[0, 0, 1, 0], [0, 0, 0, -1], [1, 0, 0, 0], [0, -1, 0, 0]]) - matrix_op = MatrixOp(m, Parameter("beta")) - for method in ["to_instruction", "to_circuit"]: - with self.subTest(method): - # QiskitError: multiplication of Operator with ParameterExpression isn't implemented - self.assertRaises(QiskitError, getattr(matrix_op, method)) - - def test_list_op_to_circuit(self): - """Test if unitary ListOps transpile to circuit.""" - - # generate unitary matrices of dimension 2,4,8, seed is fixed - np.random.seed(233423) - u2 = unitary_group.rvs(2) - u4 = unitary_group.rvs(4) - u8 = unitary_group.rvs(8) - - # pauli matrices as numpy.arrays - x = np.array([[0.0, 1.0], [1.0, 0.0]]) - y = np.array([[0.0, -1.0j], [1.0j, 0.0]]) - z = np.array([[1.0, 0.0], [0.0, -1.0]]) - - # create MatrixOp and CircuitOp out of matrices - op2 = MatrixOp(u2) - op4 = MatrixOp(u4) - op8 = MatrixOp(u8) - c2 = op2.to_circuit_op() - - # algorithm using only matrix operations on numpy.arrays - xu4 = np.kron(x, u4) - zc2 = np.kron(z, u2) - zc2y = np.kron(zc2, y) - matrix = np.matmul(xu4, zc2y) - matrix = np.matmul(matrix, u8) - matrix = np.kron(matrix, u2) - operator = Operator(matrix) - - # same algorithm as above, but using PrimitiveOps - list_op = ((X ^ op4) @ (Z ^ c2 ^ Y) @ op8) ^ op2 - circuit = list_op.to_circuit() - - # verify that ListOp.to_circuit() outputs correct quantum circuit - self.assertTrue(operator.equiv(circuit), "ListOp.to_circuit() outputs wrong circuit!") - - def test_composed_op_to_circuit(self): - """ - Test if unitary ComposedOp transpile to circuit and represents expected operator. - Test if to_circuit on non-unitary ListOp raises exception. - """ - - x = np.array([[0.0, 1.0], [1.0, 0.0]]) # Pauli X as numpy array - y = np.array([[0.0, -1.0j], [1.0j, 0.0]]) # Pauli Y as numpy array - - m1 = np.array([[0, 0, 1, 0], [0, 0, 0, -1], [0, 0, 0, 0], [0, 0, 0, 0]]) # non-unitary - m2 = np.array([[0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 0], [0, -1, 0, 0]]) # non-unitary - - m_op1 = MatrixOp(m1) - m_op2 = MatrixOp(m2) - - pm1 = (X ^ Y) ^ m_op1 # non-unitary TensoredOp - pm2 = (X ^ Y) ^ m_op2 # non-unitary TensoredOp - - self.assertRaises(ValueError, pm1.to_circuit) - self.assertRaises(ValueError, pm2.to_circuit) - - summed_op = pm1 + pm2 # unitary SummedOp([TensoredOp, TensoredOp]) - circuit = summed_op.to_circuit() # should transpile without any exception - - # same algorithm that leads to summed_op above, but using only arrays and matrix operations - unitary = np.kron(np.kron(x, y), m1 + m2) - - self.assertTrue(Operator(unitary).equiv(circuit)) - - def test_pauli_op_to_circuit(self): - """Test PauliOp.to_circuit()""" - with self.subTest("single Pauli"): - pauli = PauliOp(Pauli("Y")) - expected = QuantumCircuit(1) - expected.y(0) - self.assertEqual(pauli.to_circuit(), expected) - - with self.subTest("single Pauli with phase"): - pauli = PauliOp(Pauli("-iX")) - expected = QuantumCircuit(1) - expected.x(0) - expected.global_phase = -pi / 2 - self.assertEqual(Operator(pauli.to_circuit()), Operator(expected)) - - with self.subTest("two qubit"): - pauli = PauliOp(Pauli("IX")) - expected = QuantumCircuit(2) - expected.pauli("IX", range(2)) - self.assertEqual(pauli.to_circuit(), expected) - expected = QuantumCircuit(2) - expected.x(0) - self.assertEqual(pauli.to_circuit().decompose(), expected) - - with self.subTest("Pauli identity"): - pauli = PauliOp(Pauli("I")) - expected = QuantumCircuit(1) - self.assertEqual(pauli.to_circuit(), expected) - - with self.subTest("two qubit with phase"): - pauli = PauliOp(Pauli("iXZ")) - expected = QuantumCircuit(2) - expected.pauli("XZ", range(2)) - expected.global_phase = pi / 2 - self.assertEqual(pauli.to_circuit(), expected) - expected = QuantumCircuit(2) - expected.z(0) - expected.x(1) - expected.global_phase = pi / 2 - self.assertEqual(pauli.to_circuit().decompose(), expected) - - def test_op_to_circuit_with_parameters(self): - """On parameterized SummedOp, to_matrix_op returns ListOp, instead of MatrixOp. To avoid - the infinite recursion, OpflowError is raised.""" - m1 = np.array([[0, 0, 1, 0], [0, 0, 0, -1], [0, 0, 0, 0], [0, 0, 0, 0]]) # non-unitary - m2 = np.array([[0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 0], [0, -1, 0, 0]]) # non-unitary - - op1_with_param = MatrixOp(m1, Parameter("alpha")) # non-unitary - op2_with_param = MatrixOp(m2, Parameter("beta")) # non-unitary - - summed_op_with_param = op1_with_param + op2_with_param # unitary - # should raise OpflowError error - self.assertRaises(OpflowError, summed_op_with_param.to_circuit) - - def test_permute_list_op_with_inconsistent_num_qubits(self): - """Test if permute raises error if ListOp contains operators with different num_qubits.""" - list_op = ListOp([X, X ^ X]) - self.assertRaises(OpflowError, list_op.permute, [0, 1]) - - @data(Z, CircuitOp(ZGate()), MatrixOp([[1, 0], [0, -1]])) - def test_op_indent(self, op): - """Test that indentation correctly adds INDENTATION at the beginning of each line""" - initial_str = str(op) - indented_str = op._indent(initial_str) - starts_with_indent = indented_str.startswith(op.INDENTATION) - self.assertTrue(starts_with_indent) - indented_str_content = (indented_str[len(op.INDENTATION) :]).split(f"\n{op.INDENTATION}") - self.assertListEqual(indented_str_content, initial_str.split("\n")) - - def test_composed_op_immutable_under_eval(self): - """Test ``ComposedOp.eval`` does not change the operator instance.""" - op = 2 * ComposedOp([X]) - _ = op.eval() - # previous bug: after op.eval(), op was 2 * ComposedOp([2 * X]) - self.assertEqual(op, 2 * ComposedOp([X])) - - def test_op_parameters(self): - """Test that Parameters are stored correctly""" - phi = Parameter("φ") - theta = ParameterVector(name="θ", length=2) - - qc = QuantumCircuit(2) - qc.rz(phi, 0) - qc.rz(phi, 1) - for i in range(2): - qc.rx(theta[i], i) - qc.h(0) - qc.x(1) - - l = Parameter("λ") - op = PrimitiveOp(qc, coeff=l) - - params = {phi, l, *theta.params} - - self.assertEqual(params, op.parameters) - self.assertEqual(params, StateFn(op).parameters) - self.assertEqual(params, StateFn(qc, coeff=l).parameters) - - def test_list_op_parameters(self): - """Test that Parameters are stored correctly in a List Operator""" - lam = Parameter("λ") - phi = Parameter("φ") - omega = Parameter("ω") - - mat_op = PrimitiveOp([[0, 1], [1, 0]], coeff=omega) - - qc = QuantumCircuit(1) - qc.rx(phi, 0) - qc_op = PrimitiveOp(qc) - - op1 = SummedOp([mat_op, qc_op]) - - params = [phi, omega] - self.assertEqual(op1.parameters, set(params)) - - # check list nesting case - op2 = PrimitiveOp([[1, 0], [0, -1]], coeff=lam) - - list_op = ListOp([op1, op2]) - - params.append(lam) - self.assertEqual(list_op.parameters, set(params)) - - @data( - VectorStateFn([1, 0]), - CircuitStateFn(QuantumCircuit(1)), - OperatorStateFn(I), - OperatorStateFn(MatrixOp([[1, 0], [0, 1]])), - OperatorStateFn(CircuitOp(QuantumCircuit(1))), - ) - def test_statefn_eval(self, op): - """Test calling eval on StateFn returns the statevector.""" - expected = Statevector([1, 0]) - self.assertEqual(op.eval().primitive, expected) - - def test_sparse_eval(self): - """Test calling eval on a DictStateFn returns a sparse statevector.""" - op = DictStateFn({"0": 1}) - expected = scipy.sparse.csr_matrix([[1, 0]]) - self.assertFalse((op.eval().primitive != expected).toarray().any()) - - def test_sparse_to_dict(self): - """Test converting a sparse vector state function to a dict state function.""" - isqrt2 = 1 / np.sqrt(2) - sparse = scipy.sparse.csr_matrix([[0, isqrt2, 0, isqrt2]]) - sparse_fn = SparseVectorStateFn(sparse) - dict_fn = DictStateFn({"01": isqrt2, "11": isqrt2}) - - with self.subTest("sparse to dict"): - self.assertEqual(dict_fn, sparse_fn.to_dict_fn()) - - with self.subTest("dict to sparse"): - self.assertEqual(dict_fn.to_spmatrix_op(), sparse_fn) - - def test_to_circuit_op(self): - """Test to_circuit_op method.""" - vector = np.array([2, 2]) - vsfn = VectorStateFn([1, 1], coeff=2) - dsfn = DictStateFn({"0": 1, "1": 1}, coeff=2) - - for sfn in [vsfn, dsfn]: - np.testing.assert_array_almost_equal(sfn.to_circuit_op().eval().primitive.data, vector) - - def test_invalid_primitive(self): - """Test invalid MatrixOp construction""" - msg = ( - "MatrixOp can only be instantiated with " - "['list', 'ndarray', 'spmatrix', 'Operator'], not " - ) - - with self.assertRaises(TypeError) as cm: - _ = MatrixOp("invalid") - - self.assertEqual(str(cm.exception), msg + "'str'") - - with self.assertRaises(TypeError) as cm: - _ = MatrixOp(None) - - self.assertEqual(str(cm.exception), msg + "'NoneType'") - - with self.assertRaises(TypeError) as cm: - _ = MatrixOp(2.0) - - self.assertEqual(str(cm.exception), msg + "'float'") - - def test_summedop_equals(self): - """Test SummedOp.equals""" - ops = [Z, CircuitOp(ZGate()), MatrixOp([[1, 0], [0, -1]]), Zero, Minus] - sum_op = sum(ops + [ListOp(ops)]) - self.assertEqual(sum_op, sum_op) - self.assertEqual(sum_op + sum_op, 2 * sum_op) - self.assertEqual(sum_op + sum_op + sum_op, 3 * sum_op) - ops2 = [Z, CircuitOp(ZGate()), MatrixOp([[1, 0], [0, 1]]), Zero, Minus] - sum_op2 = sum(ops2 + [ListOp(ops)]) - self.assertNotEqual(sum_op, sum_op2) - self.assertEqual(sum_op2, sum_op2) - sum_op3 = sum(ops) - self.assertNotEqual(sum_op, sum_op3) - self.assertNotEqual(sum_op2, sum_op3) - self.assertEqual(sum_op3, sum_op3) - - def test_empty_listops(self): - """Test reduce and eval on ListOp with empty oplist.""" - with self.subTest("reduce empty ComposedOp "): - self.assertEqual(ComposedOp([]).reduce(), ComposedOp([])) - with self.subTest("reduce empty TensoredOp "): - self.assertEqual(TensoredOp([]).reduce(), TensoredOp([])) - with self.subTest("eval empty ComposedOp "): - self.assertEqual(ComposedOp([]).eval(), 0.0) - with self.subTest("eval empty TensoredOp "): - self.assertEqual(TensoredOp([]).eval(), 0.0) - - def test_composed_op_to_matrix_with_coeff(self): - """Test coefficients are properly handled. - - Regression test of Qiskit/qiskit-terra#9283. - """ - x = MatrixOp(X.to_matrix()) - composed = 0.5 * (x @ X) - - expected = 0.5 * np.eye(2) - - np.testing.assert_almost_equal(composed.to_matrix(), expected) - - def test_composed_op_to_matrix_with_vector(self): - """Test a matrix-vector composed op can be cast to matrix. - - Regression test of Qiskit/qiskit-terra#9283. - """ - x = MatrixOp(X.to_matrix()) - composed = x @ Zero - - expected = np.array([0, 1]) - - np.testing.assert_almost_equal(composed.to_matrix(), expected) - - def test_tensored_op_to_matrix(self): - """Test tensored operators to matrix works correctly with a global coefficient. - - Regression test of Qiskit/qiskit-terra#9398. - """ - op = TensoredOp([X, I], coeff=0.5) - expected = 1 / 2 * np.kron(X.to_matrix(), I.to_matrix()) - np.testing.assert_almost_equal(op.to_matrix(), expected) - - -class TestOpMethods(QiskitOpflowTestCase): - """Basic method tests.""" - - def test_listop_num_qubits(self): - """Test that ListOp.num_qubits checks that all operators have the same number of qubits.""" - op = ListOp([X ^ Y, Y ^ Z]) - with self.subTest("All operators have the same numbers of qubits"): - self.assertEqual(op.num_qubits, 2) - - op = ListOp([X ^ Y, Y]) - with self.subTest("Operators have different numbers of qubits"): - with self.assertRaises(ValueError): - op.num_qubits # pylint: disable=pointless-statement - - with self.assertRaises(ValueError): - X @ op # pylint: disable=pointless-statement - - def test_is_hermitian(self): - """Test is_hermitian method.""" - with self.subTest("I"): - self.assertTrue(I.is_hermitian()) - - with self.subTest("X"): - self.assertTrue(X.is_hermitian()) - - with self.subTest("Y"): - self.assertTrue(Y.is_hermitian()) - - with self.subTest("Z"): - self.assertTrue(Z.is_hermitian()) - - with self.subTest("XY"): - self.assertFalse((X @ Y).is_hermitian()) - - with self.subTest("CX"): - self.assertTrue(CX.is_hermitian()) - - with self.subTest("T"): - self.assertFalse(T.is_hermitian()) - - -@ddt -class TestListOpMethods(QiskitOpflowTestCase): - """Test ListOp accessing methods""" - - @data(ListOp, SummedOp, ComposedOp, TensoredOp) - def test_indexing(self, list_op_type): - """Test indexing and slicing""" - coeff = 3 + 0.2j - states_op = list_op_type([X, Y, Z, I], coeff=coeff) - - single_op = states_op[1] - self.assertIsInstance(single_op, OperatorBase) - self.assertNotIsInstance(single_op, ListOp) - - list_one_element = states_op[1:2] - self.assertIsInstance(list_one_element, list_op_type) - self.assertEqual(len(list_one_element), 1) - self.assertEqual(list_one_element[0], Y) - - list_two_elements = states_op[::2] - self.assertIsInstance(list_two_elements, list_op_type) - self.assertEqual(len(list_two_elements), 2) - self.assertEqual(list_two_elements[0], X) - self.assertEqual(list_two_elements[1], Z) - - self.assertEqual(list_one_element.coeff, coeff) - self.assertEqual(list_two_elements.coeff, coeff) - - -class TestListOpComboFn(QiskitOpflowTestCase): - """Test combo fn is propagated.""" - - def setUp(self): - super().setUp() - self.combo_fn = lambda x: [x_i**2 for x_i in x] - self.listop = ListOp([X], combo_fn=self.combo_fn) - - def assertComboFnPreserved(self, processed_op): - """Assert the quadratic combo_fn is preserved.""" - x = [1, 2, 3] - self.assertListEqual(processed_op.combo_fn(x), self.combo_fn(x)) - - def test_at_conversion(self): - """Test after conversion the combo_fn is preserved.""" - for method in ["to_matrix_op", "to_pauli_op", "to_circuit_op"]: - with self.subTest(method): - converted = getattr(self.listop, method)() - self.assertComboFnPreserved(converted) - - def test_after_mul(self): - """Test after multiplication the combo_fn is preserved.""" - self.assertComboFnPreserved(2 * self.listop) - - def test_at_traverse(self): - """Test after traversing the combo_fn is preserved.""" - - def traverse_fn(op): - return -op - - traversed = self.listop.traverse(traverse_fn) - self.assertComboFnPreserved(traversed) - - def test_after_adjoint(self): - """Test after traversing the combo_fn is preserved.""" - self.assertComboFnPreserved(self.listop.adjoint()) - - def test_after_reduce(self): - """Test after reducing the combo_fn is preserved.""" - self.assertComboFnPreserved(self.listop.reduce()) - - -def pauli_group_labels(nq, full_group=True): - """Generate list of the N-qubit pauli group string labels""" - labels = ["".join(i) for i in itertools.product(("I", "X", "Y", "Z"), repeat=nq)] - if full_group: - labels = ["".join(i) for i in itertools.product(("", "-i", "-", "i"), labels)] - return labels - - -def operator_from_label(label): - """Construct operator from full Pauli group label""" - return Operator(Pauli(label)) - - -@ddt -class TestPauliOp(QiskitOpflowTestCase): - """PauliOp tests.""" - - def test_construct(self): - """constructor test""" - pauli = Pauli("XYZX") - coeff = 3.0 - pauli_op = PauliOp(pauli, coeff) - self.assertIsInstance(pauli_op, PauliOp) - self.assertEqual(pauli_op.primitive, pauli) - self.assertEqual(pauli_op.coeff, coeff) - self.assertEqual(pauli_op.num_qubits, 4) - - def test_add(self): - """add test""" - pauli_sum = X + Y - summed_op = SummedOp([X, Y]) - self.assertEqual(pauli_sum, summed_op) - - a = Parameter("a") - b = Parameter("b") - actual = PauliOp(Pauli("X"), a) + PauliOp(Pauli("Y"), b) - expected = SummedOp([PauliOp(Pauli("X"), a), PauliOp(Pauli("Y"), b)]) - self.assertEqual(actual, expected) - - def test_adjoint(self): - """adjoint test""" - pauli_op = PauliOp(Pauli("XYZX"), coeff=3) - expected = PauliOp(Pauli("XYZX"), coeff=3) - - self.assertEqual(~pauli_op, expected) - - pauli_op = PauliOp(Pauli("XXY"), coeff=2j) - expected = PauliOp(Pauli("XXY"), coeff=-2j) - self.assertEqual(~pauli_op, expected) - - pauli_op = PauliOp(Pauli("XYZX"), coeff=2 + 3j) - expected = PauliOp(Pauli("XYZX"), coeff=2 - 3j) - self.assertEqual(~pauli_op, expected) - - pauli_op = PauliOp(Pauli("iXYZX"), coeff=2 + 3j) - expected = PauliOp(Pauli("-iXYZX"), coeff=2 - 3j) - self.assertEqual(~pauli_op, expected) - - @data(*itertools.product(pauli_group_labels(2, full_group=True), repeat=2)) - @unpack - def test_compose(self, label1, label2): - """compose test""" - p1 = PauliOp(Pauli(label1)) - p2 = PauliOp(Pauli(label2)) - value = Operator(p1 @ p2) - op1 = operator_from_label(label1) - op2 = operator_from_label(label2) - target = op1 @ op2 - self.assertEqual(value, target) - - def test_equals(self): - """equality test""" - - self.assertEqual(I @ X, X) - self.assertEqual(X, I @ X) - - theta = Parameter("theta") - pauli_op = theta * X ^ Z - expected = PauliOp( - Pauli("XZ"), - coeff=1.0 * theta, - ) - self.assertEqual(pauli_op, expected) - - def test_eval(self): - """eval test""" - target0 = (X ^ Y ^ Z).eval("000") - target1 = (X ^ Y ^ Z).eval(Zero ^ 3) - expected = DictStateFn({"110": 1j}) - self.assertEqual(target0, expected) - self.assertEqual(target1, expected) - - def test_exp_i(self): - """exp_i test""" - target = (2 * X ^ Z).exp_i() - expected = EvolvedOp(PauliOp(Pauli("XZ"), coeff=2.0), coeff=1.0) - self.assertEqual(target, expected) - - @data(([1, 2, 4], "XIYZI"), ([2, 1, 0], "ZYX")) - @unpack - def test_permute(self, permutation, expected_pauli): - """Test the permute method.""" - pauli_op = PauliOp(Pauli("XYZ"), coeff=1.0) - expected = PauliOp(Pauli(expected_pauli), coeff=1.0) - permuted = pauli_op.permute(permutation) - - with self.subTest(msg="test permutated object"): - self.assertEqual(permuted, expected) - - with self.subTest(msg="test original object is unchanged"): - original = PauliOp(Pauli("XYZ")) - self.assertEqual(pauli_op, original) - - def test_primitive_strings(self): - """primitive strings test""" - target = (2 * X ^ Z).primitive_strings() - expected = {"Pauli"} - self.assertEqual(target, expected) - - def test_tensor(self): - """tensor test""" - pauli_op = X ^ Y ^ Z - tensored_op = PauliOp(Pauli("XYZ")) - self.assertEqual(pauli_op, tensored_op) - - def test_to_instruction(self): - """to_instruction test""" - target = (X ^ Z).to_instruction() - qc = QuantumCircuit(2) - qc.u(0, 0, np.pi, 0) - qc.u(np.pi, 0, np.pi, 1) - qc_out = QuantumCircuit(2) - qc_out.append(target, qc_out.qubits) - qc_out = transpile(qc_out, basis_gates=["u"]) - self.assertEqual(qc, qc_out) - - def test_to_matrix(self): - """to_matrix test""" - target = (X ^ Y).to_matrix() - expected = np.kron(np.array([[0.0, 1.0], [1.0, 0.0]]), np.array([[0.0, -1j], [1j, 0.0]])) - np.testing.assert_array_equal(target, expected) - - def test_to_spmatrix(self): - """to_spmatrix test""" - target = X ^ Y - expected = csr_matrix( - np.kron(np.array([[0.0, 1.0], [1.0, 0.0]]), np.array([[0.0, -1j], [1j, 0.0]])) - ) - self.assertEqual((target.to_spmatrix() - expected).nnz, 0) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_pauli_basis_change.py b/test/python/opflow/test_pauli_basis_change.py deleted file mode 100644 index 5b9f9f324c97..000000000000 --- a/test/python/opflow/test_pauli_basis_change.py +++ /dev/null @@ -1,156 +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. - -"""Test Pauli Change of Basis Converter""" - -import itertools -import unittest -from functools import reduce -from test.python.opflow import QiskitOpflowTestCase - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.opflow import ( - ComposedOp, - I, - OperatorStateFn, - PauliSumOp, - SummedOp, - X, - Y, - Z, -) -from qiskit.opflow.converters import PauliBasisChange -from qiskit.quantum_info import Pauli, SparsePauliOp - - -class TestPauliCoB(QiskitOpflowTestCase): - """Pauli Change of Basis Converter tests.""" - - def test_pauli_cob_singles(self): - """from to file test""" - singles = [X, Y, Z] - dests = [None, Y] - for pauli, dest in itertools.product(singles, dests): - # print(pauli) - converter = PauliBasisChange(destination_basis=dest) - inst, dest = converter.get_cob_circuit(pauli.primitive) - cob = converter.convert(pauli) - np.testing.assert_array_almost_equal( - pauli.to_matrix(), inst.adjoint().to_matrix() @ dest.to_matrix() @ inst.to_matrix() - ) - np.testing.assert_array_almost_equal(pauli.to_matrix(), cob.to_matrix()) - np.testing.assert_array_almost_equal( - inst.compose(pauli).compose(inst.adjoint()).to_matrix(), dest.to_matrix() - ) - - def test_pauli_cob_two_qubit(self): - """pauli cob two qubit test""" - multis = [Y ^ X, Z ^ Y, I ^ Z, Z ^ I, X ^ X, I ^ X] - for pauli, dest in itertools.product(multis, reversed(multis)): - converter = PauliBasisChange(destination_basis=dest) - inst, dest = converter.get_cob_circuit(pauli.primitive) - cob = converter.convert(pauli) - np.testing.assert_array_almost_equal( - pauli.to_matrix(), inst.adjoint().to_matrix() @ dest.to_matrix() @ inst.to_matrix() - ) - np.testing.assert_array_almost_equal(pauli.to_matrix(), cob.to_matrix()) - np.testing.assert_array_almost_equal( - inst.compose(pauli).compose(inst.adjoint()).to_matrix(), dest.to_matrix() - ) - - def test_pauli_cob_multiqubit(self): - """pauli cob multi qubit test""" - # Helpful prints for debugging commented out below. - multis = [Y ^ X ^ I ^ I, I ^ Z ^ Y ^ X, X ^ Y ^ I ^ Z, I ^ I ^ I ^ X, X ^ X ^ X ^ X] - for pauli, dest in itertools.product(multis, reversed(multis)): - # print(pauli) - # print(dest) - converter = PauliBasisChange(destination_basis=dest) - inst, dest = converter.get_cob_circuit(pauli.primitive) - cob = converter.convert(pauli) - # print(inst) - # print(pauli.to_matrix()) - # print(np.round(inst.adjoint().to_matrix() @ cob.to_matrix())) - np.testing.assert_array_almost_equal( - pauli.to_matrix(), inst.adjoint().to_matrix() @ dest.to_matrix() @ inst.to_matrix() - ) - np.testing.assert_array_almost_equal(pauli.to_matrix(), cob.to_matrix()) - np.testing.assert_array_almost_equal( - inst.compose(pauli).compose(inst.adjoint()).to_matrix(), dest.to_matrix() - ) - - def test_pauli_cob_traverse(self): - """pauli cob traverse test""" - # Helpful prints for debugging commented out below. - multis = [(X ^ Y) + (I ^ Z) + (Z ^ Z), (Y ^ X ^ I ^ I) + (I ^ Z ^ Y ^ X)] - dests = [Y ^ Y, I ^ I ^ I ^ Z] - for paulis, dest in zip(multis, dests): - converter = PauliBasisChange(destination_basis=dest, traverse=True) - - cob = converter.convert(paulis) - self.assertIsInstance(cob, SummedOp) - inst = [None] * len(paulis) - ret_dest = [None] * len(paulis) - cob_mat = [None] * len(paulis) - for i, pauli in enumerate(paulis): - inst[i], ret_dest[i] = converter.get_cob_circuit(pauli.to_pauli_op().primitive) - self.assertEqual(dest, ret_dest[i]) - - self.assertIsInstance(cob.oplist[i], ComposedOp) - cob_mat[i] = cob.oplist[i].to_matrix() - np.testing.assert_array_almost_equal(pauli.to_matrix(), cob_mat[i]) - np.testing.assert_array_almost_equal(paulis.to_matrix(), sum(cob_mat)) - - def test_grouped_pauli(self): - """grouped pauli test""" - pauli = 2 * (I ^ I) + (X ^ I) + 3 * (X ^ Y) - grouped_pauli = PauliSumOp(pauli.primitive, grouping_type="TPB") - - converter = PauliBasisChange() - cob = converter.convert(grouped_pauli) - np.testing.assert_array_almost_equal(pauli.to_matrix(), cob.to_matrix()) - - origin_x = reduce(np.logical_or, pauli.primitive.paulis.x) - origin_z = reduce(np.logical_or, pauli.primitive.paulis.z) - origin_pauli = Pauli((origin_z, origin_x)) - inst, dest = converter.get_cob_circuit(origin_pauli) - self.assertEqual(str(dest), "ZZ") - expected_inst = np.array( - [ - [0.5, -0.5j, 0.5, -0.5j], - [0.5, 0.5j, 0.5, 0.5j], - [0.5, -0.5j, -0.5, 0.5j], - [0.5, 0.5j, -0.5, -0.5j], - ] - ) - np.testing.assert_array_almost_equal(inst.to_matrix(), expected_inst) - - def test_grouped_pauli_statefn(self): - """grouped pauli test with statefn""" - grouped_pauli = PauliSumOp(SparsePauliOp(["Y"]), grouping_type="TPB") - observable = OperatorStateFn(grouped_pauli, is_measurement=True) - - converter = PauliBasisChange(replacement_fn=PauliBasisChange.measurement_replacement_fn) - cob = converter.convert(observable) - - expected = PauliSumOp(SparsePauliOp(["Z"]), grouping_type="TPB") - self.assertEqual(cob[0].primitive, expected) - circuit = QuantumCircuit(1) - circuit.sdg(0) - circuit.h(0) - self.assertEqual(cob[1].primitive, circuit) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_pauli_expectation.py b/test/python/opflow/test_pauli_expectation.py deleted file mode 100644 index 19821aab56da..000000000000 --- a/test/python/opflow/test_pauli_expectation.py +++ /dev/null @@ -1,317 +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 PauliExpectation""" - -import itertools -import unittest -import warnings -from test.python.opflow import QiskitOpflowTestCase - -import numpy as np - -from qiskit import BasicAer -from qiskit.opflow import ( - CX, - CircuitSampler, - H, - I, - ListOp, - Minus, - One, - PauliExpectation, - PauliSumOp, - Plus, - S, - StateFn, - X, - Y, - Z, - Zero, -) -from qiskit.utils import QuantumInstance, algorithm_globals - - -# pylint: disable=invalid-name - - -class TestPauliExpectation(QiskitOpflowTestCase): - """Pauli Change of Basis Expectation tests.""" - - def setUp(self) -> None: - super().setUp() - self.seed = 97 - backend = BasicAer.get_backend("qasm_simulator") - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend, seed_simulator=self.seed, seed_transpiler=self.seed - ) - self.sampler = CircuitSampler(q_instance, attach_results=True) - self.expect = PauliExpectation() - - def test_pauli_expect_pair(self): - """pauli expect pair test""" - - op = Z ^ Z - # wf = (Pl^Pl) + (Ze^Ze) - wf = CX @ (H ^ I) @ Zero - - converted_meas = self.expect.convert(~StateFn(op) @ wf) - self.assertAlmostEqual(converted_meas.eval(), 0, delta=0.1) - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - self.assertAlmostEqual(sampled.eval(), 0, delta=0.1) - - def test_pauli_expect_single(self): - """pauli expect single test""" - - paulis = [Z, X, Y, I] - states = [Zero, One, Plus, Minus, S @ Plus, S @ Minus] - for pauli, state in itertools.product(paulis, states): - converted_meas = self.expect.convert(~StateFn(pauli) @ state) - matmulmean = state.adjoint().to_matrix() @ pauli.to_matrix() @ state.to_matrix() - self.assertAlmostEqual(converted_meas.eval(), matmulmean, delta=0.1) - - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - - self.assertAlmostEqual(sampled.eval(), matmulmean, delta=0.1) - - def test_pauli_expect_op_vector(self): - """pauli expect op vector test""" - - paulis_op = ListOp([X, Y, Z, I]) - converted_meas = self.expect.convert(~StateFn(paulis_op)) - - plus_mean = converted_meas @ Plus - np.testing.assert_array_almost_equal(plus_mean.eval(), [1, 0, 0, 1], decimal=1) - - with self.assertWarns(DeprecationWarning): - sampled_plus = self.sampler.convert(plus_mean) - np.testing.assert_array_almost_equal(sampled_plus.eval(), [1, 0, 0, 1], decimal=1) - - minus_mean = converted_meas @ Minus - np.testing.assert_array_almost_equal(minus_mean.eval(), [-1, 0, 0, 1], decimal=1) - - sampled_minus = self.sampler.convert(minus_mean) - np.testing.assert_array_almost_equal(sampled_minus.eval(), [-1, 0, 0, 1], decimal=1) - - zero_mean = converted_meas @ Zero - np.testing.assert_array_almost_equal(zero_mean.eval(), [0, 0, 1, 1], decimal=1) - - sampled_zero = self.sampler.convert(zero_mean) - np.testing.assert_array_almost_equal(sampled_zero.eval(), [0, 0, 1, 1], decimal=1) - - sum_zero = (Plus + Minus) * (0.5**0.5) - sum_zero_mean = converted_meas @ sum_zero - np.testing.assert_array_almost_equal(sum_zero_mean.eval(), [0, 0, 1, 1], decimal=1) - - sampled_zero_mean = self.sampler.convert(sum_zero_mean) - - # !!NOTE!!: Depolarizing channel (Sampling) means interference - # does not happen between circuits in sum, so expectation does - # not equal expectation for Zero!! - np.testing.assert_array_almost_equal(sampled_zero_mean.eval(), [0, 0, 0, 1], decimal=1) - - for i, op in enumerate(paulis_op.oplist): - mat_op = op.to_matrix() - np.testing.assert_array_almost_equal( - zero_mean.eval()[i], - Zero.adjoint().to_matrix() @ mat_op @ Zero.to_matrix(), - decimal=1, - ) - np.testing.assert_array_almost_equal( - plus_mean.eval()[i], - Plus.adjoint().to_matrix() @ mat_op @ Plus.to_matrix(), - decimal=1, - ) - np.testing.assert_array_almost_equal( - minus_mean.eval()[i], - Minus.adjoint().to_matrix() @ mat_op @ Minus.to_matrix(), - decimal=1, - ) - - def test_pauli_expect_state_vector(self): - """pauli expect state vector test""" - - states_op = ListOp([One, Zero, Plus, Minus]) - - paulis_op = X - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - np.testing.assert_array_almost_equal(converted_meas.eval(), [0, 0, 1, -1], decimal=1) - - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - - np.testing.assert_array_almost_equal(sampled.eval(), [0, 0, 1, -1], decimal=1) - - # Small test to see if execution results are accessible - for composed_op in sampled: - self.assertIn("counts", composed_op[1].execution_results) - - def test_pauli_expect_op_vector_state_vector(self): - """pauli expect op vector state vector test""" - - paulis_op = ListOp([X, Y, Z, I]) - states_op = ListOp([One, Zero, Plus, Minus]) - - valids = [[+0, 0, 1, -1], [+0, 0, 0, 0], [-1, 1, 0, -0], [+1, 1, 1, 1]] - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - np.testing.assert_array_almost_equal(converted_meas.eval(), valids, decimal=1) - - with self.assertWarns(DeprecationWarning): - sampled = self.sampler.convert(converted_meas) - - np.testing.assert_array_almost_equal(sampled.eval(), valids, decimal=1) - - def test_to_matrix_called(self): - """test to matrix called in different situations""" - qs = 45 - - states_op = ListOp([Zero ^ qs, One ^ qs, (Zero ^ qs) + (One ^ qs)]) - paulis_op = ListOp([Z ^ qs, (I ^ Z ^ I) ^ int(qs / 3)]) - - # 45 qubit calculation - throws exception if to_matrix is called - # massive is False - with self.assertRaises(ValueError): - states_op.to_matrix() - paulis_op.to_matrix() - - # now set global variable or argument - try: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.massive = True - with self.assertRaises(MemoryError): - states_op.to_matrix() - paulis_op.to_matrix() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.massive = False - with self.assertRaises(MemoryError): - states_op.to_matrix(massive=True) - paulis_op.to_matrix(massive=True) - finally: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.massive = False - - def test_not_to_matrix_called(self): - """45 qubit calculation - literally will not work if to_matrix is - somehow called (in addition to massive=False throwing an error)""" - - qs = 45 - states_op = ListOp([Zero ^ qs, One ^ qs, (Zero ^ qs) + (One ^ qs)]) - paulis_op = ListOp([Z ^ qs, (I ^ Z ^ I) ^ int(qs / 3)]) - - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - np.testing.assert_array_almost_equal(converted_meas.eval(), [[1, -1, 0], [1, -1, 0]]) - - def test_grouped_pauli_expectation(self): - """grouped pauli expectation test""" - - two_qubit_H2 = ( - (-1.052373245772859 * I ^ I) - + (0.39793742484318045 * I ^ Z) - + (-0.39793742484318045 * Z ^ I) - + (-0.01128010425623538 * Z ^ Z) - + (0.18093119978423156 * X ^ X) - ) - wf = CX @ (H ^ I) @ Zero - expect_op = PauliExpectation(group_paulis=False).convert(~StateFn(two_qubit_H2) @ wf) - self.sampler._extract_circuitstatefns(expect_op) - num_circuits_ungrouped = len(self.sampler._circuit_ops_cache) - self.assertEqual(num_circuits_ungrouped, 5) - - expect_op_grouped = PauliExpectation(group_paulis=True).convert(~StateFn(two_qubit_H2) @ wf) - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - - sampler = CircuitSampler(q_instance) - sampler._extract_circuitstatefns(expect_op_grouped) - num_circuits_grouped = len(sampler._circuit_ops_cache) - - self.assertEqual(num_circuits_grouped, 2) - - @unittest.skip(reason="IBMQ testing not available in general.") - def test_ibmq_grouped_pauli_expectation(self): - """pauli expect op vector state vector test""" - from qiskit import IBMQ - - p = IBMQ.load_account() - backend = p.get_backend("ibmq_qasm_simulator") - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - backend, seed_simulator=self.seed, seed_transpiler=self.seed - ) - paulis_op = ListOp([X, Y, Z, I]) - states_op = ListOp([One, Zero, Plus, Minus]) - - valids = [[+0, 0, 1, -1], [+0, 0, 0, 0], [-1, 1, 0, -0], [+1, 1, 1, 1]] - converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) - - with self.assertWarns(DeprecationWarning): - sampled = CircuitSampler(q_instance).convert(converted_meas) - np.testing.assert_array_almost_equal(sampled.eval(), valids, decimal=1) - - def test_multi_representation_ops(self): - """Test observables with mixed representations""" - - mixed_ops = ListOp([X.to_matrix_op(), H, H + I, X]) - converted_meas = self.expect.convert(~StateFn(mixed_ops)) - plus_mean = converted_meas @ Plus - - with self.assertWarns(DeprecationWarning): - sampled_plus = self.sampler.convert(plus_mean) - np.testing.assert_array_almost_equal( - sampled_plus.eval(), [1, 0.5**0.5, (1 + 0.5**0.5), 1], decimal=1 - ) - - def test_pauli_expectation_non_hermite_op(self): - """Test PauliExpectation for non hermitian operator""" - - exp = ~StateFn(1j * Z) @ One - self.assertEqual(self.expect.convert(exp).eval(), 1j) - - def test_list_pauli_sum_op(self): - """Test PauliExpectation for List[PauliSumOp]""" - - test_op = ListOp([~StateFn(PauliSumOp.from_list([("XX", 1), ("ZI", 3), ("ZZ", 5)]))]) - observable = self.expect.convert(test_op) - self.assertIsInstance(observable, ListOp) - self.assertIsInstance(observable[0][0][0].primitive, PauliSumOp) - self.assertIsInstance(observable[0][1][0].primitive, PauliSumOp) - - def test_expectation_with_coeff(self): - """Test PauliExpectation with coefficients.""" - - with self.subTest("integer coefficients"): - exp = 3 * ~StateFn(X) @ (2 * Minus) - with self.assertWarns(DeprecationWarning): - target = self.sampler.convert(self.expect.convert(exp)).eval() - self.assertEqual(target, -12) - - with self.subTest("complex coefficients"): - exp = 3j * ~StateFn(X) @ (2j * Minus) - with self.assertWarns(DeprecationWarning): - target = self.sampler.convert(self.expect.convert(exp)).eval() - self.assertEqual(target, -12j) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_pauli_sum_op.py b/test/python/opflow/test_pauli_sum_op.py deleted file mode 100644 index 7b97756ae3a6..000000000000 --- a/test/python/opflow/test_pauli_sum_op.py +++ /dev/null @@ -1,365 +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. - -"""Test PauliSumOp.""" - -import unittest -from itertools import product -from test.python.opflow import QiskitOpflowTestCase - -import numpy as np -from ddt import data, ddt, unpack -from scipy.sparse import csr_matrix -from sympy import Symbol - -from qiskit import QuantumCircuit, transpile -from qiskit.circuit import Parameter, ParameterExpression, ParameterVector -from qiskit.opflow import ( - CX, - CircuitStateFn, - DictStateFn, - H, - I, - One, - OperatorStateFn, - OpflowError, - PauliSumOp, - SummedOp, - X, - Y, - Z, - Zero, -) -from qiskit.quantum_info import Pauli, PauliList, SparsePauliOp - - -@ddt -class TestPauliSumOp(QiskitOpflowTestCase): - """PauliSumOp tests.""" - - def test_construct(self): - """constructor test""" - sparse_pauli = SparsePauliOp(Pauli("XYZX"), coeffs=[2.0]) - coeff = 3.0 - pauli_sum = PauliSumOp(sparse_pauli, coeff=coeff) - self.assertIsInstance(pauli_sum, PauliSumOp) - self.assertEqual(pauli_sum.primitive, sparse_pauli) - self.assertEqual(pauli_sum.coeff, coeff) - self.assertEqual(pauli_sum.num_qubits, 4) - - def test_coeffs(self): - """ListOp.coeffs test""" - sum1 = SummedOp( - [(0 + 1j) * X, (1 / np.sqrt(2) + 1j / np.sqrt(2)) * Z], 0.5 - ).collapse_summands() - self.assertAlmostEqual(sum1.coeffs[0], 0.5j) - self.assertAlmostEqual(sum1.coeffs[1], (1 + 1j) / (2 * np.sqrt(2))) - - a_param = Parameter("a") - b_param = Parameter("b") - param_exp = ParameterExpression({a_param: 1, b_param: 0}, Symbol("a") ** 2 + Symbol("b")) - sum2 = SummedOp([X, (1 / np.sqrt(2) - 1j / np.sqrt(2)) * Y], param_exp).collapse_summands() - self.assertIsInstance(sum2.coeffs[0], ParameterExpression) - self.assertIsInstance(sum2.coeffs[1], ParameterExpression) - - # Nested ListOp - sum_nested = SummedOp([X, sum1]) - self.assertRaises(TypeError, lambda: sum_nested.coeffs) - - def test_add(self): - """add test""" - pauli_sum = 3 * X + Y - self.assertIsInstance(pauli_sum, PauliSumOp) - expected = PauliSumOp(3.0 * SparsePauliOp(Pauli("X")) + SparsePauliOp(Pauli("Y"))) - self.assertEqual(pauli_sum, expected) - - pauli_sum = X + Y - summed_op = SummedOp([X, Y]) - self.assertEqual(pauli_sum, summed_op) - - a = Parameter("a") - b = Parameter("b") - actual = a * PauliSumOp.from_list([("X", 2)]) + b * PauliSumOp.from_list([("Y", 1)]) - expected = SummedOp( - [PauliSumOp.from_list([("X", 2)], a), PauliSumOp.from_list([("Y", 1)], b)] - ) - self.assertEqual(actual, expected) - - def test_mul(self): - """multiplication test""" - target = 2 * (X + Z) - self.assertEqual(target.coeff, 1) - self.assertListEqual(target.primitive.to_list(), [("X", (2 + 0j)), ("Z", (2 + 0j))]) - - target = 0 * (X + Z) - self.assertEqual(target.coeff, 0) - self.assertListEqual(target.primitive.to_list(), [("X", (1 + 0j)), ("Z", (1 + 0j))]) - - beta = Parameter("β") - target = beta * (X + Z) - self.assertEqual(target.coeff, 1.0 * beta) - self.assertListEqual(target.primitive.to_list(), [("X", (1 + 0j)), ("Z", (1 + 0j))]) - - def test_adjoint(self): - """adjoint test""" - pauli_sum = PauliSumOp(SparsePauliOp(Pauli("XYZX"), coeffs=[2]), coeff=3) - expected = PauliSumOp(SparsePauliOp(Pauli("XYZX")), coeff=6) - - self.assertEqual(pauli_sum.adjoint(), expected) - - pauli_sum = PauliSumOp(SparsePauliOp(Pauli("XYZY"), coeffs=[2]), coeff=3j) - expected = PauliSumOp(SparsePauliOp(Pauli("XYZY")), coeff=-6j) - self.assertEqual(pauli_sum.adjoint(), expected) - - pauli_sum = PauliSumOp(SparsePauliOp(Pauli("X"), coeffs=[1])) - self.assertEqual(pauli_sum.adjoint(), pauli_sum) - - pauli_sum = PauliSumOp(SparsePauliOp(Pauli("Y"), coeffs=[1])) - self.assertEqual(pauli_sum.adjoint(), pauli_sum) - - pauli_sum = PauliSumOp(SparsePauliOp(Pauli("Z"), coeffs=[1])) - self.assertEqual(pauli_sum.adjoint(), pauli_sum) - - pauli_sum = (Z ^ Z) + (Y ^ I) - self.assertEqual(pauli_sum.adjoint(), pauli_sum) - - def test_equals(self): - """equality test""" - - self.assertNotEqual((X ^ X) + (Y ^ Y), X + Y) - self.assertEqual((X ^ X) + (Y ^ Y), (Y ^ Y) + (X ^ X)) - self.assertEqual(0 * X + I, I) - self.assertEqual(I, 0 * X + I) - - theta = ParameterVector("theta", 2) - pauli_sum0 = theta[0] * (X + Z) - pauli_sum1 = theta[1] * (X + Z) - expected = PauliSumOp( - SparsePauliOp(Pauli("X")) + SparsePauliOp(Pauli("Z")), - coeff=1.0 * theta[0], - ) - self.assertEqual(pauli_sum0, expected) - self.assertNotEqual(pauli_sum1, expected) - - def test_tensor(self): - """Test for tensor operation""" - with self.subTest("Test 1"): - pauli_sum = ((I - Z) ^ (I - Z)) + ((X - Y) ^ (X + Y)) - expected = (I ^ I) - (I ^ Z) - (Z ^ I) + (Z ^ Z) + (X ^ X) + (X ^ Y) - (Y ^ X) - (Y ^ Y) - self.assertEqual(pauli_sum, expected) - - with self.subTest("Test 2"): - pauli_sum = (Z + I) ^ Z - expected = (Z ^ Z) + (I ^ Z) - self.assertEqual(pauli_sum, expected) - - with self.subTest("Test 3"): - pauli_sum = Z ^ (Z + I) - expected = (Z ^ Z) + (Z ^ I) - self.assertEqual(pauli_sum, expected) - - @data(([1, 2, 4], "XIYZI"), ([2, 1, 0], "ZYX")) - @unpack - def test_permute(self, permutation, expected_pauli): - """Test the permute method.""" - pauli_sum = PauliSumOp(SparsePauliOp.from_list([("XYZ", 1)])) - expected = PauliSumOp(SparsePauliOp.from_list([(expected_pauli, 1)])) - permuted = pauli_sum.permute(permutation) - - with self.subTest(msg="test permutated object"): - self.assertEqual(permuted, expected) - - with self.subTest(msg="test original object is unchanged"): - original = PauliSumOp(SparsePauliOp.from_list([("XYZ", 1)])) - self.assertEqual(pauli_sum, original) - - @data([1, 2, 1], [1, 2, -1]) - def test_permute_invalid(self, permutation): - """Test the permute method raises an error on invalid permutations.""" - pauli_sum = PauliSumOp(SparsePauliOp((X ^ Y ^ Z).primitive)) - - with self.assertRaises(OpflowError): - pauli_sum.permute(permutation) - - def test_compose(self): - """compose test""" - target = (X + Z) @ (Y + Z) - expected = 1j * Z - 1j * Y - 1j * X + I - self.assertEqual(target, expected) - - observable = (X ^ X) + (Y ^ Y) + (Z ^ Z) - state = CircuitStateFn((CX @ (X ^ H @ X)).to_circuit()) - self.assertAlmostEqual((~OperatorStateFn(observable) @ state).eval(), -3) - - def test_to_matrix(self): - """test for to_matrix method""" - target = (Z + Y).to_matrix() - expected = np.array([[1.0, -1j], [1j, -1]]) - np.testing.assert_array_equal(target, expected) - - def test_str(self): - """str test""" - target = 3.0 * (X + 2.0 * Y - 4.0 * Z) - expected = "3.0 * X\n+ 6.0 * Y\n- 12.0 * Z" - self.assertEqual(str(target), expected) - - alpha = Parameter("α") - target = alpha * (X + 2.0 * Y - 4.0 * Z) - expected = "1.0*α * (\n 1.0 * X\n + 2.0 * Y\n - 4.0 * Z\n)" - self.assertEqual(str(target), expected) - - def test_eval(self): - """eval test""" - target0 = (2 * (X ^ Y ^ Z) + 3 * (X ^ X ^ Z)).eval("000") - target1 = (2 * (X ^ Y ^ Z) + 3 * (X ^ X ^ Z)).eval(Zero ^ 3) - expected = DictStateFn({"110": (3 + 2j)}) - self.assertEqual(target0, expected) - self.assertEqual(target1, expected) - - phi = 0.5 * ((One + Zero) ^ 2) - zero_op = (Z + I) / 2 - one_op = (I - Z) / 2 - h1 = one_op ^ I - h2 = one_op ^ (one_op + zero_op) - h2a = one_op ^ one_op - h2b = one_op ^ zero_op - self.assertEqual((~OperatorStateFn(h1) @ phi).eval(), 0.5) - self.assertEqual((~OperatorStateFn(h2) @ phi).eval(), 0.5) - self.assertEqual((~OperatorStateFn(h2a) @ phi).eval(), 0.25) - self.assertEqual((~OperatorStateFn(h2b) @ phi).eval(), 0.25) - - pauli_op = (Z ^ I ^ X) + (I ^ I ^ Y) - mat_op = pauli_op.to_matrix_op() - full_basis = ["".join(b) for b in product("01", repeat=pauli_op.num_qubits)] - for bstr1, bstr2 in product(full_basis, full_basis): - self.assertEqual(pauli_op.eval(bstr1).eval(bstr2), mat_op.eval(bstr1).eval(bstr2)) - - def test_exp_i(self): - """exp_i test""" - # TODO: add tests when special methods are added - pass - - def test_to_instruction(self): - """test for to_instruction""" - target = ((X + Z) / np.sqrt(2)).to_instruction() - qc = QuantumCircuit(1) - qc.u(np.pi / 2, 0, np.pi, 0) - qc_out = transpile(target.definition, basis_gates=["u"]) - self.assertEqual(qc_out, qc) - - def test_to_pauli_op(self): - """test to_pauli_op method""" - target = X + Y - self.assertIsInstance(target, PauliSumOp) - expected = SummedOp([X, Y]) - self.assertEqual(target.to_pauli_op(), expected) - - def test_getitem(self): - """test get item method""" - target = X + Z - self.assertEqual(target[0], PauliSumOp(SparsePauliOp(X.primitive))) - self.assertEqual(target[1], PauliSumOp(SparsePauliOp(Z.primitive))) - - def test_len(self): - """test len""" - target = X + Y + Z - self.assertEqual(len(target), 3) - - def test_reduce(self): - """test reduce""" - target = X + X + Z - self.assertEqual(len(target.reduce()), 2) - - def test_to_spmatrix(self): - """test to_spmatrix""" - target = X + Y - expected = csr_matrix([[0, 1 - 1j], [1 + 1j, 0]]) - self.assertEqual((target.to_spmatrix() - expected).nnz, 0) - - def test_from_list(self): - """test from_list""" - target = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - expected = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - self.assertEqual(target, expected) - - a = Parameter("a") - target = PauliSumOp.from_list([("X", 0.5 * a), ("Y", -0.5j * a)], dtype=object) - expected = PauliSumOp( - SparsePauliOp.from_list([("X", 0.5 * a), ("Y", -0.5j * a)], dtype=object) - ) - self.assertEqual(target.primitive, expected.primitive) - - def test_matrix_iter(self): - """Test PauliSumOp dense matrix_iter method.""" - labels = ["III", "IXI", "IYY", "YIZ", "XYZ", "III"] - coeffs = np.array([1, 2, 3, 4, 5, 6]) - paulis = PauliList(labels) - coeff = 10 - op = PauliSumOp(SparsePauliOp(paulis, coeffs), coeff) - for idx, i in enumerate(op.matrix_iter()): - self.assertTrue(np.array_equal(i, coeff * coeffs[idx] * Pauli(labels[idx]).to_matrix())) - - def test_matrix_iter_sparse(self): - """Test PauliSumOp sparse matrix_iter method.""" - labels = ["III", "IXI", "IYY", "YIZ", "XYZ", "III"] - coeffs = np.array([1, 2, 3, 4, 5, 6]) - coeff = 10 - paulis = PauliList(labels) - op = PauliSumOp(SparsePauliOp(paulis, coeffs), coeff) - for idx, i in enumerate(op.matrix_iter(sparse=True)): - self.assertTrue( - np.array_equal(i.toarray(), coeff * coeffs[idx] * Pauli(labels[idx]).to_matrix()) - ) - - def test_is_hermitian(self): - """Test is_hermitian method""" - with self.subTest("True test"): - target = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - self.assertTrue(target.is_hermitian()) - - with self.subTest("False test"): - target = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045j), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - self.assertFalse(target.is_hermitian()) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_state_construction.py b/test/python/opflow/test_state_construction.py deleted file mode 100644 index 05d595c18da4..000000000000 --- a/test/python/opflow/test_state_construction.py +++ /dev/null @@ -1,250 +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. - -"""Test Operator construction, including OpPrimitives and singletons.""" - -import unittest -from test.python.opflow import QiskitOpflowTestCase -import numpy as np - -from qiskit import QuantumCircuit, BasicAer, execute -from qiskit.circuit import ParameterVector -from qiskit.quantum_info import Statevector - -from qiskit.opflow import ( - StateFn, - Zero, - One, - Plus, - Minus, - PrimitiveOp, - CircuitOp, - SummedOp, - H, - I, - Z, - X, - Y, - CX, - CircuitStateFn, - DictToCircuitSum, -) - - -class TestStateConstruction(QiskitOpflowTestCase): - """State Construction tests.""" - - def test_state_singletons(self): - """state singletons test""" - self.assertEqual(Zero.primitive, {"0": 1}) - self.assertEqual(One.primitive, {"1": 1}) - - self.assertEqual((Zero ^ 5).primitive, {"00000": 1}) - self.assertEqual((One ^ 5).primitive, {"11111": 1}) - self.assertEqual(((Zero ^ One) ^ 3).primitive, {"010101": 1}) - - def test_zero_broadcast(self): - """zero broadcast test""" - np.testing.assert_array_almost_equal(((H ^ 5) @ Zero).to_matrix(), (Plus ^ 5).to_matrix()) - - def test_state_to_matrix(self): - """state to matrix test""" - np.testing.assert_array_equal(Zero.to_matrix(), np.array([1, 0])) - np.testing.assert_array_equal(One.to_matrix(), np.array([0, 1])) - np.testing.assert_array_almost_equal( - Plus.to_matrix(), (Zero.to_matrix() + One.to_matrix()) / (np.sqrt(2)) - ) - np.testing.assert_array_almost_equal( - Minus.to_matrix(), (Zero.to_matrix() - One.to_matrix()) / (np.sqrt(2)) - ) - - # TODO Not a great test because doesn't test against validated values - # or test internal representation. Fix this. - gnarly_state = (One ^ Plus ^ Zero ^ Minus * 0.3) @ StateFn( - Statevector.from_label("r0+l") - ) + (StateFn(X ^ Z ^ Y ^ I) * 0.1j) - gnarly_mat = gnarly_state.to_matrix() - gnarly_mat_separate = (One ^ Plus ^ Zero ^ Minus * 0.3).to_matrix() - gnarly_mat_separate = np.dot( - gnarly_mat_separate, StateFn(Statevector.from_label("r0+l")).to_matrix() - ) - gnarly_mat_separate = gnarly_mat_separate + (StateFn(X ^ Z ^ Y ^ I) * 0.1j).to_matrix() - np.testing.assert_array_almost_equal(gnarly_mat, gnarly_mat_separate) - - def test_qiskit_result_instantiation(self): - """qiskit result instantiation test""" - qc = QuantumCircuit(3) - # REMEMBER: This is Qubit 2 in Operator land. - qc.h(0) - sv_res = execute(qc, BasicAer.get_backend("statevector_simulator")).result() - sv_vector = sv_res.get_statevector() - qc_op = PrimitiveOp(qc) @ Zero - - qasm_res = execute( - qc_op.to_circuit(meas=True), BasicAer.get_backend("qasm_simulator") - ).result() - - np.testing.assert_array_almost_equal( - StateFn(sv_res).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0] - ) - np.testing.assert_array_almost_equal( - StateFn(sv_vector).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0] - ) - np.testing.assert_array_almost_equal( - StateFn(qasm_res).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0], decimal=1 - ) - - np.testing.assert_array_almost_equal( - ((I ^ I ^ H) @ Zero).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0] - ) - np.testing.assert_array_almost_equal( - qc_op.to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0] - ) - - def test_state_meas_composition(self): - """state meas composition test""" - pass - # print((~Zero^4).eval(Zero^4)) - # print((~One^4).eval(Zero^4)) - # print((~One ^ 4).eval(One ^ 4)) - # print(StateFn(I^Z, is_measurement=True).eval(One^2)) - - def test_add_direct(self): - """add direct test""" - wf = StateFn({"101010": 0.5, "111111": 0.3}) + (Zero ^ 6) - self.assertEqual(wf.primitive, {"101010": 0.5, "111111": 0.3, "000000": 1.0}) - wf = (4 * StateFn({"101010": 0.5, "111111": 0.3})) + ((3 + 0.1j) * (Zero ^ 6)) - self.assertEqual( - wf.primitive, {"000000": (3 + 0.1j), "101010": (2 + 0j), "111111": (1.2 + 0j)} - ) - - def test_circuit_state_fn_from_dict_as_sum(self): - """state fn circuit from dict as sum test""" - statedict = {"1010101": 0.5, "1000000": 0.1, "0000000": 0.2j, "1111111": 0.5j} - sfc_sum = CircuitStateFn.from_dict(statedict) - self.assertIsInstance(sfc_sum, SummedOp) - for sfc_op in sfc_sum.oplist: - self.assertIsInstance(sfc_op, CircuitStateFn) - samples = sfc_op.sample() - self.assertIn(list(samples.keys())[0], statedict) - self.assertEqual(sfc_op.coeff, statedict[list(samples.keys())[0]]) - np.testing.assert_array_almost_equal(StateFn(statedict).to_matrix(), sfc_sum.to_matrix()) - - def test_circuit_state_fn_from_dict_initialize(self): - """state fn circuit from dict initialize test""" - statedict = {"101": 0.5, "100": 0.1, "000": 0.2, "111": 0.5} - sfc = CircuitStateFn.from_dict(statedict) - self.assertIsInstance(sfc, CircuitStateFn) - samples = sfc.sample() - np.testing.assert_array_almost_equal( - StateFn(statedict).to_matrix(), np.round(sfc.to_matrix(), decimals=1) - ) - for k, v in samples.items(): - self.assertIn(k, statedict) - # It's ok if these are far apart because the dict is sampled. - self.assertAlmostEqual(v, np.abs(statedict[k]) ** 0.5, delta=0.5) - - # Follows same code path as above, but testing to be thorough - sfc_vector = CircuitStateFn.from_vector(StateFn(statedict).to_matrix()) - np.testing.assert_array_almost_equal(StateFn(statedict).to_matrix(), sfc_vector.to_matrix()) - - # #1276 - def test_circuit_state_fn_from_complex_vector_initialize(self): - """state fn circuit from complex vector initialize test""" - sfc = CircuitStateFn.from_vector(np.array([1j / np.sqrt(2), 0, 1j / np.sqrt(2), 0])) - self.assertIsInstance(sfc, CircuitStateFn) - - def test_sampling(self): - """state fn circuit from dict initialize test""" - statedict = {"101": 0.5 + 1.0j, "100": 0.1 + 2.0j, "000": 0.2 + 0.0j, "111": 0.5 + 1.0j} - sfc = CircuitStateFn.from_dict(statedict) - circ_samples = sfc.sample() - dict_samples = StateFn(statedict).sample() - vec_samples = StateFn(statedict).to_matrix_op().sample() - for k, v in circ_samples.items(): - self.assertIn(k, dict_samples) - self.assertIn(k, vec_samples) - # It's ok if these are far apart because the dict is sampled. - self.assertAlmostEqual(v, np.abs(dict_samples[k]) ** 0.5, delta=0.5) - self.assertAlmostEqual(v, np.abs(vec_samples[k]) ** 0.5, delta=0.5) - - def test_dict_to_circuit_sum(self): - """Test DictToCircuitSum converter.""" - # Test qubits < entires, so dict is converted to Initialize CircuitStateFn - dict_state_3q = StateFn({"101": 0.5, "100": 0.1, "000": 0.2, "111": 0.5}) - circuit_state_3q = DictToCircuitSum().convert(dict_state_3q) - self.assertIsInstance(circuit_state_3q, CircuitStateFn) - np.testing.assert_array_almost_equal( - dict_state_3q.to_matrix(), circuit_state_3q.to_matrix() - ) - - # Test qubits >= entires, so dict is converted to Initialize CircuitStateFn - dict_state_4q = dict_state_3q ^ Zero - circuit_state_4q = DictToCircuitSum().convert(dict_state_4q) - self.assertIsInstance(circuit_state_4q, SummedOp) - np.testing.assert_array_almost_equal( - dict_state_4q.to_matrix(), circuit_state_4q.to_matrix() - ) - - # Test VectorStateFn conversion - vect_state_3q = dict_state_3q.to_matrix_op() - circuit_state_3q_vect = DictToCircuitSum().convert(vect_state_3q) - self.assertIsInstance(circuit_state_3q_vect, CircuitStateFn) - np.testing.assert_array_almost_equal( - vect_state_3q.to_matrix(), circuit_state_3q_vect.to_matrix() - ) - - def test_circuit_permute(self): - r"""Test the CircuitStateFn's .permute method""" - perm = range(7)[::-1] - c_op = ( - ((CX ^ 3) ^ X) - @ (H ^ 7) - @ (X ^ Y ^ Z ^ I ^ X ^ X ^ X) - @ (Y ^ (CX ^ 3)) - @ (X ^ Y ^ Z ^ I ^ X ^ X ^ X) - ) @ Zero - c_op_perm = c_op.permute(perm) - self.assertNotEqual(c_op, c_op_perm) - c_op_id = c_op_perm.permute(perm) - self.assertEqual(c_op, c_op_id) - - def test_primitive_param_binding(self): - """Test that assign_parameters binds parameters of both the underlying primitive and coeffs.""" - theta = ParameterVector("theta", 2) - # only OperatorStateFn can have a primitive with a parameterized coefficient - op = StateFn(theta[0] * X) * theta[1] - bound = op.assign_parameters(dict(zip(theta, [0.2, 0.3]))) - self.assertEqual(bound.coeff, 0.3) - self.assertEqual(bound.primitive.coeff, 0.2) - - # #6003 - def test_flatten_statefn_composed_with_composed_op(self): - """Test that composing a StateFn with a ComposedOp constructs a single ComposedOp""" - circuit = QuantumCircuit(1) - vector = [1, 0] - ex = ~StateFn(I) @ (CircuitOp(circuit) @ StateFn(vector)) - self.assertEqual(len(ex), 3) - self.assertEqual(ex.eval(), 1) - - def test_tensorstate_to_matrix(self): - """Test tensored states to matrix works correctly with a global coefficient. - - Regression test of Qiskit/qiskit-terra#9398. - """ - state = 0.5 * (Plus ^ Zero) - expected = 1 / (2 * np.sqrt(2)) * np.array([1, 0, 1, 0]) - np.testing.assert_almost_equal(state.to_matrix(), expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_state_op_meas_evals.py b/test/python/opflow/test_state_op_meas_evals.py deleted file mode 100644 index e6d23390ea78..000000000000 --- a/test/python/opflow/test_state_op_meas_evals.py +++ /dev/null @@ -1,248 +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. - -# pylint: disable=no-name-in-module - - -"""Test Operator construction, including OpPrimitives and singletons.""" - -import unittest -from test.python.opflow import QiskitOpflowTestCase -from ddt import ddt, data -import numpy - -from qiskit.circuit import QuantumCircuit, Parameter -from qiskit.utils import QuantumInstance -from qiskit.opflow import StateFn, Zero, One, H, X, I, Z, Plus, Minus, CircuitSampler, ListOp -from qiskit.opflow.exceptions import OpflowError -from qiskit.quantum_info import Statevector - - -@ddt -class TestStateOpMeasEvals(QiskitOpflowTestCase): - """Tests of evals of Meas-Operator-StateFn combos.""" - - def test_statefn_overlaps(self): - """state functions overlaps test""" - wf = (4 * StateFn({"101010": 0.5, "111111": 0.3})) + ((3 + 0.1j) * (Zero ^ 6)) - wf_vec = StateFn(wf.to_matrix()) - self.assertAlmostEqual(wf.adjoint().eval(wf), 14.45) - self.assertAlmostEqual(wf_vec.adjoint().eval(wf_vec), 14.45) - self.assertAlmostEqual(wf_vec.adjoint().eval(wf), 14.45) - self.assertAlmostEqual(wf.adjoint().eval(wf_vec), 14.45) - - def test_wf_evals_x(self): - """wf evals x test""" - qbits = 4 - - wf = ((Zero ^ qbits) + (One ^ qbits)) * (1 / 2**0.5) - # Note: wf = Plus^qbits fails because TensoredOp can't handle it. - wf_vec = StateFn(wf.to_matrix()) - op = X ^ qbits - # op = I^6 - self.assertAlmostEqual(wf.adjoint().eval(op.eval(wf)), 1) - self.assertAlmostEqual(wf_vec.adjoint().eval(op.eval(wf)), 1) - self.assertAlmostEqual(wf.adjoint().eval(op.eval(wf_vec)), 1) - self.assertAlmostEqual(wf_vec.adjoint().eval(op.eval(wf_vec)), 1) - - # op = (H^X^Y)^2 - op = H ^ 6 - wf = ((Zero ^ 6) + (One ^ 6)) * (1 / 2**0.5) - wf_vec = StateFn(wf.to_matrix()) - # print(wf.adjoint().to_matrix() @ op.to_matrix() @ wf.to_matrix()) - self.assertAlmostEqual(wf.adjoint().eval(op.eval(wf)), 0.25) - self.assertAlmostEqual(wf_vec.adjoint().eval(op.eval(wf)), 0.25) - self.assertAlmostEqual(wf.adjoint().eval(op.eval(wf_vec)), 0.25) - self.assertAlmostEqual(wf_vec.adjoint().eval(op.eval(wf_vec)), 0.25) - - def test_coefficients_correctly_propagated(self): - """Test that the coefficients in SummedOp and states are correctly used.""" - try: - from qiskit.providers.aer import Aer - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - with self.subTest("zero coeff in SummedOp"): - op = 0 * (I + Z) - state = Plus - self.assertEqual((~StateFn(op) @ state).eval(), 0j) - - backend = Aer.get_backend("aer_simulator") - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance(backend, seed_simulator=97, seed_transpiler=97) - op = I - with self.subTest("zero coeff in summed StateFn and CircuitSampler"): - with self.assertWarns(DeprecationWarning): - state = 0 * (Plus + Minus) - sampler = CircuitSampler(q_instance).convert(~StateFn(op) @ state) - self.assertEqual(sampler.eval(), 0j) - - with self.subTest("coeff gets squared in CircuitSampler shot-based readout"): - with self.assertWarns(DeprecationWarning): - state = (Plus + Minus) / numpy.sqrt(2) - sampler = CircuitSampler(q_instance).convert(~StateFn(op) @ state) - self.assertAlmostEqual(sampler.eval(), 1 + 0j) - - def test_is_measurement_correctly_propagated(self): - """Test if is_measurement property of StateFn is propagated to converted StateFn.""" - try: - from qiskit.providers.aer import Aer - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - backend = Aer.get_backend("aer_simulator") - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance(backend) # no seeds needed since no values are compared - state = Plus - sampler = CircuitSampler(q_instance).convert(~state @ state) - self.assertTrue(sampler.oplist[0].is_measurement) - - def test_parameter_binding_on_listop(self): - """Test passing a ListOp with differing parameters works with the circuit sampler.""" - try: - from qiskit.providers.aer import Aer - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - x, y = Parameter("x"), Parameter("y") - - circuit1 = QuantumCircuit(1) - circuit1.p(0.2, 0) - circuit2 = QuantumCircuit(1) - circuit2.p(x, 0) - circuit3 = QuantumCircuit(1) - circuit3.p(y, 0) - - with self.assertWarns(DeprecationWarning): - bindings = {x: -0.4, y: 0.4} - listop = ListOp([StateFn(circuit) for circuit in [circuit1, circuit2, circuit3]]) - sampler = CircuitSampler(Aer.get_backend("aer_simulator")) - sampled = sampler.convert(listop, params=bindings) - - self.assertTrue(all(len(op.parameters) == 0 for op in sampled.oplist)) - - def test_list_op_eval_coeff_with_nonlinear_combofn(self): - """Test evaluating a ListOp with non-linear combo function works with coefficients.""" - - state = One - op = ListOp(5 * [I], coeff=2, combo_fn=numpy.prod) - expr1 = ~StateFn(op) @ state - - expr2 = ListOp(5 * [~state @ I @ state], coeff=2, combo_fn=numpy.prod) - - self.assertEqual(expr1.eval(), 2) # if the coeff is propagated too far the result is 4 - self.assertEqual(expr2.eval(), 2) - - def test_single_parameter_binds(self): - """Test passing parameter binds as a dictionary to the circuit sampler.""" - try: - from qiskit.providers.aer import Aer - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.ry(x, 0) - - with self.assertWarns(DeprecationWarning): - expr = ~StateFn(H) @ StateFn(circuit) - sampler = CircuitSampler(Aer.get_backend("aer_simulator_statevector")) - res = sampler.convert(expr, params={x: 0}).eval() - - self.assertIsInstance(res, complex) - - @data("all", "last") - def test_circuit_sampler_caching(self, caching): - """Test caching all operators works.""" - try: - from qiskit.providers.aer import Aer - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - - x = Parameter("x") - circuit = QuantumCircuit(1) - circuit.ry(x, 0) - - with self.assertWarns(DeprecationWarning): - - expr1 = ~StateFn(H) @ StateFn(circuit) - expr2 = ~StateFn(X) @ StateFn(circuit) - sampler = CircuitSampler(Aer.get_backend("aer_simulator_statevector"), caching=caching) - - res1 = sampler.convert(expr1, params={x: 0}).eval() - res2 = sampler.convert(expr2, params={x: 0}).eval() - res3 = sampler.convert(expr1, params={x: 0}).eval() - res4 = sampler.convert(expr2, params={x: 0}).eval() - - self.assertEqual(res1, res3) - self.assertEqual(res2, res4) - if caching == "last": - self.assertEqual(len(sampler._cached_ops.keys()), 1) - else: - self.assertEqual(len(sampler._cached_ops.keys()), 2) - - def test_adjoint_nonunitary_circuit_raises(self): - """Test adjoint on a non-unitary circuit raises a OpflowError instead of CircuitError.""" - circuit = QuantumCircuit(1) - circuit.reset(0) - - with self.assertRaises(OpflowError): - _ = StateFn(circuit).adjoint() - - def test_evaluating_nonunitary_circuit_state(self): - """Test evaluating a circuit works even if it contains non-unitary instruction (resets). - - TODO: allow this for (~StateFn(circuit) @ op @ StateFn(circuit)), but this requires - refactoring how the AerPauliExpectation works, since that currently relies on - composing with CircuitMeasurements - """ - circuit = QuantumCircuit(1) - circuit.initialize([0, 1], [0]) - - op = Z - res = (~StateFn(op) @ StateFn(circuit)).eval() - - self.assertAlmostEqual(-1 + 0j, res) - - def test_quantum_instance_with_backend_shots(self): - """Test sampling a circuit where the backend has shots attached.""" - try: - from qiskit.providers.aer import AerSimulator - except Exception as ex: # pylint: disable=broad-except - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - - backend = AerSimulator(shots=10) - - with self.assertWarns(DeprecationWarning): - sampler = CircuitSampler(backend) - res = sampler.convert(~Plus @ Plus).eval() - self.assertAlmostEqual(res, 1 + 0j, places=2) - - def test_adjoint_vector_to_circuit_fn(self): - """Test it is possible to adjoint a VectorStateFn that was converted to a CircuitStateFn.""" - - left = StateFn([0, 1]) - left_circuit = left.to_circuit_op().primitive - - right_circuit = QuantumCircuit(1) - right_circuit.x(0) - - circuit = left_circuit.inverse().compose(right_circuit) - - self.assertTrue(Statevector(circuit).equiv([1, 0])) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_tapered_pauli.py b/test/python/opflow/test_tapered_pauli.py deleted file mode 100644 index 967ca82373b8..000000000000 --- a/test/python/opflow/test_tapered_pauli.py +++ /dev/null @@ -1,57 +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. - -"""Test TaperedPauliSumOp""" - -import unittest -from test.python.opflow import QiskitOpflowTestCase - -from qiskit.circuit import Parameter -from qiskit.opflow import PauliSumOp, TaperedPauliSumOp, Z2Symmetries -from qiskit.quantum_info import Pauli, SparsePauliOp - - -class TestZ2Symmetries(QiskitOpflowTestCase): - """Z2Symmetries tests.""" - - def setUp(self): - super().setUp() - z2_symmetries = Z2Symmetries( - [Pauli("IIZI"), Pauli("ZIII")], [Pauli("IIXI"), Pauli("XIII")], [1, 3], [-1, 1] - ) - self.primitive = SparsePauliOp.from_list( - [ - ("II", (-1.052373245772859)), - ("ZI", (-0.39793742484318007)), - ("IZ", (0.39793742484318007)), - ("ZZ", (-0.01128010425623538)), - ("XX", (0.18093119978423142)), - ] - ) - self.tapered_qubit_op = TaperedPauliSumOp(self.primitive, z2_symmetries) - - def test_multiply_parameter(self): - """test for multiplication of parameter""" - param = Parameter("c") - expected = PauliSumOp(self.primitive, coeff=param) - self.assertEqual(param * self.tapered_qubit_op, expected) - - def test_assign_parameters(self): - """test assign_parameters""" - param = Parameter("c") - parameterized_op = param * self.tapered_qubit_op - expected = PauliSumOp(self.primitive, coeff=46) - self.assertEqual(parameterized_op.assign_parameters({param: 46}), expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/opflow/test_two_qubit_reduction.py b/test/python/opflow/test_two_qubit_reduction.py deleted file mode 100644 index 859d63446ebc..000000000000 --- a/test/python/opflow/test_two_qubit_reduction.py +++ /dev/null @@ -1,64 +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. - -"""Test TwoQubitReduction""" - -from test.python.opflow import QiskitOpflowTestCase - -from qiskit.opflow import PauliSumOp, TwoQubitReduction, TaperedPauliSumOp, Z2Symmetries -from qiskit.quantum_info import Pauli, SparsePauliOp - - -class TestTwoQubitReduction(QiskitOpflowTestCase): - """TwoQubitReduction tests.""" - - def test_convert(self): - """convert test""" - - 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) - self.assertIsInstance(tapered_qubit_op, TaperedPauliSumOp) - - primitive = SparsePauliOp.from_list( - [ - ("II", -1.052373245772859), - ("ZI", -0.39793742484318007), - ("IZ", 0.39793742484318007), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423142), - ] - ) - symmetries = [Pauli("IIZI"), Pauli("ZIII")] - sq_paulis = [Pauli("IIXI"), Pauli("XIII")] - sq_list = [1, 3] - tapering_values = [-1, 1] - z2_symmetries = Z2Symmetries(symmetries, sq_paulis, sq_list, tapering_values) - expected_op = TaperedPauliSumOp(primitive, z2_symmetries) - self.assertEqual(tapered_qubit_op, expected_op) diff --git a/test/python/opflow/test_z2_symmetries.py b/test/python/opflow/test_z2_symmetries.py deleted file mode 100644 index 37e8e0e16a27..000000000000 --- a/test/python/opflow/test_z2_symmetries.py +++ /dev/null @@ -1,112 +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. - -"""Test Z2Symmetries""" - -from test.python.opflow import QiskitOpflowTestCase - -from qiskit.opflow import PauliSumOp, TaperedPauliSumOp, Z2Symmetries -from qiskit.quantum_info import Pauli, SparsePauliOp - - -class TestZ2Symmetries(QiskitOpflowTestCase): - """Z2Symmetries tests.""" - - def test_find_Z2_symmetries(self): - """test for find_Z2_symmetries""" - - qubit_op = PauliSumOp.from_list( - [ - ("II", -1.0537076071291125), - ("IZ", 0.393983679438514), - ("ZI", -0.39398367943851387), - ("ZZ", -0.01123658523318205), - ("XX", 0.1812888082114961), - ] - ) - z2_symmetries = Z2Symmetries.find_Z2_symmetries(qubit_op) - self.assertEqual(z2_symmetries.symmetries, [Pauli("ZZ")]) - self.assertEqual(z2_symmetries.sq_paulis, [Pauli("IX")]) - self.assertEqual(z2_symmetries.sq_list, [0]) - self.assertEqual(z2_symmetries.tapering_values, None) - - tapered_op = z2_symmetries.taper(qubit_op)[1] - self.assertEqual(tapered_op.z2_symmetries.symmetries, [Pauli("ZZ")]) - self.assertEqual(tapered_op.z2_symmetries.sq_paulis, [Pauli("IX")]) - self.assertEqual(tapered_op.z2_symmetries.sq_list, [0]) - self.assertEqual(tapered_op.z2_symmetries.tapering_values, [-1]) - - z2_symmetries.tapering_values = [-1] - primitive = SparsePauliOp.from_list( - [ - ("I", -1.0424710218959303), - ("Z", -0.7879673588770277), - ("X", -0.18128880821149604), - ] - ) - expected_op = TaperedPauliSumOp(primitive, z2_symmetries) - self.assertEqual(tapered_op, expected_op) - - def test_taper_empty_operator(self): - """Test tapering of empty operator""" - z2_symmetries = Z2Symmetries( - symmetries=[Pauli("IIZI"), Pauli("IZIZ"), Pauli("ZIII")], - sq_paulis=[Pauli("IIXI"), Pauli("IIIX"), Pauli("XIII")], - sq_list=[1, 0, 3], - tapering_values=[1, -1, -1], - ) - empty_op = PauliSumOp.from_list([("IIII", 0.0)]) - tapered_op = z2_symmetries.taper(empty_op) - expected_op = PauliSumOp.from_list([("I", 0.0)]) - self.assertEqual(tapered_op, expected_op) - - def test_truncate_tapered_op(self): - """Test setting cutoff tolerances for the tapered operator works.""" - qubit_op = PauliSumOp.from_list( - [ - ("II", -1.0537076071291125), - ("IZ", 0.393983679438514), - ("ZI", -0.39398367943851387), - ("ZZ", -0.01123658523318205), - ("XX", 0.1812888082114961), - ] - ) - z2_symmetries = Z2Symmetries.find_Z2_symmetries(qubit_op) - z2_symmetries.tol = 0.2 # removes the X part of the tapered op which is < 0.2 - - tapered_op = z2_symmetries.taper(qubit_op)[1] - primitive = SparsePauliOp.from_list( - [ - ("I", -1.0424710218959303), - ("Z", -0.7879673588770277), - ] - ) - expected_op = TaperedPauliSumOp(primitive, z2_symmetries) - self.assertEqual(tapered_op, expected_op) - - def test_twostep_tapering(self): - """Test the two-step tapering""" - qubit_op = PauliSumOp.from_list( - [ - ("II", -1.0537076071291125), - ("IZ", 0.393983679438514), - ("ZI", -0.39398367943851387), - ("ZZ", -0.01123658523318205), - ("XX", 0.1812888082114961), - ] - ) - z2_symmetries = Z2Symmetries.find_Z2_symmetries(qubit_op) - tapered_op = z2_symmetries.taper(qubit_op) - - tapered_op_firststep = z2_symmetries.convert_clifford(qubit_op) - tapered_op_secondstep = z2_symmetries.taper_clifford(tapered_op_firststep) - self.assertEqual(tapered_op, tapered_op_secondstep) diff --git a/test/python/primitives/test_backend_estimator.py b/test/python/primitives/test_backend_estimator.py index 01f8c1fa251a..0df15f590728 100644 --- a/test/python/primitives/test_backend_estimator.py +++ b/test/python/primitives/test_backend_estimator.py @@ -13,9 +13,11 @@ """Tests for Estimator.""" import unittest -from test import combine -from test.python.transpiler._dummy_passes import DummyTP from unittest.mock import patch +from multiprocessing import Manager + +from test import combine +from test.python.transpiler._dummy_passes import DummyAP import numpy as np from ddt import ddt @@ -34,6 +36,18 @@ BACKENDS = [FakeNairobi(), FakeNairobiV2(), FakeBackendSimple()] +class CallbackPass(DummyAP): + """A dummy analysis pass that calls a callback when executed""" + + def __init__(self, message, callback): + super().__init__() + self.message = message + self.callback = callback + + def run(self, dag): + self.callback(self.message) + + @ddt class TestBackendEstimator(QiskitTestCase): """Test Estimator""" @@ -328,24 +342,40 @@ def test_bound_pass_manager(self): op = SparsePauliOp.from_list([("II", 1)]) with self.subTest("Test single circuit"): + messages = [] - dummy_pass = DummyTP() + def callback(msg): + messages.append(msg) - with patch.object(DummyTP, "run", wraps=dummy_pass.run) as mock_pass: - bound_pass = PassManager(dummy_pass) - estimator = BackendEstimator(backend=FakeNairobi(), bound_pass_manager=bound_pass) - _ = estimator.run(qc, op).result() - self.assertEqual(mock_pass.call_count, 1) + bound_counter = CallbackPass("bound_pass_manager", callback) + bound_pass = PassManager(bound_counter) + estimator = BackendEstimator(backend=FakeNairobi(), bound_pass_manager=bound_pass) + _ = estimator.run(qc, op).result() + expected = [ + "bound_pass_manager", + ] + self.assertEqual(messages, expected) with self.subTest("Test circuit batch"): - - dummy_pass = DummyTP() - - with patch.object(DummyTP, "run", wraps=dummy_pass.run) as mock_pass: - bound_pass = PassManager(dummy_pass) + with Manager() as manager: + # The multiprocessing manager is used to share data + # between different processes. Pass Managers parallelize + # execution for batches of circuits, so this is necessary + # to keep track of the callback calls for num_circuits > 1 + messages = manager.list() + + def callback(msg): # pylint: disable=function-redefined + messages.append(msg) + + bound_counter = CallbackPass("bound_pass_manager", callback) + bound_pass = PassManager(bound_counter) estimator = BackendEstimator(backend=FakeNairobi(), bound_pass_manager=bound_pass) _ = estimator.run([qc, qc], [op, op]).result() - self.assertEqual(mock_pass.call_count, 2) + expected = [ + "bound_pass_manager", + "bound_pass_manager", + ] + self.assertEqual(list(messages), expected) @combine(backend=BACKENDS) def test_layout(self, backend): diff --git a/test/python/primitives/test_backend_sampler.py b/test/python/primitives/test_backend_sampler.py index 971104af9870..b4b8d79e32a3 100644 --- a/test/python/primitives/test_backend_sampler.py +++ b/test/python/primitives/test_backend_sampler.py @@ -14,9 +14,10 @@ import math import unittest -from unittest.mock import patch +from multiprocessing import Manager + from test import combine -from test.python.transpiler._dummy_passes import DummyTP +from test.python.transpiler._dummy_passes import DummyAP import numpy as np from ddt import ddt @@ -34,6 +35,18 @@ BACKENDS = [FakeNairobi(), FakeNairobiV2()] +class CallbackPass(DummyAP): + """A dummy analysis pass that calls a callback when executed""" + + def __init__(self, message, callback): + super().__init__() + self.message = message + self.callback = callback + + def run(self, dag): + self.callback(self.message) + + @ddt class TestBackendSampler(QiskitTestCase): """Test BackendSampler""" @@ -386,24 +399,40 @@ def test_bound_pass_manager(self): """Test bound pass manager.""" with self.subTest("Test single circuit"): + messages = [] - dummy_pass = DummyTP() + def callback(msg): + messages.append(msg) - with patch.object(DummyTP, "run", wraps=dummy_pass.run) as mock_pass: - bound_pass = PassManager(dummy_pass) - sampler = BackendSampler(backend=FakeNairobi(), bound_pass_manager=bound_pass) - _ = sampler.run(self._circuit[0]).result() - self.assertEqual(mock_pass.call_count, 1) + bound_counter = CallbackPass("bound_pass_manager", callback) + bound_pass = PassManager(bound_counter) + sampler = BackendSampler(backend=FakeNairobi(), bound_pass_manager=bound_pass) + _ = sampler.run([self._circuit[0]]).result() + expected = [ + "bound_pass_manager", + ] + self.assertEqual(messages, expected) with self.subTest("Test circuit batch"): - - dummy_pass = DummyTP() - - with patch.object(DummyTP, "run", wraps=dummy_pass.run) as mock_pass: - bound_pass = PassManager(dummy_pass) + with Manager() as manager: + # The multiprocessing manager is used to share data + # between different processes. Pass Managers parallelize + # execution for batches of circuits, so this is necessary + # to keep track of the callback calls for num_circuits > 1 + messages = manager.list() + + def callback(msg): # pylint: disable=function-redefined + messages.append(msg) + + bound_counter = CallbackPass("bound_pass_manager", callback) + bound_pass = PassManager(bound_counter) sampler = BackendSampler(backend=FakeNairobi(), bound_pass_manager=bound_pass) _ = sampler.run([self._circuit[0], self._circuit[0]]).result() - self.assertEqual(mock_pass.call_count, 2) + expected = [ + "bound_pass_manager", + "bound_pass_manager", + ] + self.assertEqual(list(messages), expected) if __name__ == "__main__": diff --git a/test/python/primitives/test_estimator.py b/test/python/primitives/test_estimator.py index eaa1c7d922fd..7a190cbe0034 100644 --- a/test/python/primitives/test_estimator.py +++ b/test/python/primitives/test_estimator.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 @@ -20,7 +20,6 @@ from qiskit.circuit import Parameter, QuantumCircuit from qiskit.circuit.library import RealAmplitudes from qiskit.exceptions import QiskitError -from qiskit.opflow import PauliSumOp from qiskit.primitives import Estimator, EstimatorResult from qiskit.primitives.base import validation from qiskit.primitives.utils import _observable_key @@ -350,7 +349,6 @@ class TestObservableValidation(QiskitTestCase): ("IXYZ", (SparsePauliOp("IXYZ"),)), (Pauli("IXYZ"), (SparsePauliOp("IXYZ"),)), (SparsePauliOp("IXYZ"), (SparsePauliOp("IXYZ"),)), - (PauliSumOp(SparsePauliOp("IXYZ")), (SparsePauliOp("IXYZ"),)), ( ["IXYZ", "ZYXI"], (SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")), @@ -363,10 +361,6 @@ class TestObservableValidation(QiskitTestCase): [SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")], (SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")), ), - ( - [PauliSumOp(SparsePauliOp("IXYZ")), PauliSumOp(SparsePauliOp("ZYXI"))], - (SparsePauliOp("IXYZ"), SparsePauliOp("ZYXI")), - ), ) @unpack def test_validate_observables(self, obsevables, expected): diff --git a/test/python/providers/test_fake_backends.py b/test/python/providers/test_fake_backends.py index 1b7668a5b9f4..078a5110f818 100644 --- a/test/python/providers/test_fake_backends.py +++ b/test/python/providers/test_fake_backends.py @@ -497,7 +497,7 @@ def test_filter_faulty_qubits_backend_v2_converter(self): props_dict = backend.properties().to_dict() for i in range(62, 67): non_operational = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, @@ -518,7 +518,7 @@ def test_filter_faulty_qubits_backend_v2_converter_with_delay(self): props_dict = backend.properties().to_dict() for i in range(62, 67): non_operational = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, @@ -539,7 +539,7 @@ def test_filter_faulty_qubits_and_gates_backend_v2_converter(self): props_dict = backend.properties().to_dict() for i in range(62, 67): non_operational = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, @@ -556,7 +556,7 @@ def test_filter_faulty_qubits_and_gates_backend_v2_converter(self): (34, 24), } non_operational_gate = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, @@ -591,7 +591,7 @@ def test_filter_faulty_gates_v2_converter(self): (34, 24), } non_operational_gate = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, @@ -618,7 +618,7 @@ def test_filter_faulty_no_faults_v2_converter(self): def test_faulty_full_path_transpile_connected_cmap(self, opt_level): backend = FakeYorktown() non_operational_gate = { - "date": datetime.datetime.utcnow(), + "date": datetime.datetime.now(datetime.timezone.utc), "name": "operational", "unit": "", "value": 0, diff --git a/test/python/qasm2/test_circuit_methods.py b/test/python/qasm2/test_circuit_methods.py index 9027fb4d54b9..e6b35e582ccb 100644 --- a/test/python/qasm2/test_circuit_methods.py +++ b/test/python/qasm2/test_circuit_methods.py @@ -21,6 +21,7 @@ from qiskit.test import QiskitTestCase from qiskit.transpiler.passes import Unroller from qiskit.converters.circuit_to_dag import circuit_to_dag +from qiskit.qasm2 import dumps class LoadFromQasmTest(QiskitTestCase): @@ -254,18 +255,15 @@ def test_qasm_example_file(self): def test_qasm_qas_string_order(self): """Test that gates are returned in qasm in ascending order.""" - expected_qasm = ( - "\n".join( - [ - "OPENQASM 2.0;", - 'include "qelib1.inc";', - "qreg q[3];", - "h q[0];", - "h q[1];", - "h q[2];", - ] - ) - + "\n" + expected_qasm = "\n".join( + [ + "OPENQASM 2.0;", + 'include "qelib1.inc";', + "qreg q[3];", + "h q[0];", + "h q[1];", + "h q[2];", + ] ) qasm_string = """OPENQASM 2.0; include "qelib1.inc"; @@ -273,7 +271,7 @@ def test_qasm_qas_string_order(self): h q;""" q_circuit = QuantumCircuit.from_qasm_str(qasm_string) - self.assertEqual(q_circuit.qasm(), expected_qasm) + self.assertEqual(dumps(q_circuit), expected_qasm) def test_from_qasm_str_custom_gate1(self): """Test load custom gates (simple case)""" diff --git a/test/python/qasm2/test_legacy_importer.py b/test/python/qasm2/test_legacy_importer.py deleted file mode 100644 index dea4d53f8fef..000000000000 --- a/test/python/qasm2/test_legacy_importer.py +++ /dev/null @@ -1,508 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of 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 cases for the legacy OpenQASM 2 parser.""" - -# pylint: disable=missing-function-docstring - - -import os - -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit.circuit import Gate, Parameter -from qiskit.converters import ast_to_dag, dag_to_circuit -from qiskit.exceptions import QiskitError -from qiskit.qasm import Qasm -from qiskit.test import QiskitTestCase -from qiskit.transpiler.passes import Unroller -from qiskit.converters.circuit_to_dag import circuit_to_dag - - -def from_qasm_str(qasm_str): - return dag_to_circuit(ast_to_dag(Qasm(data=qasm_str).parse())) - - -def from_qasm_file(path): - return dag_to_circuit(ast_to_dag(Qasm(filename=path).parse())) - - -class LoadFromQasmTest(QiskitTestCase): - """Test circuit.from_qasm_* set of methods.""" - - def setUp(self): - super().setUp() - self.qasm_file_name = "entangled_registers.qasm" - self.qasm_dir = os.path.join( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm" - ) - self.qasm_file_path = os.path.join(self.qasm_dir, self.qasm_file_name) - - def test_qasm_file(self): - """ - Test qasm_file and get_circuit. - - If all is correct we should get the qasm file loaded in _qasm_file_path - """ - q_circuit = from_qasm_file(self.qasm_file_path) - qr_a = QuantumRegister(4, "a") - qr_b = QuantumRegister(4, "b") - cr_c = ClassicalRegister(4, "c") - cr_d = ClassicalRegister(4, "d") - q_circuit_2 = QuantumCircuit(qr_a, qr_b, cr_c, cr_d) - q_circuit_2.h(qr_a) - q_circuit_2.cx(qr_a, qr_b) - q_circuit_2.barrier(qr_a) - q_circuit_2.barrier(qr_b) - q_circuit_2.measure(qr_a, cr_c) - q_circuit_2.measure(qr_b, cr_d) - self.assertEqual(q_circuit, q_circuit_2) - - def test_loading_all_qelib1_gates(self): - """Test setting up a circuit with all gates defined in qiskit/qasm/libs/qelib1.inc.""" - from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, CU1Gate, CU3Gate, UGate - - all_gates_qasm = os.path.join(self.qasm_dir, "all_gates.qasm") - qasm_circuit = from_qasm_file(all_gates_qasm) - - ref_circuit = QuantumCircuit(3, 3) - - # abstract gates (legacy) - ref_circuit.append(UGate(0.2, 0.1, 0.6), [0]) - ref_circuit.cx(0, 1) - # the hardware primitives - ref_circuit.append(U3Gate(0.2, 0.1, 0.6), [0]) - ref_circuit.append(U2Gate(0.1, 0.6), [0]) - ref_circuit.append(U1Gate(0.6), [0]) - ref_circuit.id(0) - ref_circuit.cx(0, 1) - # the standard single qubit gates - ref_circuit.u(0.2, 0.1, 0.6, 0) - ref_circuit.p(0.6, 0) - ref_circuit.x(0) - ref_circuit.y(0) - ref_circuit.z(0) - ref_circuit.h(0) - ref_circuit.s(0) - ref_circuit.t(0) - ref_circuit.sdg(0) - ref_circuit.tdg(0) - ref_circuit.sx(0) - ref_circuit.sxdg(0) - # the standard rotations - ref_circuit.rx(0.1, 0) - ref_circuit.ry(0.1, 0) - ref_circuit.rz(0.1, 0) - # the barrier - ref_circuit.barrier() - # the standard user-defined gates - ref_circuit.swap(0, 1) - ref_circuit.cswap(0, 1, 2) - ref_circuit.cy(0, 1) - ref_circuit.cz(0, 1) - ref_circuit.ch(0, 1) - ref_circuit.csx(0, 1) - ref_circuit.append(CU1Gate(0.6), [0, 1]) - ref_circuit.append(CU3Gate(0.2, 0.1, 0.6), [0, 1]) - ref_circuit.cp(0.6, 0, 1) - ref_circuit.cu(0.2, 0.1, 0.6, 0, 0, 1) - ref_circuit.ccx(0, 1, 2) - ref_circuit.crx(0.6, 0, 1) - ref_circuit.cry(0.6, 0, 1) - ref_circuit.crz(0.6, 0, 1) - ref_circuit.rxx(0.2, 0, 1) - ref_circuit.rzz(0.2, 0, 1) - ref_circuit.measure([0, 1, 2], [0, 1, 2]) - - self.assertEqual(qasm_circuit, ref_circuit) - - def test_fail_qasm_file(self): - """ - Test fail_qasm_file. - - If all is correct we should get a QiskitError - """ - self.assertRaises(QiskitError, from_qasm_file, "") - - def test_qasm_text(self): - """ - Test qasm_text and get_circuit. - - If all is correct we should get the qasm file loaded from the string - """ - qasm_string = "// A simple 8 qubit example\nOPENQASM 2.0;\n" - qasm_string += 'include "qelib1.inc";\nqreg a[4];\n' - qasm_string += "qreg b[4];\ncreg c[4];\ncreg d[4];\nh a;\ncx a, b;\n" - qasm_string += "barrier a;\nbarrier b;\nmeasure a[0]->c[0];\n" - qasm_string += "measure a[1]->c[1];\nmeasure a[2]->c[2];\n" - qasm_string += "measure a[3]->c[3];\nmeasure b[0]->d[0];\n" - qasm_string += "measure b[1]->d[1];\nmeasure b[2]->d[2];\n" - qasm_string += "measure b[3]->d[3];" - q_circuit = from_qasm_str(qasm_string) - - qr_a = QuantumRegister(4, "a") - qr_b = QuantumRegister(4, "b") - cr_c = ClassicalRegister(4, "c") - cr_d = ClassicalRegister(4, "d") - ref = QuantumCircuit(qr_a, qr_b, cr_c, cr_d) - ref.h(qr_a[3]) - ref.cx(qr_a[3], qr_b[3]) - ref.h(qr_a[2]) - ref.cx(qr_a[2], qr_b[2]) - ref.h(qr_a[1]) - ref.cx(qr_a[1], qr_b[1]) - ref.h(qr_a[0]) - ref.cx(qr_a[0], qr_b[0]) - ref.barrier(qr_b) - ref.measure(qr_b, cr_d) - ref.barrier(qr_a) - ref.measure(qr_a, cr_c) - - self.assertEqual(len(q_circuit.cregs), 2) - self.assertEqual(len(q_circuit.qregs), 2) - self.assertEqual(q_circuit, ref) - - def test_qasm_text_conditional(self): - """ - Test qasm_text and get_circuit when conditionals are present. - """ - qasm_string = ( - "\n".join( - [ - "OPENQASM 2.0;", - 'include "qelib1.inc";', - "qreg q[1];", - "creg c0[4];", - "creg c1[4];", - "x q[0];", - "if(c1==4) x q[0];", - ] - ) - + "\n" - ) - q_circuit = from_qasm_str(qasm_string) - - qr = QuantumRegister(1, "q") - cr0 = ClassicalRegister(4, "c0") - cr1 = ClassicalRegister(4, "c1") - ref = QuantumCircuit(qr, cr0, cr1) - ref.x(qr[0]) - ref.x(qr[0]).c_if(cr1, 4) - - self.assertEqual(len(q_circuit.cregs), 2) - self.assertEqual(len(q_circuit.qregs), 1) - self.assertEqual(q_circuit, ref) - - def test_opaque_gate(self): - """ - Test parse an opaque gate - - See https://github.com/Qiskit/qiskit-terra/issues/1566. - """ - - qasm_string = ( - "\n".join( - [ - "OPENQASM 2.0;", - 'include "qelib1.inc";', - "opaque my_gate(theta,phi,lambda) a,b;", - "qreg q[3];", - "my_gate(1,2,3) q[1],q[2];", - ] - ) - + "\n" - ) - circuit = from_qasm_str(qasm_string) - - qr = QuantumRegister(3, "q") - expected = QuantumCircuit(qr) - expected.append(Gate(name="my_gate", num_qubits=2, params=[1, 2, 3]), [qr[1], qr[2]]) - - self.assertEqual(circuit, expected) - - def test_qasm_example_file(self): - """Loads qasm/example.qasm.""" - qasm_filename = os.path.join(self.qasm_dir, "example.qasm") - expected_circuit = from_qasm_str( - "\n".join( - [ - "OPENQASM 2.0;", - 'include "qelib1.inc";', - "qreg q[3];", - "qreg r[3];", - "creg c[3];", - "creg d[3];", - "h q[2];", - "cx q[2],r[2];", - "measure r[2] -> d[2];", - "h q[1];", - "cx q[1],r[1];", - "measure r[1] -> d[1];", - "h q[0];", - "cx q[0],r[0];", - "measure r[0] -> d[0];", - "barrier q[0],q[1],q[2];", - "measure q[2] -> c[2];", - "measure q[1] -> c[1];", - "measure q[0] -> c[0];", - ] - ) - + "\n" - ) - - q_circuit = from_qasm_file(qasm_filename) - - self.assertEqual(q_circuit, expected_circuit) - self.assertEqual(len(q_circuit.cregs), 2) - self.assertEqual(len(q_circuit.qregs), 2) - - def test_qasm_qas_string_order(self): - """Test that gates are returned in qasm in ascending order.""" - expected_qasm = ( - "\n".join( - [ - "OPENQASM 2.0;", - 'include "qelib1.inc";', - "qreg q[3];", - "h q[0];", - "h q[1];", - "h q[2];", - ] - ) - + "\n" - ) - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - qreg q[3]; - h q;""" - q_circuit = from_qasm_str(qasm_string) - - self.assertEqual(q_circuit.qasm(), expected_qasm) - - def test_from_qasm_str_custom_gate1(self): - """Test load custom gates (simple case)""" - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate rinv q {sdg q; h q; sdg q; h q; } - qreg qr[1]; - rinv qr[0];""" - circuit = from_qasm_str(qasm_string) - - rinv_q = QuantumRegister(1, name="q") - rinv_gate = QuantumCircuit(rinv_q, name="rinv") - rinv_gate.sdg(rinv_q) - rinv_gate.h(rinv_q) - rinv_gate.sdg(rinv_q) - rinv_gate.h(rinv_q) - rinv = rinv_gate.to_instruction() - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(rinv, [qr[0]]) - - self.assertEqualUnroll(["sdg", "h"], circuit, expected) - - def test_from_qasm_str_custom_gate2(self): - """Test load custom gates (no so simple case, different bit order) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate swap2 a,b { - cx a,b; - cx b,a; // different bit order - cx a,b; - } - qreg qr[3]; - swap2 qr[0], qr[1]; - swap2 qr[1], qr[2];""" - circuit = from_qasm_str(qasm_string) - - ab_args = QuantumRegister(2, name="ab") - swap_gate = QuantumCircuit(ab_args, name="swap2") - swap_gate.cx(ab_args[0], ab_args[1]) - swap_gate.cx(ab_args[1], ab_args[0]) - swap_gate.cx(ab_args[0], ab_args[1]) - swap = swap_gate.to_instruction() - - qr = QuantumRegister(3, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(swap, [qr[0], qr[1]]) - expected.append(swap, [qr[1], qr[2]]) - - self.assertEqualUnroll(["cx"], expected, circuit) - - def test_from_qasm_str_custom_gate3(self): - """Test load custom gates (no so simple case, different bit count) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate cswap2 a,b,c - { - cx c,b; // different bit count - ccx a,b,c; //previously defined gate - cx c,b; - } - qreg qr[3]; - cswap2 qr[1], qr[0], qr[2];""" - circuit = from_qasm_str(qasm_string) - - abc_args = QuantumRegister(3, name="abc") - cswap_gate = QuantumCircuit(abc_args, name="cswap2") - cswap_gate.cx(abc_args[2], abc_args[1]) - cswap_gate.ccx(abc_args[0], abc_args[1], abc_args[2]) - cswap_gate.cx(abc_args[2], abc_args[1]) - cswap = cswap_gate.to_instruction() - - qr = QuantumRegister(3, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(cswap, [qr[1], qr[0], qr[2]]) - - self.assertEqualUnroll(["cx", "h", "tdg", "t"], circuit, expected) - - def test_from_qasm_str_custom_gate4(self): - """Test load custom gates (parameterized) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate my_gate(phi,lambda) q {u(1.5707963267948966,phi,lambda) q;} - qreg qr[1]; - my_gate(pi, pi) qr[0];""" - circuit = from_qasm_str(qasm_string) - - my_gate_circuit = QuantumCircuit(1, name="my_gate") - phi = Parameter("phi") - lam = Parameter("lambda") - my_gate_circuit.u(1.5707963267948966, phi, lam, 0) - my_gate = my_gate_circuit.to_gate() - - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(my_gate, [qr[0]]) - expected = expected.assign_parameters({phi: 3.141592653589793, lam: 3.141592653589793}) - - self.assertEqualUnroll("u", circuit, expected) - - def test_from_qasm_str_custom_gate5(self): - """Test load custom gates (parameterized, with biop and constant) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate my_gate(phi,lambda) q {u(pi/2,phi,lambda) q;} // biop with pi - qreg qr[1]; - my_gate(pi, pi) qr[0];""" - circuit = from_qasm_str(qasm_string) - - my_gate_circuit = QuantumCircuit(1, name="my_gate") - phi = Parameter("phi") - lam = Parameter("lambda") - my_gate_circuit.u(1.5707963267948966, phi, lam, 0) - my_gate = my_gate_circuit.to_gate() - - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(my_gate, [qr[0]]) - expected = expected.assign_parameters({phi: 3.141592653589793, lam: 3.141592653589793}) - - self.assertEqualUnroll("u", circuit, expected) - - def test_from_qasm_str_custom_gate6(self): - """Test load custom gates (parameters used in expressions) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-591668924 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate my_gate(phi,lambda) q - {rx(phi+pi) q; ry(lambda/2) q;} // parameters used in expressions - qreg qr[1]; - my_gate(pi, pi) qr[0];""" - circuit = from_qasm_str(qasm_string) - - my_gate_circuit = QuantumCircuit(1, name="my_gate") - phi = Parameter("phi") - lam = Parameter("lambda") - my_gate_circuit.rx(phi + 3.141592653589793, 0) - my_gate_circuit.ry(lam / 2, 0) - my_gate = my_gate_circuit.to_gate() - - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.append(my_gate, [qr[0]]) - expected = expected.assign_parameters({phi: 3.141592653589793, lam: 3.141592653589793}) - - self.assertEqualUnroll(["rx", "ry"], circuit, expected) - - def test_from_qasm_str_custom_gate7(self): - """Test load custom gates (build in functions) - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-592208951 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate my_gate(phi,lambda) q - {u(asin(cos(phi)/2), phi+pi, lambda/2) q;} // build func - qreg qr[1]; - my_gate(pi, pi) qr[0];""" - circuit = from_qasm_str(qasm_string) - - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.u(-0.5235987755982988, 6.283185307179586, 1.5707963267948966, qr[0]) - self.assertEqualUnroll("u", circuit, expected) - - def test_from_qasm_str_nested_custom_gate(self): - """Test chain of custom gates - See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-592261942 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - gate my_other_gate(phi,lambda) q - {u(asin(cos(phi)/2), phi+pi, lambda/2) q;} - gate my_gate(phi) r - {my_other_gate(phi, phi+pi) r;} - qreg qr[1]; - my_gate(pi) qr[0];""" - circuit = from_qasm_str(qasm_string) - - qr = QuantumRegister(1, name="qr") - expected = QuantumCircuit(qr, name="circuit") - expected.u(-0.5235987755982988, 6.283185307179586, 3.141592653589793, qr[0]) - self.assertEqualUnroll("u", circuit, expected) - - def test_from_qasm_str_delay(self): - """Test delay instruction/opaque-gate - See: https://github.com/Qiskit/qiskit-terra/issues/6510 - """ - qasm_string = """OPENQASM 2.0; - include "qelib1.inc"; - - opaque delay(time) q; - - qreg q[1]; - delay(172) q[0];""" - circuit = from_qasm_str(qasm_string) - - qr = QuantumRegister(1, name="q") - expected = QuantumCircuit(qr, name="circuit") - expected.delay(172, qr[0]) - self.assertEqualUnroll("u", circuit, expected) - - def assertEqualUnroll(self, basis, circuit, expected): - """Compares the dags after unrolling to basis""" - circuit_dag = circuit_to_dag(circuit) - expected_dag = circuit_to_dag(expected) - with self.assertWarns(DeprecationWarning): - circuit_result = Unroller(basis).run(circuit_dag) - expected_result = Unroller(basis).run(expected_dag) - - self.assertEqual(circuit_result, expected_result) diff --git a/test/python/qpy/test_block_load_from_qpy.py b/test/python/qpy/test_block_load_from_qpy.py index 8137ef21a6bb..321e018d5a64 100644 --- a/test/python/qpy/test_block_load_from_qpy.py +++ b/test/python/qpy/test_block_load_from_qpy.py @@ -37,7 +37,6 @@ ) from qiskit.pulse.instructions import Play, TimeBlockade from qiskit.circuit import Parameter, QuantumCircuit, Gate -from qiskit.exceptions import MissingOptionalLibraryError from qiskit.test import QiskitTestCase from qiskit.qpy import dump, load from qiskit.utils import optionals as _optional @@ -460,22 +459,3 @@ def test_symengine_full_path(self): qpy_file.seek(0) new_sched = load(qpy_file)[0] self.assertEqual(self.test_sched, new_sched) - - @unittest.skipIf(not _optional.HAS_SYMENGINE, "Install symengine to run this test.") - def test_dump_no_symengine(self): - """Test dump fails if symengine is not installed and use_symengine==True.""" - qpy_file = io.BytesIO() - with _optional.HAS_SYMENGINE.disable_locally(): - with self.assertRaises(MissingOptionalLibraryError): - dump(self.test_sched, qpy_file, use_symengine=True) - - @unittest.skipIf(not _optional.HAS_SYMENGINE, "Install symengine to run this test.") - def test_load_no_symengine(self): - """Test that load fails if symengine is not installed and the - file was created with use_symengine==True.""" - qpy_file = io.BytesIO() - dump(self.test_sched, qpy_file, use_symengine=True) - qpy_file.seek(0) - with _optional.HAS_SYMENGINE.disable_locally(): - with self.assertRaises(MissingOptionalLibraryError): - _ = load(qpy_file)[0] diff --git a/test/python/quantum_info/states/test_statevector.py b/test/python/quantum_info/states/test_statevector.py index a7228d6bf5d2..c1c6beed27cc 100644 --- a/test/python/quantum_info/states/test_statevector.py +++ b/test/python/quantum_info/states/test_statevector.py @@ -33,7 +33,7 @@ from qiskit.quantum_info.operators.operator import Operator from qiskit.quantum_info.operators.symplectic import Pauli, SparsePauliOp from qiskit.quantum_info.operators.predicates import matrix_equal -from qiskit.visualization.state_visualization import numbers_to_latex_terms, state_to_latex +from qiskit.visualization.state_visualization import state_to_latex logger = logging.getLogger(__name__) @@ -1320,28 +1320,6 @@ def test_state_to_latex_with_decimals_round(self): "0.354 |000\\rangle+0.354 |001\\rangle- 0.354 i |110\\rangle+0.354 i |111\\rangle", ) - def test_number_to_latex_terms(self): - """Test conversions of complex numbers to latex terms""" - - cases = [ - ([1 - 8e-17, 0], ["", None]), - ([0, -1], [None, "-"]), - ([0, 1], [None, ""]), - ([0, 1j], [None, "i"]), - ([-1, 1], ["-", "+"]), - ([0, 1j], [None, "i"]), - ([-1, 1j], ["-", "+i"]), - ([1e-16 + 1j], ["i"]), - ([-1 + 1e-16 * 1j], ["-"]), - ([-1, -1 - 1j], ["-", "+(-1 - i)"]), - ([np.sqrt(2) / 2, np.sqrt(2) / 2], ["\\frac{\\sqrt{2}}{2}", "+\\frac{\\sqrt{2}}{2}"]), - ([1 + np.sqrt(2)], ["(1 + \\sqrt{2})"]), - ] - with self.assertWarns(DeprecationWarning): - for numbers, latex_terms in cases: - terms = numbers_to_latex_terms(numbers, 15) - self.assertListEqual(terms, latex_terms) - def test_statevector_draw_latex_regression(self): """Test numerical rounding errors are not printed""" sv = Statevector(np.array([1 - 8e-17, 8.32667268e-17j])) diff --git a/test/python/quantum_info/test_synthesis.py b/test/python/quantum_info/test_synthesis.py index 2d134967e34f..c04337d3ffd3 100644 --- a/test/python/quantum_info/test_synthesis.py +++ b/test/python/quantum_info/test_synthesis.py @@ -30,6 +30,7 @@ from qiskit.circuit.library import ( HGate, IGate, + RGate, SdgGate, SGate, U3Gate, @@ -446,6 +447,8 @@ def test_special_RR(self): self.check_oneq_special_cases(U3Gate(-np.pi, 0.2, 0.0).to_matrix(), "RR", {"r": 1}) self.check_oneq_special_cases(U3Gate(np.pi, 0.0, 0.2).to_matrix(), "RR", {"r": 1}) self.check_oneq_special_cases(U3Gate(0.1, 0.2, 0.3).to_matrix(), "RR", {"r": 2}) + self.check_oneq_special_cases(U3Gate(0.1, 0.2, -0.2).to_matrix(), "RR", {"r": 1}) + self.check_oneq_special_cases(RGate(0.1, 0.2).to_matrix(), "RR", {"r": 1}) def test_special_U1X(self): """Special cases of U1X""" diff --git a/test/python/result/test_sampled_expval.py b/test/python/result/test_sampled_expval.py index f7cb06c5b86b..cb68099b310e 100644 --- a/test/python/result/test_sampled_expval.py +++ b/test/python/result/test_sampled_expval.py @@ -15,7 +15,6 @@ import unittest from qiskit.result import Counts, QuasiDistribution, ProbDistribution, sampled_expectation_value from qiskit.quantum_info import Pauli, SparsePauliOp -from qiskit.opflow import PauliOp, PauliSumOp from qiskit.test import QiskitTestCase @@ -82,17 +81,9 @@ def test_same(self): exp2 = sampled_expectation_value(counts, Pauli(oper)) self.assertAlmostEqual(exp2, ans) - with self.assertWarns(DeprecationWarning): - exp3 = sampled_expectation_value(counts, PauliOp(Pauli(oper))) - self.assertAlmostEqual(exp3, ans) - spo = SparsePauliOp([oper], coeffs=[1]) - with self.assertWarns(DeprecationWarning): - exp4 = sampled_expectation_value(counts, PauliSumOp(spo, coeff=2)) - self.assertAlmostEqual(exp4, 2 * ans) - - exp5 = sampled_expectation_value(counts, SparsePauliOp.from_list([[oper, 1]])) - self.assertAlmostEqual(exp5, ans) + exp3 = sampled_expectation_value(counts, spo) + self.assertAlmostEqual(exp3, ans) def test_asym_ops(self): """Test that asymmetric exp values work""" diff --git a/test/python/test_qasm_parser.py b/test/python/test_qasm_parser.py deleted file mode 100644 index 49cdc2f12673..000000000000 --- a/test/python/test_qasm_parser.py +++ /dev/null @@ -1,126 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of 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 for the QASM parser""" - -import os -import unittest -import ply -import ddt - -from qiskit.qasm import Qasm, QasmError -from qiskit.qasm.node.node import Node -from qiskit.test import QiskitTestCase - - -def parse(file_path): - """ - Simple helper - - file_path: Path to the OpenQASM file - - prec: Precision for the returned string - """ - qasm = Qasm(file_path) - return qasm.parse().qasm() - - -@ddt.ddt -class TestParser(QiskitTestCase): - """QasmParser""" - - def setUp(self): - super().setUp() - self.qasm_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "qasm") - self.qasm_file_path = os.path.join(self.qasm_dir, "example.qasm") - self.qasm_file_path_fail = os.path.join(self.qasm_dir, "example_fail.qasm") - self.qasm_file_path_if = os.path.join(self.qasm_dir, "example_if.qasm") - self.qasm_file_path_version_fail = os.path.join(self.qasm_dir, "example_version_fail.qasm") - self.qasm_file_path_version_2 = os.path.join(self.qasm_dir, "example_version_2.qasm") - self.qasm_file_path_minor_ver_fail = os.path.join( - self.qasm_dir, "example_minor_version_fail.qasm" - ) - - def test_parser(self): - """should return a correct response for a valid circuit.""" - - res = parse(self.qasm_file_path) - self.log.info(res) - # TODO: For now only some basic checks. - starts_expected = "OPENQASM 2.0;\ngate " - ends_expected = "\n".join( - [ - "}", - "qreg q[3];", - "qreg r[3];", - "h q;", - "cx q,r;", - "creg c[3];", - "creg d[3];", - "barrier q;", - "measure q -> c;", - "measure r -> d;", - "", - ] - ) - - self.assertEqual(res[: len(starts_expected)], starts_expected) - self.assertEqual(res[-len(ends_expected) :], ends_expected) - - def test_parser_fail(self): - """should fail a for a not valid circuit.""" - - self.assertRaisesRegex( - QasmError, "Perhaps there is a missing", parse, file_path=self.qasm_file_path_fail - ) - - @ddt.data("example_version_fail.qasm", "example_minor_version_fail.qasm") - def test_parser_version_fail(self, filename): - """Ensure versions other than 2.0 or 2 fail.""" - filename = os.path.join(self.qasm_dir, filename) - with self.assertRaisesRegex( - QasmError, r"Invalid version: '.+'\. This module supports OpenQASM 2\.0 only\." - ): - parse(filename) - - def test_parser_version_2(self): - """should succeed for OPENQASM version 2. Parser should automatically add minor verison.""" - res = parse(self.qasm_file_path_version_2) - version_start = "OPENQASM 2.0;" - self.assertEqual(res[: len(version_start)], version_start) - - def test_all_valid_nodes(self): - """Test that the tree contains only Node subclasses.""" - - def inspect(node): - """Inspect node children.""" - for child in node.children: - self.assertTrue(isinstance(child, Node)) - inspect(child) - - # Test the canonical example file. - qasm = Qasm(self.qasm_file_path) - res = qasm.parse() - inspect(res) - - # Test a file containing if instructions. - qasm_if = Qasm(self.qasm_file_path_if) - res_if = qasm_if.parse() - inspect(res_if) - - def test_generate_tokens(self): - """Test whether we get only valid tokens.""" - qasm = Qasm(self.qasm_file_path) - for token in qasm.generate_tokens(): - self.assertTrue(isinstance(token, ply.lex.LexToken)) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/test_util.py b/test/python/test_util.py index b574ff8390b0..d60aad284c18 100644 --- a/test/python/test_util.py +++ b/test/python/test_util.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (C) 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 @@ -13,11 +13,9 @@ """Tests for qiskit/utils""" from unittest import mock -import numpy as np from qiskit.utils.multiprocessing import local_hardware_info from qiskit.test import QiskitTestCase -from qiskit.utils.arithmetic import triu_to_dense class TestUtil(QiskitTestCase): @@ -31,14 +29,3 @@ def test_local_hardware_none_cpu_count(self, cpu_count_mock, vmem_mock, platform del cpu_count_mock, vmem_mock, platform_mock # unused result = local_hardware_info() self.assertEqual(1, result["cpus"]) - - def test_triu_to_dense(self): - """Test conversion of upper triangular matrix to dense matrix.""" - np.random.seed(50) - n = np.random.randint(5, 15) - m = np.random.randint(-100, 100, size=(n, n)) - symm = (m + m.T) / 2 - - triu = [[symm[i, j] for i in range(j, n)] for j in range(n)] - - self.assertTrue(np.array_equal(symm, triu_to_dense(triu))) diff --git a/test/python/test_version.py b/test/python/test_version.py deleted file mode 100644 index 3d260d6a3a8e..000000000000 --- a/test/python/test_version.py +++ /dev/null @@ -1,26 +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 qiskit/version.py""" - -from qiskit import __qiskit_version__ -from qiskit import __version__ -from qiskit.test import QiskitTestCase - - -class TestVersion(QiskitTestCase): - """Tests for qiskit/version.py""" - - def test_qiskit_version(self): - """Test qiskit-version sets the correct version for terra.""" - with self.assertWarnsRegex(DeprecationWarning, "__qiskit_version__"): - self.assertEqual(__version__, __qiskit_version__["qiskit"]) diff --git a/test/python/transpiler/test_filter_op_nodes.py b/test/python/transpiler/test_filter_op_nodes.py new file mode 100644 index 000000000000..0084dd35d4c0 --- /dev/null +++ b/test/python/transpiler/test_filter_op_nodes.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. + +"""FilterOpNodes pass testing""" + + +from qiskit import QuantumCircuit +from qiskit.transpiler.passes import FilterOpNodes +from qiskit.test import QiskitTestCase + + +class TestFilterOpNodes(QiskitTestCase): + """Tests for FilterOpNodes transformation pass.""" + + def test_empty_circuit(self): + """Empty DAG has does nothing.""" + circuit = QuantumCircuit() + self.assertEqual(FilterOpNodes(lambda x: False)(circuit), circuit) + + def test_remove_x_gate(self): + """Test filter removes matching gates.""" + circuit = QuantumCircuit(2) + circuit.x(0) + circuit.x(1) + circuit.cx(0, 1) + circuit.cx(1, 0) + circuit.cx(0, 1) + circuit.measure_all() + + filter_pass = FilterOpNodes(lambda node: node.op.name != "x") + + expected = QuantumCircuit(2) + expected.cx(0, 1) + expected.cx(1, 0) + expected.cx(0, 1) + expected.measure_all() + + self.assertEqual(filter_pass(circuit), expected) + + def test_filter_exception(self): + """Test a filter function exception passes through.""" + circuit = QuantumCircuit(2) + circuit.x(0) + circuit.x(1) + circuit.cx(0, 1) + circuit.cx(1, 0) + circuit.cx(0, 1) + circuit.measure_all() + + def filter_fn(node): + raise TypeError("Failure") + + filter_pass = FilterOpNodes(filter_fn) + with self.assertRaises(TypeError): + filter_pass(circuit) + + def test_no_matches(self): + """Test the pass does nothing if there are no filter matches.""" + circuit = QuantumCircuit(2) + circuit.x(0) + circuit.x(1) + circuit.cx(0, 1) + circuit.cx(1, 0) + circuit.cx(0, 1) + circuit.measure_all() + + filter_pass = FilterOpNodes(lambda node: node.op.name != "cz") + + self.assertEqual(filter_pass(circuit), circuit) diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index aaa84257d490..100e7882c782 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -129,6 +129,29 @@ def test_trivial_case(self): self.assertEqual(new_qc, qc) + def test_2q_barriers_not_routed(self): + """Test that a 2q barrier is not routed.""" + coupling = CouplingMap.from_line(5) + + qr = QuantumRegister(5, "q") + qc = QuantumCircuit(qr) + qc.barrier(0, 1) + qc.barrier(0, 2) + qc.barrier(0, 3) + qc.barrier(2, 3) + qc.h(0) + qc.barrier(1, 2) + qc.barrier(1, 0) + qc.barrier(1, 3) + qc.barrier(1, 4) + qc.barrier(4, 3) + qc.barrier(0, 4) + + passmanager = PassManager(SabreSwap(coupling, "basic")) + new_qc = passmanager.run(qc) + + self.assertEqual(new_qc, qc) + def test_trivial_with_target(self): """Test that an already mapped circuit is unchanged with target.""" coupling = CouplingMap.from_ring(5) diff --git a/test/python/transpiler/test_unitary_synthesis.py b/test/python/transpiler/test_unitary_synthesis.py index d93b03934173..93302ca78fd1 100644 --- a/test/python/transpiler/test_unitary_synthesis.py +++ b/test/python/transpiler/test_unitary_synthesis.py @@ -344,25 +344,36 @@ def test_two_qubit_synthesis_not_pulse_optimal(self): backend = FakeVigo() conf = backend.configuration() qr = QuantumRegister(2) - coupling_map = CouplingMap([[0, 1], [1, 2], [1, 3], [3, 4]]) - triv_layout_pass = TrivialLayout(coupling_map) qc = QuantumCircuit(qr) qc.unitary(random_unitary(4, seed=12), [0, 1]) - unisynth_pass = UnitarySynthesis( - basis_gates=conf.basis_gates, - coupling_map=coupling_map, - backend_props=backend.properties(), - pulse_optimize=False, - natural_direction=True, + coupling_map = CouplingMap([[0, 1]]) + pm_nonoptimal = PassManager( + [ + TrivialLayout(coupling_map), + UnitarySynthesis( + basis_gates=conf.basis_gates, + coupling_map=coupling_map, + backend_props=backend.properties(), + pulse_optimize=False, + natural_direction=True, + ), + ] ) - pm = PassManager([triv_layout_pass, unisynth_pass]) - qc_out = pm.run(qc) - if isinstance(qc_out, QuantumCircuit): - num_ops = qc_out.count_ops() - else: - num_ops = qc_out[0].count_ops() - self.assertIn("sx", num_ops) - self.assertGreaterEqual(num_ops["sx"], 16) + pm_optimal = PassManager( + [ + TrivialLayout(coupling_map), + UnitarySynthesis( + basis_gates=conf.basis_gates, + coupling_map=coupling_map, + backend_props=backend.properties(), + pulse_optimize=True, + natural_direction=True, + ), + ] + ) + qc_nonoptimal = pm_nonoptimal.run(qc) + qc_optimal = pm_optimal.run(qc) + self.assertGreater(qc_nonoptimal.count_ops()["sx"], qc_optimal.count_ops()["sx"]) def test_two_qubit_pulse_optimal_true_raises(self): """Verify raises if pulse optimal==True but cx is not in the backend basis.""" diff --git a/test/python/utils/mitigation/__init__.py b/test/python/utils/mitigation/__init__.py deleted file mode 100644 index ce3f4ee22ee0..000000000000 --- a/test/python/utils/mitigation/__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 mitigation utils unit tests.""" diff --git a/test/python/utils/mitigation/test_meas.py b/test/python/utils/mitigation/test_meas.py deleted file mode 100644 index a5105b5ba4d5..000000000000 --- a/test/python/utils/mitigation/test_meas.py +++ /dev/null @@ -1,709 +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. - -# pylint: disable=invalid-name - -""" -Test of measurement calibration: -1) Preparation of the basis states, generating the calibration circuits -(without noise), computing the calibration matrices, -and validating that they equal -to the identity matrices -2) Generating ideal (equally distributed) results, computing -the calibration output (without noise), -and validating that it is equally distributed -3) Testing the the measurement calibration on a circuit -(without noise), verifying that it is close to the -expected (equally distributed) result -4) Testing the fitters on pre-generated data with noise -""" - -import unittest -import numpy as np - -import qiskit -from qiskit.test import QiskitTestCase -from qiskit.result.result import Result -from qiskit.utils.mitigation import ( - CompleteMeasFitter, - TensoredMeasFitter, - complete_meas_cal, - tensored_meas_cal, -) -from qiskit.utils.mitigation._filters import MeasurementFilter -from qiskit.utils.mitigation.circuits import count_keys - -from qiskit.utils import optionals - -if optionals.HAS_AER: - # pylint: disable=no-name-in-module - from qiskit_aer import AerSimulator - from qiskit_aer.noise import NoiseModel - from qiskit_aer.noise.errors.standard_errors import pauli_error - -# fixed seed for tests - for both simulator and transpiler -SEED = 42 - - -def convert_ndarray_to_list_in_data(data: np.ndarray): - """ - converts ndarray format into list format (keeps all the dicts in the array) - also convert inner ndarrays into lists (recursively) - Args: - data: ndarray containing dicts or ndarrays in it - - Returns: - list: same array, converted to list format (in order to save it as json) - - """ - new_data = [] - for item in data: - if isinstance(item, np.ndarray): - new_item = convert_ndarray_to_list_in_data(item) - elif isinstance(item, dict): - new_item = {} - for key, value in item.items(): - new_item[key] = value.tolist() - else: - new_item = item - new_data.append(new_item) - - return new_data - - -def meas_calib_circ_creation(): - """ - create measurement calibration circuits and a GHZ state circuit for the tests - - Returns: - QuantumCircuit: the measurement calibrations circuits - list[str]: the mitigation pattern - QuantumCircuit: ghz circuit with 5 qubits (3 are used) - - """ - qubit_list = [1, 2, 3] - total_number_of_qubit = 5 - meas_calibs, state_labels = complete_meas_cal(qubit_list=qubit_list, qr=total_number_of_qubit) - - # Choose 3 qubits - qubit_1 = qubit_list[0] - qubit_2 = qubit_list[1] - qubit_3 = qubit_list[2] - ghz = qiskit.QuantumCircuit(total_number_of_qubit, len(qubit_list)) - ghz.h(qubit_1) - ghz.cx(qubit_1, qubit_2) - ghz.cx(qubit_1, qubit_3) - for i in qubit_list: - ghz.measure(i, i - 1) - return meas_calibs, state_labels, ghz - - -def tensored_calib_circ_creation(): - """ - create tensored measurement calibration circuits and a GHZ state circuit for the tests - - Returns: - QuantumCircuit: the tensored measurement calibration circuit - list[list[int]]: the mitigation pattern - QuantumCircuit: ghz circuit with 5 qubits (3 are used) - - """ - mit_pattern = [[2], [4, 1]] - meas_layout = [2, 4, 1] - qr = qiskit.QuantumRegister(5) - # Generate the calibration circuits - meas_calibs, mit_pattern = tensored_meas_cal(mit_pattern, qr=qr) - - cr = qiskit.ClassicalRegister(3) - ghz_circ = qiskit.QuantumCircuit(qr, cr) - ghz_circ.h(mit_pattern[0][0]) - ghz_circ.cx(mit_pattern[0][0], mit_pattern[1][0]) - ghz_circ.cx(mit_pattern[0][0], mit_pattern[1][1]) - ghz_circ.measure(mit_pattern[0][0], cr[0]) - ghz_circ.measure(mit_pattern[1][0], cr[1]) - ghz_circ.measure(mit_pattern[1][1], cr[2]) - return meas_calibs, mit_pattern, ghz_circ, meas_layout - - -def meas_calibration_circ_execution(shots: int, seed: int): - """ - create measurement calibration circuits and simulate them with noise - Args: - shots (int): number of shots per simulation - seed (int): the seed to use in the simulations - - Returns: - list: list of Results of the measurement calibration simulations - list: list of all the possible states with this amount of qubits - dict: dictionary of results counts of GHZ circuit simulation with measurement errors - """ - # define the circuits - meas_calibs, state_labels, ghz = meas_calib_circ_creation() - - # define noise - prob = 0.2 - error_meas = pauli_error([("X", prob), ("I", 1 - prob)]) - noise_model = NoiseModel() - noise_model.add_all_qubit_quantum_error(error_meas, "measure") - - # run the circuits multiple times - backend = AerSimulator() - cal_results = qiskit.execute( - meas_calibs, backend=backend, shots=shots, noise_model=noise_model, seed_simulator=seed - ).result() - - ghz_results = ( - qiskit.execute( - ghz, backend=backend, shots=shots, noise_model=noise_model, seed_simulator=seed - ) - .result() - .get_counts() - ) - - return cal_results, state_labels, ghz_results - - -def tensored_calib_circ_execution(shots: int, seed: int): - """ - create tensored measurement calibration circuits and simulate them with noise - Args: - shots (int): number of shots per simulation - seed (int): the seed to use in the simulations - - Returns: - list: list of Results of the measurement calibration simulations - list: the mitigation pattern - dict: dictionary of results counts of GHZ circuit simulation with measurement errors - """ - # define the circuits - meas_calibs, mit_pattern, ghz_circ, meas_layout = tensored_calib_circ_creation() - # define noise - prob = 0.2 - error_meas = pauli_error([("X", prob), ("I", 1 - prob)]) - noise_model = NoiseModel() - noise_model.add_all_qubit_quantum_error(error_meas, "measure") - - # run the circuits multiple times - backend = AerSimulator() - cal_results = qiskit.execute( - meas_calibs, backend=backend, shots=shots, noise_model=noise_model, seed_simulator=seed - ).result() - - ghz_results = qiskit.execute( - ghz_circ, backend=backend, shots=shots, noise_model=noise_model, seed_simulator=seed - ).result() - - return cal_results, mit_pattern, ghz_results, meas_layout - - -@unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") -class TestMeasCal(QiskitTestCase): - """The test class.""" - - def setUp(self): - super().setUp() - self.nq_list = [1, 2, 3, 4, 5] # Test up to 5 qubits - self.shots = 1024 # Number of shots (should be a power of 2) - - @staticmethod - def choose_calibration(nq, pattern_type): - """ - Generate a calibration circuit - - Args: - nq (int): number of qubits - pattern_type (int): a pattern in range(1, 2**nq) - - Returns: - qubits: a list of qubits according to the given pattern - weight: the weight of the pattern_type, - equals to the number of qubits - - Additional Information: - qr[i] exists if and only if the i-th bit in the binary - expression of - pattern_type equals 1 - """ - qubits = [] - weight = 0 - for i in range(nq): - pattern_bit = pattern_type & 1 - pattern_type = pattern_type >> 1 - if pattern_bit == 1: - qubits.append(i) - weight += 1 - return qubits, weight - - def generate_ideal_results(self, state_labels, weight): - """ - Generate ideal equally distributed results - - Args: - state_labels (list): a list of calibration state labels - weight (int): the number of qubits - - Returns: - results_dict: a dictionary of equally distributed results - results_list: a list of equally distributed results - - Additional Information: - for each state in state_labels: - result_dict[state] = #shots/len(state_labels) - """ - results_dict = {} - results_list = [0] * (2**weight) - state_num = len(state_labels) - for state in state_labels: - shots_per_state = self.shots / state_num - results_dict[state] = shots_per_state - # converting state (binary) to an integer - place = int(state, 2) - results_list[place] = shots_per_state - return results_dict, results_list - - def test_ideal_meas_cal(self): - """Test ideal execution, without noise.""" - for nq in self.nq_list: - for pattern_type in range(1, 2**nq): - - # Generate the quantum register according to the pattern - qubits, weight = self.choose_calibration(nq, pattern_type) - - with self.assertWarns(DeprecationWarning): - # Generate the calibration circuits - meas_calibs, state_labels = complete_meas_cal( - qubit_list=qubits, circlabel="test" - ) - - # Perform an ideal execution on the generated circuits - backend = AerSimulator() - job = qiskit.execute(meas_calibs, backend=backend, shots=self.shots) - cal_results = job.result() - - with self.assertWarns(DeprecationWarning): - # Make a calibration matrix - meas_cal = CompleteMeasFitter(cal_results, state_labels, circlabel="test") - - # Assert that the calibration matrix is equal to identity - IdentityMatrix = np.identity(2**weight) - self.assertListEqual( - meas_cal.cal_matrix.tolist(), - IdentityMatrix.tolist(), - "Error: the calibration matrix is not equal to identity", - ) - - # Assert that the readout fidelity is equal to 1 - self.assertEqual( - meas_cal.readout_fidelity(), - 1.0, - "Error: the average fidelity is not equal to 1", - ) - - # Generate ideal (equally distributed) results - results_dict, results_list = self.generate_ideal_results(state_labels, weight) - - with self.assertWarns(DeprecationWarning): - # Output the filter - meas_filter = meas_cal.filter - - # Apply the calibration matrix to results - # in list and dict forms using different methods - results_dict_1 = meas_filter.apply(results_dict, method="least_squares") - results_dict_0 = meas_filter.apply(results_dict, method="pseudo_inverse") - results_list_1 = meas_filter.apply(results_list, method="least_squares") - results_list_0 = meas_filter.apply(results_list, method="pseudo_inverse") - - # Assert that the results are equally distributed - self.assertListEqual(results_list, results_list_0.tolist()) - self.assertListEqual(results_list, np.round(results_list_1).tolist()) - self.assertDictEqual(results_dict, results_dict_0) - round_results = {} - for key, val in results_dict_1.items(): - round_results[key] = np.round(val) - self.assertDictEqual(results_dict, round_results) - - def test_meas_cal_on_circuit(self): - """Test an execution on a circuit.""" - # Generate the calibration circuits - with self.assertWarns(DeprecationWarning): - meas_calibs, state_labels, ghz = meas_calib_circ_creation() - - # Run the calibration circuits - backend = AerSimulator() - job = qiskit.execute( - meas_calibs, - backend=backend, - shots=self.shots, - seed_simulator=SEED, - seed_transpiler=SEED, - ) - cal_results = job.result() - - with self.assertWarns(DeprecationWarning): - # Make a calibration matrix - meas_cal = CompleteMeasFitter(cal_results, state_labels) - # Calculate the fidelity - fidelity = meas_cal.readout_fidelity() - - job = qiskit.execute( - [ghz], backend=backend, shots=self.shots, seed_simulator=SEED, seed_transpiler=SEED - ) - results = job.result() - - # Predicted equally distributed results - predicted_results = {"000": 0.5, "111": 0.5} - - with self.assertWarns(DeprecationWarning): - meas_filter = meas_cal.filter - - # Calculate the results after mitigation - output_results_pseudo_inverse = meas_filter.apply( - results, method="pseudo_inverse" - ).get_counts(0) - output_results_least_square = meas_filter.apply(results, method="least_squares").get_counts( - 0 - ) - - # Compare with expected fidelity and expected results - self.assertAlmostEqual(fidelity, 1.0) - self.assertAlmostEqual( - output_results_pseudo_inverse["000"] / self.shots, predicted_results["000"], places=1 - ) - - self.assertAlmostEqual( - output_results_least_square["000"] / self.shots, predicted_results["000"], places=1 - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["111"] / self.shots, predicted_results["111"], places=1 - ) - - self.assertAlmostEqual( - output_results_least_square["111"] / self.shots, predicted_results["111"], places=1 - ) - - def test_ideal_tensored_meas_cal(self): - """Test ideal execution, without noise.""" - - mit_pattern = [[1, 2], [3, 4, 5], [6]] - meas_layout = [1, 2, 3, 4, 5, 6] - - # Generate the calibration circuits - with self.assertWarns(DeprecationWarning): - meas_calibs, _ = tensored_meas_cal(mit_pattern=mit_pattern) - - # Perform an ideal execution on the generated circuits - backend = AerSimulator() - cal_results = qiskit.execute(meas_calibs, backend=backend, shots=self.shots).result() - - with self.assertWarns(DeprecationWarning): - # Make calibration matrices - meas_cal = TensoredMeasFitter(cal_results, mit_pattern=mit_pattern) - - # Assert that the calibration matrices are equal to identity - cal_matrices = meas_cal.cal_matrices - self.assertEqual( - len(mit_pattern), len(cal_matrices), "Wrong number of calibration matrices" - ) - for qubit_list, cal_mat in zip(mit_pattern, cal_matrices): - IdentityMatrix = np.identity(2 ** len(qubit_list)) - self.assertListEqual( - cal_mat.tolist(), - IdentityMatrix.tolist(), - "Error: the calibration matrix is not equal to identity", - ) - - # Assert that the readout fidelity is equal to 1 - self.assertEqual( - meas_cal.readout_fidelity(), - 1.0, - "Error: the average fidelity is not equal to 1", - ) - - with self.assertWarns(DeprecationWarning): - # Generate ideal (equally distributed) results - results_dict, _ = self.generate_ideal_results(count_keys(6), 6) - # Output the filter - meas_filter = meas_cal.filter - # Apply the calibration matrix to results - # in list and dict forms using different methods - results_dict_1 = meas_filter.apply( - results_dict, method="least_squares", meas_layout=meas_layout - ) - results_dict_0 = meas_filter.apply( - results_dict, method="pseudo_inverse", meas_layout=meas_layout - ) - - # Assert that the results are equally distributed - self.assertDictEqual(results_dict, results_dict_0) - round_results = {} - for key, val in results_dict_1.items(): - round_results[key] = np.round(val) - self.assertDictEqual(results_dict, round_results) - - def test_tensored_meas_cal_on_circuit(self): - """Test an execution on a circuit.""" - - with self.assertWarns(DeprecationWarning): - # Generate the calibration circuits - meas_calibs, mit_pattern, ghz, meas_layout = tensored_calib_circ_creation() - - # Run the calibration circuits - backend = AerSimulator() - cal_results = qiskit.execute( - meas_calibs, - backend=backend, - shots=self.shots, - seed_simulator=SEED, - seed_transpiler=SEED, - ).result() - - with self.assertWarns(DeprecationWarning): - # Make a calibration matrix - meas_cal = TensoredMeasFitter(cal_results, mit_pattern=mit_pattern) - # Calculate the fidelity - fidelity = meas_cal.readout_fidelity(0) * meas_cal.readout_fidelity(1) - - results = qiskit.execute( - [ghz], backend=backend, shots=self.shots, seed_simulator=SEED, seed_transpiler=SEED - ).result() - - # Predicted equally distributed results - predicted_results = {"000": 0.5, "111": 0.5} - - with self.assertWarns(DeprecationWarning): - meas_filter = meas_cal.filter - # Calculate the results after mitigation - output_results_pseudo_inverse = meas_filter.apply( - results, method="pseudo_inverse", meas_layout=meas_layout - ).get_counts(0) - output_results_least_square = meas_filter.apply( - results, method="least_squares", meas_layout=meas_layout - ).get_counts(0) - - # Compare with expected fidelity and expected results - self.assertAlmostEqual(fidelity, 1.0) - self.assertAlmostEqual( - output_results_pseudo_inverse["000"] / self.shots, predicted_results["000"], places=1 - ) - - self.assertAlmostEqual( - output_results_least_square["000"] / self.shots, predicted_results["000"], places=1 - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["111"] / self.shots, predicted_results["111"], places=1 - ) - - self.assertAlmostEqual( - output_results_least_square["111"] / self.shots, predicted_results["111"], places=1 - ) - - def test_meas_fitter_with_noise(self): - """Test the MeasurementFitter with noise.""" - tests = [] - runs = 3 - with self.assertWarns(DeprecationWarning): - for run in range(runs): - cal_results, state_labels, circuit_results = meas_calibration_circ_execution( - 1000, SEED + run - ) - - meas_cal = CompleteMeasFitter(cal_results, state_labels) - meas_filter = MeasurementFilter(meas_cal.cal_matrix, state_labels) - - # Calculate the results after mitigation - results_pseudo_inverse = meas_filter.apply(circuit_results, method="pseudo_inverse") - results_least_square = meas_filter.apply(circuit_results, method="least_squares") - tests.append( - { - "cal_matrix": convert_ndarray_to_list_in_data(meas_cal.cal_matrix), - "fidelity": meas_cal.readout_fidelity(), - "results": circuit_results, - "results_pseudo_inverse": results_pseudo_inverse, - "results_least_square": results_least_square, - } - ) - # Set the state labels - state_labels = ["000", "001", "010", "011", "100", "101", "110", "111"] - meas_cal = CompleteMeasFitter(None, state_labels, circlabel="test") - - for tst_index, _ in enumerate(tests): - # Set the calibration matrix - meas_cal.cal_matrix = tests[tst_index]["cal_matrix"] - # Calculate the fidelity - fidelity = meas_cal.readout_fidelity() - - meas_filter = MeasurementFilter(tests[tst_index]["cal_matrix"], state_labels) - - # Calculate the results after mitigation - output_results_pseudo_inverse = meas_filter.apply( - tests[tst_index]["results"], method="pseudo_inverse" - ) - output_results_least_square = meas_filter.apply( - tests[tst_index]["results"], method="least_squares" - ) - - # Compare with expected fidelity and expected results - self.assertAlmostEqual(fidelity, tests[tst_index]["fidelity"], places=0) - self.assertAlmostEqual( - output_results_pseudo_inverse["000"], - tests[tst_index]["results_pseudo_inverse"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square["000"], - tests[tst_index]["results_least_square"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["111"], - tests[tst_index]["results_pseudo_inverse"]["111"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square["111"], - tests[tst_index]["results_least_square"]["111"], - places=0, - ) - - def test_tensored_meas_fitter_with_noise(self): - """Test the TensoredFitter with noise.""" - with self.assertWarns(DeprecationWarning): - cal_results, mit_pattern, circuit_results, meas_layout = tensored_calib_circ_execution( - 1000, SEED - ) - meas_cal = TensoredMeasFitter(cal_results, mit_pattern=mit_pattern) - meas_filter = meas_cal.filter - # Calculate the results after mitigation - results_pseudo_inverse = meas_filter.apply( - circuit_results.get_counts(), method="pseudo_inverse", meas_layout=meas_layout - ) - results_least_square = meas_filter.apply( - circuit_results.get_counts(), method="least_squares", meas_layout=meas_layout - ) - - saved_info = { - "cal_results": cal_results.to_dict(), - "results": circuit_results.to_dict(), - "mit_pattern": mit_pattern, - "meas_layout": meas_layout, - "fidelity": meas_cal.readout_fidelity(), - "results_pseudo_inverse": results_pseudo_inverse, - "results_least_square": results_least_square, - } - - saved_info["cal_results"] = Result.from_dict(saved_info["cal_results"]) - saved_info["results"] = Result.from_dict(saved_info["results"]) - - with self.assertWarns(DeprecationWarning): - meas_cal = TensoredMeasFitter( - saved_info["cal_results"], mit_pattern=saved_info["mit_pattern"] - ) - # Calculate the fidelity - fidelity = meas_cal.readout_fidelity(0) * meas_cal.readout_fidelity(1) - # Compare with expected fidelity and expected results - - self.assertAlmostEqual(fidelity, saved_info["fidelity"], places=0) - - with self.assertWarns(DeprecationWarning): - meas_filter = meas_cal.filter - # Calculate the results after mitigation - output_results_pseudo_inverse = meas_filter.apply( - saved_info["results"].get_counts(0), - method="pseudo_inverse", - meas_layout=saved_info["meas_layout"], - ) - output_results_least_square = meas_filter.apply( - saved_info["results"], method="least_squares", meas_layout=saved_info["meas_layout"] - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["000"], - saved_info["results_pseudo_inverse"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square.get_counts(0)["000"], - saved_info["results_least_square"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["111"], - saved_info["results_pseudo_inverse"]["111"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square.get_counts(0)["111"], - saved_info["results_least_square"]["111"], - places=0, - ) - - substates_list = [] - with self.assertWarns(DeprecationWarning): - for qubit_list in saved_info["mit_pattern"]: - substates_list.append(count_keys(len(qubit_list))[::-1]) - fitter_other_order = TensoredMeasFitter( - saved_info["cal_results"], - substate_labels_list=substates_list, - mit_pattern=saved_info["mit_pattern"], - ) - - fidelity = fitter_other_order.readout_fidelity(0) * meas_cal.readout_fidelity(1) - - self.assertAlmostEqual(fidelity, saved_info["fidelity"], places=0) - - with self.assertWarns(DeprecationWarning): - meas_filter = fitter_other_order.filter - # Calculate the results after mitigation - output_results_pseudo_inverse = meas_filter.apply( - saved_info["results"].get_counts(0), - method="pseudo_inverse", - meas_layout=saved_info["meas_layout"], - ) - output_results_least_square = meas_filter.apply( - saved_info["results"], method="least_squares", meas_layout=saved_info["meas_layout"] - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["000"], - saved_info["results_pseudo_inverse"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square.get_counts(0)["000"], - saved_info["results_least_square"]["000"], - places=0, - ) - - self.assertAlmostEqual( - output_results_pseudo_inverse["111"], - saved_info["results_pseudo_inverse"]["111"], - places=0, - ) - - self.assertAlmostEqual( - output_results_least_square.get_counts(0)["111"], - saved_info["results_least_square"]["111"], - places=0, - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/visualization/references/pass_manager_standard.dot b/test/python/visualization/references/pass_manager_standard.dot index ae4233665a10..e1c5d3979ca2 100644 --- a/test/python/visualization/references/pass_manager_standard.dot +++ b/test/python/visualization/references/pass_manager_standard.dot @@ -61,27 +61,29 @@ fontname=helvetica; label="[6] do_while"; labeljust=l; 18 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; +19 [color=black, fontname=helvetica, fontsize=10, label=label, shape=ellipse, style=dashed]; +19 -> 18; 14 -> 18; } -subgraph cluster_19 { +subgraph cluster_20 { fontname=helvetica; label="[7] "; labeljust=l; -20 [color=blue, fontname=helvetica, label=GateDirection, shape=rectangle]; -21 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -21 -> 20; -22 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -22 -> 20; -18 -> 20; +21 [color=blue, fontname=helvetica, label=GateDirection, shape=rectangle]; +22 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +22 -> 21; +23 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +23 -> 21; +18 -> 21; } -subgraph cluster_23 { +subgraph cluster_24 { fontname=helvetica; label="[8] "; labeljust=l; -24 [color=blue, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; -20 -> 24; +25 [color=blue, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; +21 -> 25; } } diff --git a/test/python/visualization/references/pass_manager_style.dot b/test/python/visualization/references/pass_manager_style.dot index 3c779cfde72b..0c80853d00be 100644 --- a/test/python/visualization/references/pass_manager_style.dot +++ b/test/python/visualization/references/pass_manager_style.dot @@ -61,27 +61,29 @@ fontname=helvetica; label="[6] do_while"; labeljust=l; 18 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; +19 [color=black, fontname=helvetica, fontsize=10, label=label, shape=ellipse, style=dashed]; +19 -> 18; 14 -> 18; } -subgraph cluster_19 { +subgraph cluster_20 { fontname=helvetica; label="[7] "; labeljust=l; -20 [color=blue, fontname=helvetica, label=GateDirection, shape=rectangle]; -21 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -21 -> 20; -22 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -22 -> 20; -18 -> 20; +21 [color=blue, fontname=helvetica, label=GateDirection, shape=rectangle]; +22 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +22 -> 21; +23 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +23 -> 21; +18 -> 21; } -subgraph cluster_23 { +subgraph cluster_24 { fontname=helvetica; label="[8] "; labeljust=l; -24 [color=grey, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; -20 -> 24; +25 [color=grey, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; +21 -> 25; } } diff --git a/tools/deploy_translatable_strings.sh b/tools/deploy_translatable_strings.sh index afac6869447d..1ae60b5f7072 100755 --- a/tools/deploy_translatable_strings.sh +++ b/tools/deploy_translatable_strings.sh @@ -84,8 +84,9 @@ rm -rf \ echo "+ 'cp' wanted files from source to target" # Copy the new rendered files and add them to the commit. 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}" +# Copy files necessary to build the Qiskit package. +cp "$SOURCE_REPO_ROOT/setup.py" "${TARGET_REPO_ROOT}" +cp "$SOURCE_REPO_ROOT/pyproject.toml" "${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}" diff --git a/tox.ini b/tox.ini index 0c799d28ba9a..df46820fc026 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,9 @@ [tox] minversion = 3.3.0 -envlist = py38, py39, py310, py311, lint-incr +envlist = py38, py39, py310, py311, py312, lint-incr isolated_build = true [testenv] -# 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} @@ -22,16 +18,14 @@ 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} [testenv:lint] basepython = python3 commands = - ruff check qiskit test tools examples setup.py qiskit_pkg - black --check {posargs} qiskit test tools examples setup.py qiskit_pkg + ruff check qiskit test tools examples setup.py + black --check {posargs} qiskit test tools examples setup.py pylint -rn qiskit test tools # This line is commented out until #6649 merges. We can't run this currently # via tox because tox doesn't support globbing @@ -45,8 +39,8 @@ commands = basepython = python3 allowlist_externals = git commands = - ruff check qiskit test tools examples setup.py qiskit_pkg - black --check {posargs} qiskit test tools examples setup.py qiskit_pkg + ruff check qiskit test tools examples setup.py + black --check {posargs} qiskit test tools examples setup.py -git fetch -q https://github.com/Qiskit/qiskit-terra.git :lint_incr_latest python {toxinidir}/tools/pylint_incr.py -rn -j4 -sn --paths :/qiskit/*.py :/test/*.py :/tools/*.py python {toxinidir}/tools/pylint_incr.py -rn -j4 -sn --disable='invalid-name,missing-module-docstring,redefined-outer-name' --paths :(glob,top)examples/python/*.py @@ -59,7 +53,7 @@ commands = skip_install = true deps = -r requirements-dev.txt -commands = black {posargs} qiskit test tools examples setup.py qiskit_pkg +commands = black {posargs} qiskit test tools examples setup.py [testenv:coverage] basepython = python3