From 94cea419010b0c8210ef633bf39d4a89618ade01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Mon, 18 Nov 2024 23:50:28 +0100 Subject: [PATCH 01/10] Fix post-oxidization change in `ConsolidateBlocks` behavior (#13450) * Set decomposer to None if kak_gates not found during ConsolidateBlocks initialization. * Add test that reproduces issue * Re-add rzz to list of kak gates * Add path for rzx gate. --- .../passes/optimization/consolidate_blocks.py | 12 ++++++-- .../transpiler/test_consolidate_blocks.py | 29 +++++++++++++++---- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 63dca11f6d2d..ce64233699b2 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -12,9 +12,11 @@ """Replace each block of consecutive gates by a single Unitary node.""" from __future__ import annotations +from math import pi from qiskit.synthesis.two_qubit import TwoQubitBasisDecomposer -from qiskit.circuit.library.standard_gates import CXGate, CZGate, iSwapGate, ECRGate +from qiskit.circuit.library.standard_gates import CXGate, CZGate, iSwapGate, ECRGate, RXXGate + from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.passmanager import PassManager from qiskit._accelerate.consolidate_blocks import consolidate_blocks @@ -27,6 +29,7 @@ "cz": CZGate(), "iswap": iSwapGate(), "ecr": ECRGate(), + "rxx": RXXGate(pi / 2), } @@ -70,7 +73,6 @@ def __init__( if basis_gates is not None: self.basis_gates = set(basis_gates) self.force_consolidate = force_consolidate - if kak_basis_gate is not None: self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate) elif basis_gates is not None: @@ -79,8 +81,12 @@ def __init__( self.decomposer = TwoQubitBasisDecomposer( KAK_GATE_NAMES[kak_gates.pop()], basis_fidelity=approximation_degree or 1.0 ) + elif "rzx" in basis_gates: + self.decomposer = TwoQubitBasisDecomposer( + CXGate(), basis_fidelity=approximation_degree or 1.0 + ) else: - self.decomposer = TwoQubitBasisDecomposer(CXGate()) + self.decomposer = None else: self.decomposer = TwoQubitBasisDecomposer(CXGate()) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 3b3aff1db228..fbe11ff77db1 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -16,20 +16,19 @@ import unittest import numpy as np +from ddt import ddt, data from qiskit.circuit import QuantumCircuit, QuantumRegister, IfElseOp, Gate from qiskit.circuit.library import U2Gate, SwapGate, CXGate, CZGate, UnitaryGate from qiskit.converters import circuit_to_dag -from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.operators.measures import process_fidelity -from qiskit.transpiler import PassManager -from qiskit.transpiler import Target -from qiskit.transpiler.passes import Collect1qRuns -from qiskit.transpiler.passes import Collect2qBlocks +from qiskit.transpiler import PassManager, Target, generate_preset_pass_manager +from qiskit.transpiler.passes import ConsolidateBlocks, Collect1qRuns, Collect2qBlocks from test import QiskitTestCase # pylint: disable=wrong-import-order +@ddt class TestConsolidateBlocks(QiskitTestCase): """ Tests to verify that consolidating blocks of gates into unitaries @@ -571,6 +570,26 @@ def __init__(self): self.assertEqual(res, qc) + @data(2, 3) + def test_no_kak_gates_in_preset_pm(self, opt_level): + """Test correct initialization of ConsolidateBlocks pass when kak_gates aren't found. + Reproduces https://github.com/Qiskit/qiskit/issues/13438.""" + + qc = QuantumCircuit(2) + qc.cz(0, 1) + qc.sx([0, 1]) + qc.cz(0, 1) + + ref_pm = generate_preset_pass_manager( + optimization_level=1, basis_gates=["rz", "rzz", "sx", "x", "rx"] + ) + ref_tqc = ref_pm.run(qc) + pm = generate_preset_pass_manager( + optimization_level=opt_level, basis_gates=["rz", "rzz", "sx", "x", "rx"] + ) + tqc = pm.run(qc) + self.assertEqual(ref_tqc, tqc) + if __name__ == "__main__": unittest.main() From 7f19daaeb865e5c1e9649539bd2cdef7e86dc40e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:29:59 +0100 Subject: [PATCH 02/10] Improve deprecation warning to clarify what gate was detected as custom. Fix typo. (#13460) --- .../preset_passmanagers/generate_preset_pass_manager.py | 7 ++++--- test/python/transpiler/test_preset_passmanagers.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 8f08a8337376..96366e14af42 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -479,10 +479,11 @@ def _parse_basis_gates(basis_gates, backend, inst_map, skip_target): if inst not in standard_gates and inst not in default_gates: warnings.warn( category=DeprecationWarning, - message="Providing custom gates through the ``basis_gates`` argument is deprecated " - "for both ``transpile`` and ``generate_preset_pass_manager`` as of Qiskit 1.3.0. " + message=f"Providing non-standard gates ({inst}) through the ``basis_gates`` " + "argument is deprecated for both ``transpile`` and ``generate_preset_pass_manager`` " + "as of Qiskit 1.3.0. " "It will be removed in Qiskit 2.0. The ``target`` parameter should be used instead. " - "You can build a target instance using ``Target.from_configuration()`` and provide" + "You can build a target instance using ``Target.from_configuration()`` and provide " "custom gate definitions with the ``custom_name_mapping`` argument.", ) skip_target = True diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index d98bd18b6cbd..0941039cdde4 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -163,7 +163,7 @@ def test_unitary_is_preserved_if_in_basis(self, level): qc.measure_all() with self.assertWarnsRegex( DeprecationWarning, - "Providing custom gates through the ``basis_gates`` argument is deprecated", + "Providing non-standard gates \\(unitary\\) through the ``basis_gates`` argument", ): result = transpile(qc, basis_gates=["cx", "u", "unitary"], optimization_level=level) self.assertEqual(result, qc) @@ -185,7 +185,7 @@ def test_unitary_is_preserved_if_in_basis_synthesis_translation(self, level): qc.measure_all() with self.assertWarnsRegex( DeprecationWarning, - "Providing custom gates through the ``basis_gates`` argument is deprecated", + "Providing non-standard gates \\(unitary\\) through the ``basis_gates`` argument", ): result = transpile( qc, @@ -1761,7 +1761,7 @@ def test_custom_basis_gates_raise(self, optimization_level): basis_gates = ["my_gate"] with self.assertWarnsRegex( DeprecationWarning, - "Providing custom gates through the ``basis_gates`` argument is deprecated", + "Providing non-standard gates \\(my_gate\\) through the ``basis_gates`` argument", ): _ = generate_preset_pass_manager( optimization_level=optimization_level, basis_gates=basis_gates From c7924262fa254b6b0f4f20dfdca47fd6e9d63dad Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 20 Nov 2024 10:39:17 -0500 Subject: [PATCH 03/10] Fix basis_count checking in ConsolidateBlocks (#13463) The ConsolidateBlocks pass was ported to rust in #13368 and as part of that implementation a small behavior difference between the rust and python interfaces was causing the pass to not work correctly with non-CX gates. The internal 2q decomposer interface stores a sentinel string for the kak gate which is used to tell the python space constructor use the python defined gate object. However in the pass code we weren't factoring this difference in, and for non-CX gates we were evaluating the basis count as the number of gates with that sentinel value name (which is almost always zero) and this was preventing the pass from consolidating many blocks that should have been. This commit fixes this issue by taking the name from python space and passing it through to the rust portion of the code and using that for the comparison. Fixes #13459 --- crates/accelerate/src/consolidate_blocks.rs | 5 +- .../passes/optimization/consolidate_blocks.py | 1 + .../transpiler/test_consolidate_blocks.py | 80 ++++++++++++++++++- 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/crates/accelerate/src/consolidate_blocks.rs b/crates/accelerate/src/consolidate_blocks.rs index 1edd592ce877..0fec3fa2909a 100644 --- a/crates/accelerate/src/consolidate_blocks.rs +++ b/crates/accelerate/src/consolidate_blocks.rs @@ -54,11 +54,12 @@ const MAX_2Q_DEPTH: usize = 20; #[allow(clippy::too_many_arguments)] #[pyfunction] -#[pyo3(signature = (dag, decomposer, force_consolidate, target=None, basis_gates=None, blocks=None, runs=None))] +#[pyo3(signature = (dag, decomposer, basis_gate_name, force_consolidate, target=None, basis_gates=None, blocks=None, runs=None))] pub(crate) fn consolidate_blocks( py: Python, dag: &mut DAGCircuit, decomposer: &TwoQubitBasisDecomposer, + basis_gate_name: &str, force_consolidate: bool, target: Option<&Target>, basis_gates: Option>, @@ -125,7 +126,7 @@ pub(crate) fn consolidate_blocks( let inst = dag.dag()[*node].unwrap_operation(); block_qargs.extend(dag.get_qargs(inst.qubits)); all_block_gates.insert(*node); - if inst.op.name() == decomposer.gate_name() { + if inst.op.name() == basis_gate_name { basis_count += 1; } if !is_supported( diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index ce64233699b2..f31401abb6a7 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -109,6 +109,7 @@ def run(self, dag): consolidate_blocks( dag, self.decomposer._inner_decomposer, + self.decomposer.gate.name, self.force_consolidate, target=self.target, basis_gates=self.basis_gates, diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index fbe11ff77db1..83379a4eecb7 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -18,8 +18,17 @@ import numpy as np from ddt import ddt, data -from qiskit.circuit import QuantumCircuit, QuantumRegister, IfElseOp, Gate -from qiskit.circuit.library import U2Gate, SwapGate, CXGate, CZGate, UnitaryGate +from qiskit.circuit import QuantumCircuit, QuantumRegister, IfElseOp, Gate, Parameter +from qiskit.circuit.library import ( + U2Gate, + SwapGate, + CXGate, + CZGate, + UnitaryGate, + SXGate, + XGate, + RZGate, +) from qiskit.converters import circuit_to_dag from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.operators.measures import process_fidelity @@ -590,6 +599,73 @@ def test_no_kak_gates_in_preset_pm(self, opt_level): tqc = pm.run(qc) self.assertEqual(ref_tqc, tqc) + def test_non_cx_basis_gate(self): + """Test a non-cx kak gate is consolidated correctly.""" + qc = QuantumCircuit(2) + qc.cz(0, 1) + qc.x(0) + qc.h(1) + qc.z(1) + qc.t(1) + qc.h(0) + qc.t(0) + qc.cz(1, 0) + qc.sx(0) + qc.sx(1) + qc.cz(0, 1) + qc.sx(0) + qc.sx(1) + qc.cz(1, 0) + qc.x(0) + qc.h(1) + qc.z(1) + qc.t(1) + qc.h(0) + qc.t(0) + qc.cz(0, 1) + + consolidate_pass = ConsolidateBlocks(basis_gates=["sx", "x", "rz", "cz"]) + res = consolidate_pass(qc) + self.assertEqual({"unitary": 1}, res.count_ops()) + self.assertEqual(Operator.from_circuit(qc), Operator(res.data[0].operation.params[0])) + + def test_non_cx_target(self): + """Test a non-cx kak gate is consolidated correctly.""" + qc = QuantumCircuit(2) + qc.cz(0, 1) + qc.x(0) + qc.h(1) + qc.z(1) + qc.t(1) + qc.h(0) + qc.t(0) + qc.cz(1, 0) + qc.sx(0) + qc.sx(1) + qc.cz(0, 1) + qc.sx(0) + qc.sx(1) + qc.cz(1, 0) + qc.x(0) + qc.h(1) + qc.z(1) + qc.t(1) + qc.h(0) + qc.t(0) + qc.cz(0, 1) + + phi = Parameter("phi") + target = Target(num_qubits=2) + target.add_instruction(SXGate(), {(0,): None, (1,): None}) + target.add_instruction(XGate(), {(0,): None, (1,): None}) + target.add_instruction(RZGate(phi), {(0,): None, (1,): None}) + target.add_instruction(CZGate(), {(0, 1): None, (1, 0): None}) + + consolidate_pass = ConsolidateBlocks(target=target) + res = consolidate_pass(qc) + self.assertEqual({"unitary": 1}, res.count_ops()) + self.assertEqual(Operator.from_circuit(qc), Operator(res.data[0].operation.params[0])) + if __name__ == "__main__": unittest.main() From a90f9fa4a4cbc02b39fc1a913316e4e779999d84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 19:57:24 -0500 Subject: [PATCH 04/10] Bump bytemuck from 1.19.0 to 1.20.0 (#13461) Bumps [bytemuck](https://github.com/Lokathor/bytemuck) from 1.19.0 to 1.20.0. - [Changelog](https://github.com/Lokathor/bytemuck/blob/main/changelog.md) - [Commits](https://github.com/Lokathor/bytemuck/compare/v1.19.0...v1.20.0) --- updated-dependencies: - dependency-name: bytemuck dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 988b68d7d92c..b1bbde626b57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,9 +111,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" dependencies = [ "bytemuck_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 156507d0dc9a..52a9471497b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ license = "Apache-2.0" # # Each crate can add on specific features freely as it inherits. [workspace.dependencies] -bytemuck = "1.19" +bytemuck = "1.20" indexmap.version = "2.6.0" hashbrown.version = "0.14.5" num-bigint = "0.4" From 5f3f59469fcd9edae2feb80f74026265ebceb29a Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 21 Nov 2024 16:04:57 +0100 Subject: [PATCH 05/10] Extend ASV tests with QASM3 dump (#13334) * test qasm3 exporter * Custom gates and parametrized circuits --------- Co-authored-by: Matthew Treinish --- test/benchmarks/mapping_passes.py | 4 +- test/benchmarks/qasm3_exporter.py | 83 +++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 test/benchmarks/qasm3_exporter.py diff --git a/test/benchmarks/mapping_passes.py b/test/benchmarks/mapping_passes.py index 4f87323f33a8..07a6b037db5b 100644 --- a/test/benchmarks/mapping_passes.py +++ b/test/benchmarks/mapping_passes.py @@ -35,7 +35,7 @@ def setup(self, n_qubits, depth): n_qubits, depth, measure=True, conditional=True, reset=True, seed=seed, max_operands=2 ) self.fresh_dag = circuit_to_dag(self.circuit) - self.basis_gates = ["u1", "u2", "u3", "cx", "iid"] + self.basis_gates = ["u1", "u2", "u3", "cx", "id"] self.cmap = [ [0, 1], [1, 0], @@ -166,7 +166,7 @@ def setup(self, n_qubits, depth): n_qubits, depth, measure=True, conditional=True, reset=True, seed=seed, max_operands=2 ) self.fresh_dag = circuit_to_dag(self.circuit) - self.basis_gates = ["u1", "u2", "u3", "cx", "iid"] + self.basis_gates = ["u1", "u2", "u3", "cx", "id"] self.cmap = [ [0, 1], [1, 0], diff --git a/test/benchmarks/qasm3_exporter.py b/test/benchmarks/qasm3_exporter.py new file mode 100644 index 000000000000..3860efe8b407 --- /dev/null +++ b/test/benchmarks/qasm3_exporter.py @@ -0,0 +1,83 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024 +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# pylint: disable=missing-docstring +# pylint: disable=attribute-defined-outside-init + +from qiskit.circuit import Parameter +from qiskit import QuantumCircuit +from qiskit import qasm3 + +from .utils import random_circuit + + +class RandomBenchmarks: + + params = ([20], [256, 1024], [0, 42]) + + param_names = ["n_qubits", "depth", "seed"] + timeout = 300 + + def setup(self, n_qubits, depth, seed): + self.circuit = random_circuit( + n_qubits, + depth, + measure=True, + conditional=True, + reset=True, + seed=seed, + max_operands=3, + ) + + def time_dumps(self, _, __, ___): + qasm3.dumps(self.circuit) + + +class CustomGateBenchmarks: + + params = ([200], [100]) + + param_names = ["n_qubits", "depth"] + timeout = 300 + + def setup(self, n_qubits, depth): + custom_gate = QuantumCircuit(2, name="custom_gate") + custom_gate.h(0) + custom_gate.x(1) + + qc = QuantumCircuit(n_qubits) + for _ in range(depth): + for i in range(n_qubits - 1): + qc.append(custom_gate.to_gate(), [i, i + 1]) + self.circuit = qc + + def time_dumps(self, _, __): + qasm3.dumps(self.circuit) + + +class ParameterizedBenchmarks: + + params = ([20, 50], [1, 5, 10]) + + param_names = ["n_qubits", "n_params"] + timeout = 300 + + def setup(self, n_qubits, n_params): + qc = QuantumCircuit(n_qubits) + params = [Parameter(f"angle{i}") for i in range(n_params)] + for n in range(n_qubits - 1): + for i in params: + qc.rx(i, n) + self.circuit = qc + + def time_dumps(self, _, __): + qasm3.dumps(self.circuit) From 4bb143275cfb01954d2898c44afe48e08640bcad Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 22 Nov 2024 12:25:21 -0500 Subject: [PATCH 06/10] Fix typing of inserted UnitaryGates in quantum_volume (#13479) There was a small typing issue in the quantum_volume implementation where there was a mismatch in the Python type and the rust type. The UnitaryGate were being added to the circuit as a rust space PyInstruction instead of a PyGate. This was incorrect as UnitaryGate is unitary and a gate type in python, this mismatch was causing subsequent transpilation or anything working with the unitaries in the quantum volume circuit from rust to mischaracterize the operations as non-unitary so things like getting the matrix would fail. This commit corrects the typing so the gates are added as a unitary operation in the rust typing. This will get much simpler and less error prone when #13272 is implemented and we have a rust native UnitaryGate type. --- .../src/circuit_library/quantum_volume.rs | 9 ++++----- .../transpiler/test_preset_passmanagers.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/accelerate/src/circuit_library/quantum_volume.rs b/crates/accelerate/src/circuit_library/quantum_volume.rs index e17357e7cec2..a6c5a3839d90 100644 --- a/crates/accelerate/src/circuit_library/quantum_volume.rs +++ b/crates/accelerate/src/circuit_library/quantum_volume.rs @@ -27,7 +27,7 @@ use rayon::prelude::*; use qiskit_circuit::circuit_data::CircuitData; use qiskit_circuit::imports::UNITARY_GATE; use qiskit_circuit::operations::Param; -use qiskit_circuit::operations::PyInstruction; +use qiskit_circuit::operations::PyGate; use qiskit_circuit::packed_instruction::PackedOperation; use qiskit_circuit::{Clbit, Qubit}; use smallvec::{smallvec, SmallVec}; @@ -127,17 +127,16 @@ pub fn quantum_volume( let unitary_gate = UNITARY_GATE .get_bound(py) .call((unitary.clone(), py.None(), false), Some(&kwargs))?; - let instruction = PyInstruction { + let instruction = PyGate { qubits: 2, clbits: 0, params: 1, op_name: "unitary".to_string(), - control_flow: false, - instruction: unitary_gate.unbind(), + gate: unitary_gate.unbind(), }; let qubit = layer_index * 2; Ok(( - PackedOperation::from_instruction(Box::new(instruction)), + PackedOperation::from_gate(Box::new(instruction)), smallvec![Param::Obj(unitary.unbind().into())], vec![permutation[qubit], permutation[qubit + 1]], vec![], diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 0941039cdde4..1ecee97a5ed9 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -22,6 +22,7 @@ from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister from qiskit.circuit import Qubit, Gate, ControlFlowOp, ForLoopOp +from qiskit.circuit.library import quantum_volume from qiskit.compiler import transpile from qiskit.transpiler import CouplingMap, Layout, PassManager, TranspilerError, Target from qiskit.circuit.library import U2Gate, U3Gate, QuantumVolume, CXGate, CZGate, XGate @@ -343,6 +344,24 @@ def test_v1(self, circuit, level, backend): ) self.assertIsInstance(result, QuantumCircuit) + @data(0, 1, 2, 3) + def test_quantum_volume_function_transpile(self, opt_level): + """Test quantum_volume transpilation.""" + qc = quantum_volume(10, 10, 12345) + backend = GenericBackendV2( + num_qubits=100, + basis_gates=["cz", "rz", "sx", "x", "id"], + coupling_map=CouplingMap.from_grid(10, 10), + ) + pm = generate_preset_pass_manager(opt_level, backend) + res = pm.run(qc) + for inst in res.data: + self.assertTrue( + backend.target.instruction_supported( + inst.operation.name, qargs=tuple(res.find_bit(x).index for x in inst.qubits) + ) + ) + @ddt class TestPassesInspection(QiskitTestCase): From 4c1de131201164d5a2116342b9a431b34a92d917 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> Date: Tue, 26 Nov 2024 01:35:19 +0900 Subject: [PATCH 07/10] update readme (#13487) --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9238bf88fa41..0546e76cf63e 100644 --- a/README.md +++ b/README.md @@ -66,13 +66,13 @@ we use `measure_all(inplace=False)` to get a copy of the circuit in which all th qc_measured = qc_example.measure_all(inplace=False) # 3. Execute using the Sampler primitive -from qiskit.primitives.sampler import Sampler -sampler = Sampler() -job = sampler.run(qc_measured, shots=1000) +from qiskit.primitives import StatevectorSampler +sampler = StatevectorSampler() +job = sampler.run([qc_measured], shots=1000) result = job.result() -print(f" > Quasi probability distribution: {result.quasi_dists}") +print(f" > Counts: {result[0].meas.get_counts()}") ``` -Running this will give an outcome similar to `{0: 0.497, 7: 0.503}` which is `000` 50% of the time and `111` 50% of the time up to statistical fluctuations. +Running this will give an outcome similar to `{'000': 497, '111': 503}` which is `000` 50% of the time and `111` 50% of the time up to statistical fluctuations. To illustrate the power of Estimator, we now use the quantum information toolbox to create the operator $XXY+XYX+YXX-YYY$ and pass it to the `run()` function, along with our quantum circuit. Note the Estimator requires a circuit _**without**_ measurement, so we use the `qc_example` circuit we created earlier. ```python @@ -81,17 +81,17 @@ from qiskit.quantum_info import SparsePauliOp operator = SparsePauliOp.from_list([("XXY", 1), ("XYX", 1), ("YXX", 1), ("YYY", -1)]) # 3. Execute using the Estimator primitive -from qiskit.primitives import Estimator -estimator = Estimator() -job = estimator.run(qc_example, operator, shots=1000) +from qiskit.primitives import StatevectorEstimator +estimator = StatevectorEstimator() +job = estimator.run([(qc_example, operator)], precision=1e-3) result = job.result() -print(f" > Expectation values: {result.values}") +print(f" > Expectation values: {result[0].data.evs}") ``` Running this will give the outcome `4`. For fun, try to assign a value of +/- 1 to each single-qubit operator X and Y and see if you can achieve this outcome. (Spoiler alert: this is not possible!) -Using the Qiskit-provided `qiskit.primitives.Sampler` and `qiskit.primitives.Estimator` will not take you very far. +Using the Qiskit-provided `qiskit.primitives.StatevectorSampler` and `qiskit.primitives.StatevectorEstimator` will not take you very far. The power of quantum computing cannot be simulated on classical computers and you need to use real quantum hardware to scale to larger quantum circuits. However, running a quantum circuit on hardware requires rewriting to the basis gates and connectivity of the quantum hardware. The tool that does this is the [transpiler](https://docs.quantum.ibm.com/api/qiskit/transpiler), and Qiskit includes transpiler passes for synthesis, optimization, mapping, and scheduling. @@ -106,7 +106,7 @@ qc_transpiled = transpile(qc_example, basis_gates = ['cz', 'sx', 'rz'], coupling ### Executing your code on real quantum hardware Qiskit provides an abstraction layer that lets users run quantum circuits on hardware from any vendor that provides a compatible interface. -The best way to use Qiskit is with a runtime environment that provides optimized implementations of `sampler` and `estimator` for a given hardware platform. This runtime may involve using pre- and post-processing, such as optimized transpiler passes with error suppression, error mitigation, and, eventually, error correction built in. A runtime implements `qiskit.primitives.BaseSampler` and `qiskit.primitives.BaseEstimator` interfaces. For example, +The best way to use Qiskit is with a runtime environment that provides optimized implementations of `sampler` and `estimator` for a given hardware platform. This runtime may involve using pre- and post-processing, such as optimized transpiler passes with error suppression, error mitigation, and, eventually, error correction built in. A runtime implements `qiskit.primitives.BaseSamplerV2` and `qiskit.primitives.BaseEstimatorV2` interfaces. For example, some packages that provide implementations of a runtime primitive implementation are: * https://github.com/Qiskit/qiskit-ibm-runtime @@ -165,4 +165,4 @@ We acknowledge partial support for Qiskit development from the DOE Office of Sci ## License -[Apache License 2.0](LICENSE.txt) \ No newline at end of file +[Apache License 2.0](LICENSE.txt) From 4e5d9f82bf4f5c4d5608e5f8ec37b9f3cf1c17c9 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 26 Nov 2024 20:52:48 +0100 Subject: [PATCH 08/10] Fix parameter handling for `NLocal(..., flatten=True)` and standard gates (#13482) * Use _append_standard_gate directly * update params on cache * Revert "Use _append_standard_gate directly" This reverts commit 9769785c11385485ef2186d7aafa6d149f4d5fa3. * Add test and reno --- crates/circuit/src/circuit_data.rs | 16 +++++++++------- ...x-cached-params-update-4d2814b698fa76b4.yaml | 17 +++++++++++++++++ test/python/circuit/library/test_nlocal.py | 15 +++++++++++++++ test/python/circuit/test_parameters.py | 13 +++++++++++++ 4 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/fix-cached-params-update-4d2814b698fa76b4.yaml diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 0025664e4d94..5422138264b3 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -1361,12 +1361,9 @@ impl CircuitData { let Param::ParameterExpression(expr) = ¶ms[parameter] else { return Err(inconsistent()); }; - params[parameter] = match bind_expr( - expr.bind_borrowed(py), - ¶m_ob, - value.as_ref(), - true, - )? { + let new_param = + bind_expr(expr.bind_borrowed(py), ¶m_ob, value.as_ref(), true)?; + params[parameter] = match new_param.clone_ref(py) { Param::Obj(obj) => { return Err(CircuitError::new_err(format!( "bad type after binding for gate '{}': '{}'", @@ -1382,8 +1379,13 @@ impl CircuitData { #[cfg(feature = "cache_pygates")] { // Standard gates can all rebuild their definitions, so if the - // cached py_op exists, just clear out any existing cache. + // cached py_op exists, update the `params` attribute and clear out + // any existing cache. if let Some(borrowed) = previous.py_op.get() { + borrowed + .bind(py) + .getattr(params_attr)? + .set_item(parameter, new_param)?; borrowed.bind(py).setattr("_definition", py.None())? } } diff --git a/releasenotes/notes/fix-cached-params-update-4d2814b698fa76b4.yaml b/releasenotes/notes/fix-cached-params-update-4d2814b698fa76b4.yaml new file mode 100644 index 000000000000..a4b2b6fe6571 --- /dev/null +++ b/releasenotes/notes/fix-cached-params-update-4d2814b698fa76b4.yaml @@ -0,0 +1,17 @@ +--- +fixes: + - | + Fixed a bug in :meth:`.QuantumCircuit.assign_parameters`, occurring when assigning parameters + to standard gates whose definition has already been triggered. In this case, the new values + were not properly propagated to the gate instances. While the circuit itself was still + compiled as expected, inspecting the individual operations would still show the old parameter. + + For example:: + + from qiskit.circuit.library import EfficientSU2 + + circuit = EfficientSU2(2, flatten=True) + circuit.assign_parameters([1.25] * circuit.num_parameters, inplace=True) + print(circuit.data[0].operation.params) # would print θ[0] instead of 1.25 + + Fixed `#13478 `__. diff --git a/test/python/circuit/library/test_nlocal.py b/test/python/circuit/library/test_nlocal.py index 753feff490b1..22bfa391612d 100644 --- a/test/python/circuit/library/test_nlocal.py +++ b/test/python/circuit/library/test_nlocal.py @@ -488,6 +488,21 @@ def test_initial_state_as_circuit_object(self): self.assertCircuitEqual(ref, expected) + def test_inplace_assignment_with_cache(self): + """Test parameters are correctly re-bound in the cached gates. + + This test requires building with the Rust feature "cache_pygates" enabled, otherwise + it does not test what it is supposed to. + + Regression test of #13478. + """ + qc = EfficientSU2(2, flatten=True) + binds = [1.25] * qc.num_parameters + + qc.assign_parameters(binds, inplace=True) + bound_op = qc.data[0].operation + self.assertAlmostEqual(bound_op.params[0], binds[0]) + @ddt class TestNLocalFunction(QiskitTestCase): diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index e291eb415813..2b342cae1968 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -361,6 +361,19 @@ def test_assign_parameters_by_iterable(self): self.assertEqual(qc.assign_parameters(dict(zip(qc.parameters, binds)).values()), expected) self.assertEqual(qc.assign_parameters(bind for bind in binds), expected) + def test_assign_parameters_with_cache(self): + """Test assigning parameters on a circuit with already triggered cache.""" + x = Parameter("x") + qc = QuantumCircuit(1) + qc.append(RZGate(x), [0]) # add via ``append`` to create a CircuitInstruction + + _ = qc.data[0].operation.definition # trigger building the cache + + binds = [1.2] + qc.assign_parameters(binds, inplace=True) + + self.assertAlmostEqual(binds[0], qc.data[0].operation.params[0]) + def test_bind_parameters_custom_definition_global_phase(self): """Test that a custom gate with a parametrized `global_phase` is assigned correctly.""" x = Parameter("x") From 6025b7c65e7173e9c1b9654a6797e1b8bf8ecddc Mon Sep 17 00:00:00 2001 From: haimeng-zhang <33587226+haimeng-zhang@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:08:20 -0500 Subject: [PATCH 09/10] Fix `InverseCancellation` to run in classical blocks (#13454) * Fix an issue that `InverseCancellation` does not run in classical blocks (#13437) * Add test functions for pass running in classical blocks. * Reformat test function file * Add a release note --- .../optimization/inverse_cancellation.py | 2 + ...inverse-cancellation-c7f4debcde4a705a.yaml | 3 ++ .../transpiler/test_inverse_cancellation.py | 54 +++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 releasenotes/notes/fix-inverse-cancellation-c7f4debcde4a705a.yaml diff --git a/qiskit/transpiler/passes/optimization/inverse_cancellation.py b/qiskit/transpiler/passes/optimization/inverse_cancellation.py index 40876679e8d9..508cfc66946a 100644 --- a/qiskit/transpiler/passes/optimization/inverse_cancellation.py +++ b/qiskit/transpiler/passes/optimization/inverse_cancellation.py @@ -19,6 +19,7 @@ from qiskit.dagcircuit import DAGCircuit from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.passes.utils import control_flow from qiskit._accelerate.inverse_cancellation import inverse_cancellation @@ -74,6 +75,7 @@ def __init__(self, gates_to_cancel: List[Union[Gate, Tuple[Gate, Gate]]]): super().__init__() + @control_flow.trivial_recurse def run(self, dag: DAGCircuit): """Run the InverseCancellation pass on `dag`. diff --git a/releasenotes/notes/fix-inverse-cancellation-c7f4debcde4a705a.yaml b/releasenotes/notes/fix-inverse-cancellation-c7f4debcde4a705a.yaml new file mode 100644 index 000000000000..7d6999677b2b --- /dev/null +++ b/releasenotes/notes/fix-inverse-cancellation-c7f4debcde4a705a.yaml @@ -0,0 +1,3 @@ +fixes: + - | + The transpilation pass :class`.InverseCancellation` now runs inside of flow controlled blocks. Previously, it ignores the pairs of gates in classical blocks that can be cancelled. Refer to `#13437 ` for more details. diff --git a/test/python/transpiler/test_inverse_cancellation.py b/test/python/transpiler/test_inverse_cancellation.py index 64d7812888b3..02072dc32c3c 100644 --- a/test/python/transpiler/test_inverse_cancellation.py +++ b/test/python/transpiler/test_inverse_cancellation.py @@ -21,6 +21,7 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.passes import InverseCancellation from qiskit.transpiler import PassManager +from qiskit.circuit import Clbit, Qubit from qiskit.circuit.library import ( RXGate, HGate, @@ -398,6 +399,59 @@ def test_backwards_pair(self): new_circ = inverse_pass(qc) self.assertEqual(new_circ, QuantumCircuit(1)) + def test_if_else(self): + """Test that the pass recurses in a simple if-else.""" + pass_ = InverseCancellation([CXGate()]) + + inner_test = QuantumCircuit(4, 1) + inner_test.cx(0, 1) + inner_test.cx(0, 1) + inner_test.cx(2, 3) + + inner_expected = QuantumCircuit(4, 1) + inner_expected.cx(2, 3) + + test = QuantumCircuit(4, 1) + test.h(0) + test.measure(0, 0) + test.if_else((0, True), inner_test.copy(), inner_test.copy(), range(4), [0]) + + expected = QuantumCircuit(4, 1) + expected.h(0) + expected.measure(0, 0) + expected.if_else((0, True), inner_expected, inner_expected, range(4), [0]) + + self.assertEqual(pass_(test), expected) + + def test_nested_control_flow(self): + """Test that collection recurses into nested control flow.""" + pass_ = InverseCancellation([CXGate()]) + qubits = [Qubit() for _ in [None] * 4] + clbit = Clbit() + + inner_test = QuantumCircuit(qubits, [clbit]) + inner_test.cx(0, 1) + inner_test.cx(0, 1) + inner_test.cx(2, 3) + + inner_expected = QuantumCircuit(qubits, [clbit]) + inner_expected.cx(2, 3) + + true_body = QuantumCircuit(qubits, [clbit]) + true_body.while_loop((clbit, True), inner_test.copy(), [0, 1, 2, 3], [0]) + + test = QuantumCircuit(qubits, [clbit]) + test.for_loop(range(2), None, inner_test.copy(), [0, 1, 2, 3], [0]) + test.if_else((clbit, True), true_body, None, [0, 1, 2, 3], [0]) + + expected_if_body = QuantumCircuit(qubits, [clbit]) + expected_if_body.while_loop((clbit, True), inner_expected, [0, 1, 2, 3], [0]) + expected = QuantumCircuit(qubits, [clbit]) + expected.for_loop(range(2), None, inner_expected, [0, 1, 2, 3], [0]) + expected.if_else((clbit, True), expected_if_body, None, [0, 1, 2, 3], [0]) + + self.assertEqual(pass_(test), expected) + if __name__ == "__main__": unittest.main() From 6979d918e2250d972b497b09ef7cdd13bbda8829 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Wed, 27 Nov 2024 07:10:09 +0200 Subject: [PATCH 10/10] fixing docstrings for PauliEvolutionGate plugins (#13494) * fixing docstrings for PauliEvolutionGate plugins * fixing link in the docstring * minor tweak * pylint --- qiskit/transpiler/passes/synthesis/hls_plugins.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/hls_plugins.py b/qiskit/transpiler/passes/synthesis/hls_plugins.py index 6696ff2999d9..a609b11f0fef 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -263,11 +263,12 @@ - Targeted connectivity * - ``"rustiq"`` - :class:`~.PauliEvolutionSynthesisRustiq` - - use a diagonalizing Clifford per Pauli term + - use the synthesis method from `Rustiq circuit synthesis library + `_ - all-to-all * - ``"default"`` - :class:`~.PauliEvolutionSynthesisDefault` - - use ``rustiq_core`` synthesis library + - use a diagonalizing Clifford per Pauli term - all-to-all .. autosummary::