From 46accafbfe37dc786b5d5b935b348b86fbf781d9 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 14 Apr 2022 21:28:43 -0400 Subject: [PATCH 01/12] Set sphinx-build --keep-going by default (#7940) * Set sphinx-build --keep-going by default For sphinx builds we set the -W flag to treat warnings as errors. This however, has the unfortunate side-effect of exiting the sphinx build on the first warning and in the typical documentation debugging workflow multiple iterations are required to debug all the issues. This commit adds the --keep-going sphinx-build flag [1] to the default build options. The --keep-going flag will still treat warnings as errors, but not exit the build early. This should hopefully improve the debuggability of the documentation as we'll get a complete picture of what's wrong with a build instead of having to deal with it one warning at a time. [1] https://www.sphinx-doc.org/en/master/man/sphinx-build.html#cmdoption-sphinx-build-keep-going * Add -T for full tracebacks to docs build * Set options on tutorials builds too * Revert tutorials changes The tutorials are actually throwing warnings which are an upstream issue in the qiskit-tutorials repo. Before we can start running with -W on the tutorials job we need to do it on the qiskit-tutorials repo first before we can apply it here. This commit reverts the job configuration change for the tutorials job until that happens. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 219bc954d5f0..85222f4cea3c 100644 --- a/tox.ini +++ b/tox.ini @@ -72,7 +72,7 @@ setenv = deps = -r{toxinidir}/requirements-dev.txt commands = - sphinx-build -W -b html docs/ docs/_build/html {posargs} + sphinx-build -W -T --keep-going -b html docs/ docs/_build/html {posargs} [pycodestyle] max-line-length = 105 From 1c5ed4726c647bbeeedf1508bc3e4c0605656e97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Apr 2022 14:02:46 +0000 Subject: [PATCH 02/12] Bump pyo3 from 0.16.3 to 0.16.4 (#7943) Bumps [pyo3](https://github.com/pyo3/pyo3) from 0.16.3 to 0.16.4. - [Release notes](https://github.com/pyo3/pyo3/releases) - [Changelog](https://github.com/PyO3/pyo3/blob/main/CHANGELOG.md) - [Commits](https://github.com/pyo3/pyo3/compare/v0.16.3...v0.16.4) --- updated-dependencies: - dependency-name: pyo3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b2f3f04889b..2c17fdb3ece0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,9 +298,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3e99c4c3e790e4fc365b42b70c1f7801f42eadc4ea648fa327e6f5ca29f215" +checksum = "cd86513975ed69bf3fb5d4a286cdcda66dbc56f84bdf4832b6c82b459f4417b2" dependencies = [ "cfg-if", "hashbrown", @@ -316,9 +316,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2486b96281859ff0a3929ba6467b13751627b974f7137362db38e2bed14b2094" +checksum = "450e2e56cbfa67bbe224cef93312b7a76d81c471d4e0c459d24d4bfaf3d75b53" dependencies = [ "once_cell", "target-lexicon", @@ -326,9 +326,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd9de1d94557751599f8bd321f10e6c1ef2801067acb58c91138deef2ae83a17" +checksum = "36e653782972eba2fe86e8319ade54b97822c65fb1ccc1e116368372faa6ebc9" dependencies = [ "libc", "pyo3-build-config", @@ -336,9 +336,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9584049129b1cfb615243391a6345c726690271ae195ffd6aa3766177296aa" +checksum = "317ce641f29f4e10e75765630bf4d28b2008612226fcc80b27f334fee8184d0f" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -348,9 +348,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c4717e6a55c51a9958eda1f5481ff7f62cccd21f45309c10e4731cb7198dbc" +checksum = "59342fce58a05983688e8d81209d06f67f0fcb1597253ef63b390b2da2417522" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 89e8b8d15975..436de5479a72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ ahash = "0.7.6" num-complex = "0.4" [dependencies.pyo3] -version = "0.16.3" +version = "0.16.4" features = ["extension-module", "hashbrown", "num-complex"] [dependencies.ndarray] From 618e0ea8b3daca194c1bde97292e57c15eddd762 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 15 Apr 2022 21:30:28 -0400 Subject: [PATCH 03/12] Revert "Workaround Aer bug with subnormal floats in randomised tests (#7719)" (#7946) Since Aer 0.10.4 is now released with corrected handling of subnormal floating-point numbers when compiled with gcc, this commit is no longer necessary. This reverts commit 77219b5c7b7146b1545c5e5190739b36f4064b2f. --- test/randomized/test_synthesis.py | 4 +-- .../randomized/test_transpiler_equivalence.py | 36 +++---------------- 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/test/randomized/test_synthesis.py b/test/randomized/test_synthesis.py index d463d71720e4..84e8ca0b34f1 100644 --- a/test/randomized/test_synthesis.py +++ b/test/randomized/test_synthesis.py @@ -32,9 +32,7 @@ class TestSynthesis(CheckDecompositions): """Test synthesis""" seed = strategies.integers(min_value=0, max_value=2**32 - 1) - # allow_subnormal=False should not need to be specified: remove after Aer PR #1469 is in the - # released version. - rotation = strategies.floats(min_value=-np.pi * 10, max_value=np.pi * 10, allow_subnormal=False) + rotation = strategies.floats(min_value=-np.pi * 10, max_value=np.pi * 10) @given(seed) def test_1q_random(self, seed): diff --git a/test/randomized/test_transpiler_equivalence.py b/test/randomized/test_transpiler_equivalence.py index 749b037fc105..302760279922 100644 --- a/test/randomized/test_transpiler_equivalence.py +++ b/test/randomized/test_transpiler_equivalence.py @@ -148,11 +148,7 @@ def add_3q_gate(self, gate, qargs): gate=st.sampled_from(oneQ_oneP_gates), qarg=qubits, param=st.floats( - allow_nan=False, - allow_infinity=False, - min_value=-10 * pi, - max_value=10 * pi, - allow_subnormal=False, + allow_nan=False, allow_infinity=False, min_value=-10 * pi, max_value=10 * pi ), ) def add_1q1p_gate(self, gate, qarg, param): @@ -163,13 +159,7 @@ def add_1q1p_gate(self, gate, qarg, param): gate=st.sampled_from(oneQ_twoP_gates), qarg=qubits, params=st.lists( - st.floats( - allow_nan=False, - allow_infinity=False, - min_value=-10 * pi, - max_value=10 * pi, - allow_subnormal=False, - ), + st.floats(allow_nan=False, allow_infinity=False, min_value=-10 * pi, max_value=10 * pi), min_size=2, max_size=2, ), @@ -182,13 +172,7 @@ def add_1q2p_gate(self, gate, qarg, params): gate=st.sampled_from(oneQ_threeP_gates), qarg=qubits, params=st.lists( - st.floats( - allow_nan=False, - allow_infinity=False, - min_value=-10 * pi, - max_value=10 * pi, - allow_subnormal=False, - ), + st.floats(allow_nan=False, allow_infinity=False, min_value=-10 * pi, max_value=10 * pi), min_size=3, max_size=3, ), @@ -201,11 +185,7 @@ def add_1q3p_gate(self, gate, qarg, params): gate=st.sampled_from(twoQ_oneP_gates), qargs=st.lists(qubits, max_size=2, min_size=2, unique=True), param=st.floats( - allow_nan=False, - allow_infinity=False, - min_value=-10 * pi, - max_value=10 * pi, - allow_subnormal=False, + allow_nan=False, allow_infinity=False, min_value=-10 * pi, max_value=10 * pi ), ) def add_2q1p_gate(self, gate, qargs, param): @@ -216,13 +196,7 @@ def add_2q1p_gate(self, gate, qargs, param): gate=st.sampled_from(twoQ_threeP_gates), qargs=st.lists(qubits, max_size=2, min_size=2, unique=True), params=st.lists( - st.floats( - allow_nan=False, - allow_infinity=False, - min_value=-10 * pi, - max_value=10 * pi, - allow_subnormal=False, - ), + st.floats(allow_nan=False, allow_infinity=False, min_value=-10 * pi, max_value=10 * pi), min_size=3, max_size=3, ), From ff421fdbaeb303f6c4d8d25565224625a12614ce Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Mon, 18 Apr 2022 19:02:29 +0300 Subject: [PATCH 04/12] Grover class crosslinks note to tutorials and textbook (#7924) * Grover: A note crosslinking to textbook and tutorials * Grover: A note crosslinking to textbook and tutorials * Update qiskit/algorithms/amplitude_amplifiers/grover.py Co-authored-by: Jake Lishman * link * link * Update qiskit/algorithms/amplitude_amplifiers/grover.py Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> * Fix formatting Co-authored-by: Jake Lishman Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> Co-authored-by: Jake Lishman --- qiskit/algorithms/amplitude_amplifiers/grover.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/qiskit/algorithms/amplitude_amplifiers/grover.py b/qiskit/algorithms/amplitude_amplifiers/grover.py index f48962d6de1e..7232fe0e4198 100644 --- a/qiskit/algorithms/amplitude_amplifiers/grover.py +++ b/qiskit/algorithms/amplitude_amplifiers/grover.py @@ -29,6 +29,14 @@ class Grover(AmplitudeAmplifier): r"""Grover's Search algorithm. + .. note:: + + If you want to learn more about the theory behind Grover's Search algorithm, check + out the `Qiskit Textbook `_. + or the `Qiskit Tutorials + `_ + for more concrete how-to examples. + Grover's Search [1, 2] is a well known quantum algorithm that can be used for searching through unstructured collections of records for particular targets with quadratic speedup compared to classical algorithms. @@ -97,7 +105,6 @@ class Grover(AmplitudeAmplifier): [3]: Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). Quantum Amplitude Amplification and Estimation. `arXiv:quant-ph/0005055 `_. - """ def __init__( From ff4ee1bb726d3c721dd960a98d232c61f17f070c Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 18 Apr 2022 16:21:35 -0400 Subject: [PATCH 05/12] Improve transpiler coverage of randomized equivalence test (#7945) * Improve transpiler coverage of randomized equivalence test. Chooses layout, routing, and scheduling methods to use when invoking transpile. This way, preset pass manager behavior with designated transpilation method combinations is covered. To support scheduling method coverage, a filter list is introduced to exclude any fake backends that do not provide the necessary information for instruction scheduling (i.e. gate durations). Secondarily, this test is now configurable via enviroment variables, the intention of which is to ultimately make it easy for transpiler passes living outside of Terra to run equivalence tests in their own CI pipelines to ensure proper Terra integration. For now, this has limited utility, since neither layout, routing, nor scheduling methods can be used with `transpile` unless Terra already knows about them. In the near future, [qiskit-toqm](https://github.com/qiskit-toqm/qiskit-toqm) is a planned integration for Terra, but will be excluded from Terra's randomized equivalence test CI pipeline since it lives externally. To provide coverage, it'll use its own CI pipeline to invoke this test with environment variables that exercise it sufficiently. This should be even more useful in the future, should the transpiler support a more plugin-oriented architecture. The following changes have been made to support the above: The hypothesis profile can now be changed via the built-in hypothesis environment variable, HYPOTHESIS_PROFILE. Note that the named profile must first be registered through some means (e.g. conftest.py). See the following for details: https://hypothesis.readthedocs.io/en/latest/settings.html#settings-profiles This test can now be optionally configured (e.g. by CI) via the following env vars: QISKIT_RANDOMIZED_TEST_LAYOUT_METHODS A space-delimited list of layout method names from which the randomizer should pick the layout method. Defaults to all available built-in methods if unspecified. QISKIT_RANDOMIZED_TEST_ROUTING_METHODS A space-delimited list of routing method names from which the randomizer should pick the routing method. Defaults to all available built-in methods if unspecified. QISKIT_RANDOMIZED_TEST_SCHEDULING_METHODS A space-delimited list of scheduling method names from which the randomizer should pick the scheduling method. Defaults to all available built-in methods if unspecified. QISKIT_RANDOMIZED_TEST_BACKEND_NEEDS_DURATIONS A boolean value (e.g. "true", "Y", etc.) which, when true, forces the randomizer to pick a backend which fully supports scheduling (i.e. has fully specified duration info). Defaults to False. QISKIT_RANDOMIZED_TEST_ALLOW_BARRIERS A boolean value (e.g. "true", "Y", etc.) which, when false, prevents the randomizer from emitting barrier instructions. Defaults to True. * Address review comments. * Address additional review comments. --- .../randomized/test_transpiler_equivalence.py | 240 +++++++++++++++--- 1 file changed, 206 insertions(+), 34 deletions(-) diff --git a/test/randomized/test_transpiler_equivalence.py b/test/randomized/test_transpiler_equivalence.py index 302760279922..3aa2122b46de 100644 --- a/test/randomized/test_transpiler_equivalence.py +++ b/test/randomized/test_transpiler_equivalence.py @@ -11,8 +11,43 @@ # that they have been altered from the originals. # pylint: disable=invalid-name -"""Randomized tests of transpiler circuit equivalence.""" +"""Randomized tests of transpiler circuit equivalence. +This test can be optionally configured (e.g. by CI) via the +following env vars: + +QISKIT_RANDOMIZED_TEST_LAYOUT_METHODS + + A space-delimited list of layout method names from which the + randomizer should pick the layout method. Defaults to all + available built-in methods if unspecified. + +QISKIT_RANDOMIZED_TEST_ROUTING_METHODS + + A space-delimited list of routing method names from which the + randomizer should pick the routing method. Defaults to all + available built-in methods if unspecified. + +QISKIT_RANDOMIZED_TEST_SCHEDULING_METHODS + + A space-delimited list of scheduling method names from which the + randomizer should pick the scheduling method. Defaults to all + available built-in methods if unspecified. + +QISKIT_RANDOMIZED_TEST_BACKEND_NEEDS_DURATIONS + + A boolean value (e.g. "true", "Y", etc.) which, when true, forces + the randomizer to pick a backend which fully supports scheduling + (i.e. has fully specified duration info). Defaults to False. + +QISKIT_RANDOMIZED_TEST_ALLOW_BARRIERS + + A boolean value (e.g. "true", "Y", etc.) which, when false, + prevents the randomizer from emitting barrier instructions. + Defaults to True. +""" + +import os from math import pi from hypothesis import assume, settings, HealthCheck @@ -25,6 +60,9 @@ from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.circuit import Measure, Reset, Gate, Barrier from qiskit.test.mock import ( + FakeProvider, + FakeOpenPulse2Q, + FakeOpenPulse3Q, FakeYorktown, FakeTenerife, FakeOurense, @@ -37,6 +75,19 @@ FakeSingapore, FakeJohannesburg, FakeBoeblingen, + FakeRochester, + FakeBurlington, + FakeCambridge, + FakeCambridgeAlternativeBasis, + FakeEssex, + FakeLondon, + FakeQasmSimulator, + FakeArmonk, + FakeRome, + FakeSantiago, + FakeSydney, + FakeToronto, + FakeValencia, ) from qiskit.test.base import dicts_almost_equal @@ -44,6 +95,17 @@ # pylint: disable=wildcard-import,unused-wildcard-import from qiskit.circuit.library.standard_gates import * +default_profile = "transpiler_equivalence" +settings.register_profile( + default_profile, + report_multiple_bugs=False, + max_examples=25, + deadline=None, + suppress_health_check=[HealthCheck.filter_too_much], +) + +settings.load_profile(os.getenv("HYPOTHESIS_PROFILE", default_profile)) + oneQ_gates = [HGate, IGate, SGate, SdgGate, TGate, TdgGate, XGate, YGate, ZGate, Reset] twoQ_gates = [CXGate, CYGate, CZGate, SwapGate, CHGate] threeQ_gates = [CCXGate, CSwapGate] @@ -58,30 +120,129 @@ oneQ_oneC_gates = [Measure] variadic_gates = [Barrier] -mock_backends = [ - FakeYorktown(), - FakeTenerife(), - FakeOurense(), - FakeVigo(), - FakeMelbourne(), - FakeRueschlikon(), - FakeTokyo(), - FakePoughkeepsie(), - FakeAlmaden(), - FakeSingapore(), - FakeJohannesburg(), - FakeBoeblingen(), -] -# FakeRochester disabled until https://github.com/Qiskit/qiskit-aer/pull/693 is released. +def _strtobool(s): + return s.lower() in ("y", "yes", "t", "true", "on", "1") -@settings( - report_multiple_bugs=False, - max_examples=25, - deadline=None, - suppress_health_check=[HealthCheck.filter_too_much], +if not _strtobool(os.getenv("QISKIT_RANDOMIZED_TEST_ALLOW_BARRIERS", "True")): + variadic_gates.remove(Barrier) + + +def _getenv_list(var_name): + value = os.getenv(var_name) + return None if value is None else value.split() + + +# Note: a value of `None` for any of the following methods means that +# the selected pass manager gets to choose. However, to avoid complexity, +# its not possible to specify `None` when overriding these with environment +# variables. Really, `None` is useful only for testing Terra's pass managers, +# and if you're overriding these, your goal is probably to test a specific +# pass or set of passes instead. +layout_methods = _getenv_list("QISKIT_RANDOMIZED_TEST_LAYOUT_METHODS") or [ + None, + "trivial", + "dense", + "noise_adaptive", + "sabre", +] +routing_methods = _getenv_list("QISKIT_RANDOMIZED_TEST_ROUTING_METHODS") or [ + None, + "basic", + "stochastic", + "lookahead", + "sabre", +] +scheduling_methods = _getenv_list("QISKIT_RANDOMIZED_TEST_SCHEDULING_METHODS") or [ + None, + "alap", + "asap", +] + +backend_needs_durations = _strtobool( + os.getenv("QISKIT_RANDOMIZED_TEST_BACKEND_NEEDS_DURATIONS", "False") ) + + +def _fully_supports_scheduling(backend): + """Checks if backend is not in the set of backends known not to have specified gate durations.""" + return not isinstance( + backend, + ( + # no coupling map + FakeArmonk, + # no measure durations + FakeAlmaden, + FakeBurlington, + FakeCambridge, + FakeCambridgeAlternativeBasis, + FakeEssex, + FakeJohannesburg, + FakeLondon, + FakeOpenPulse2Q, + FakeOpenPulse3Q, + FakePoughkeepsie, + FakeQasmSimulator, + FakeRochester, + FakeRueschlikon, + FakeSingapore, + FakeTenerife, + FakeTokyo, + # No reset duration + FakeAlmaden, + FakeArmonk, + FakeBoeblingen, + FakeBurlington, + FakeCambridge, + FakeCambridgeAlternativeBasis, + FakeEssex, + FakeJohannesburg, + FakeLondon, + FakeMelbourne, + FakeOpenPulse2Q, + FakeOpenPulse3Q, + FakeOurense, + FakePoughkeepsie, + FakeQasmSimulator, + FakeRochester, + FakeRome, + FakeRueschlikon, + FakeSantiago, + FakeSingapore, + FakeSydney, + FakeTenerife, + FakeTokyo, + FakeToronto, + FakeValencia, + FakeVigo, + FakeYorktown, + ), + ) + + +fake_provider = FakeProvider() +mock_backends = fake_provider.backends() +mock_backends_with_scheduling = [b for b in mock_backends if _fully_supports_scheduling(b)] + + +@st.composite +def transpiler_conf(draw): + """Composite search strategy to pick a valid transpiler config.""" + opt_level = draw(st.integers(min_value=0, max_value=3)) + layout_method = draw(st.sampled_from(layout_methods)) + routing_method = draw(st.sampled_from(routing_methods)) + scheduling_method = draw(st.sampled_from(scheduling_methods)) + + compatible_backends = st.one_of(st.none(), st.sampled_from(mock_backends)) + if scheduling_method is not None or backend_needs_durations: + compatible_backends = st.sampled_from(mock_backends_with_scheduling) + + backend = draw(st.one_of(compatible_backends)) + + return (backend, opt_level, layout_method, routing_method, scheduling_method) + + class QCircuitMachine(RuleBasedStateMachine): """Build a Hypothesis rule based state machine for constructing, transpiling and simulating a series of random QuantumCircuits. @@ -104,6 +265,7 @@ class QCircuitMachine(RuleBasedStateMachine): def __init__(self): super().__init__() self.qc = QuantumCircuit() + self.enable_variadic = bool(variadic_gates) @precondition(lambda self: len(self.qc.qubits) < self.max_qubits) @rule(target=qubits, n=st.integers(min_value=1, max_value=max_qubits)) @@ -210,6 +372,7 @@ def add_1q1c_gate(self, gate, qarg, carg): """Append a random 1q, 1c gate.""" self.qc.append(gate(), [qarg], [carg]) + @precondition(lambda self: self.enable_variadic) @rule(gate=st.sampled_from(variadic_gates), qargs=st.lists(qubits, min_size=1, unique=True)) def add_variQ_gate(self, gate, qargs): """Append a gate with a variable number of qargs.""" @@ -237,40 +400,49 @@ def qasm(self): self.qc.qasm() @precondition(lambda self: any(isinstance(d[0], Measure) for d in self.qc.data)) - @rule( - backend=st.one_of(st.none(), st.sampled_from(mock_backends)), - opt_level=st.integers(min_value=0, max_value=3), - ) - def equivalent_transpile(self, backend, opt_level): + @rule(conf=transpiler_conf()) + def equivalent_transpile(self, conf): """Simulate, transpile and simulate the present circuit. Verify that the counts are not significantly different before and after transpilation. """ - - print(f"Evaluating circuit at level {opt_level} on {backend}:\n{self.qc.qasm()}") + backend, opt_level, layout_method, routing_method, scheduling_method = conf assume(backend is None or backend.configuration().n_qubits >= len(self.qc.qubits)) + print( + f"Evaluating circuit at level {opt_level} on {backend} " + f"using layout_method={layout_method} routing_method={routing_method} " + f"and scheduling_method={scheduling_method}:\n{self.qc.qasm()}" + ) + shots = 4096 aer_counts = execute(self.qc, backend=self.backend, shots=shots).result().get_counts() try: - xpiled_qc = transpile(self.qc, backend=backend, optimization_level=opt_level) + xpiled_qc = transpile( + self.qc, + backend=backend, + optimization_level=opt_level, + layout_method=layout_method, + routing_method=routing_method, + scheduling_method=scheduling_method, + ) except Exception as e: failed_qasm = "Exception caught during transpilation of circuit: \n{}".format( self.qc.qasm() ) raise RuntimeError(failed_qasm) from e - xpiled_aer_counts = ( - execute(xpiled_qc, backend=self.backend, shots=shots).result().get_counts() - ) + xpiled_aer_counts = self.backend.run(xpiled_qc, shots=shots).result().get_counts() count_differences = dicts_almost_equal(aer_counts, xpiled_aer_counts, 0.05 * shots) - assert count_differences == "", "Counts not equivalent: {}\nFailing QASM: \n{}".format( - count_differences, self.qc.qasm() + assert ( + count_differences == "" + ), "Counts not equivalent: {}\nFailing QASM Input:\n{}\n\nFailing QASM Output:\n{}".format( + count_differences, self.qc.qasm(), xpiled_qc.qasm() ) From ef997f9ba2c3da0b1018394cd1a2649bdeb6a183 Mon Sep 17 00:00:00 2001 From: upsideon <65986739+upsideon@users.noreply.github.com> Date: Mon, 18 Apr 2022 16:59:18 -0600 Subject: [PATCH 06/12] Ensuring instruction labels are strings when provided to the Instruction constructor. (#7671) * Fixes #7667 by ensuring that instruction labels are strings when provided to the Instruction constructor. * Optimizing speed of instruction label type checking. * Switching to an equality rather than reference check for instruction labels. * Adding a release note for label type checking on creation. * Use Sphinx cross-ref in release note * Fix release note * Add ctrl_state to non-parameter kwargs Co-authored-by: Jake Lishman --- qiskit/circuit/instruction.py | 3 +++ ...ruction-labels-on-creation-8399dd8b5d72f272.yaml | 4 ++++ test/python/circuit/test_extensions_standard.py | 4 ++-- test/python/circuit/test_instructions.py | 13 +++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/type-check-instruction-labels-on-creation-8399dd8b5d72f272.yaml diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index d1bd8e2cf474..ed7ff3f1db3f 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -69,6 +69,7 @@ def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt Raises: CircuitError: when the register is not in the correct format. + TypeError: when the optional label is provided, but it is not a string. """ if not isinstance(num_qubits, int) or not isinstance(num_clbits, int): raise CircuitError("num_qubits and num_clbits must be integer.") @@ -86,6 +87,8 @@ def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt # already set is a temporary work around that can be removed after # the next stable qiskit-aer release if not hasattr(self, "_label"): + if label is not None and not isinstance(label, str): + raise TypeError("label expects a string or None") self._label = label # tuple (ClassicalRegister, int), tuple (Clbit, bool) or tuple (Clbit, int) # when the instruction has a conditional ("if") diff --git a/releasenotes/notes/type-check-instruction-labels-on-creation-8399dd8b5d72f272.yaml b/releasenotes/notes/type-check-instruction-labels-on-creation-8399dd8b5d72f272.yaml new file mode 100644 index 000000000000..a284e46c98cb --- /dev/null +++ b/releasenotes/notes/type-check-instruction-labels-on-creation-8399dd8b5d72f272.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + :class:`~.circuit.Instruction` labels are now type-checked on instruction creation. diff --git a/test/python/circuit/test_extensions_standard.py b/test/python/circuit/test_extensions_standard.py index 152b73e6b90e..a37d2a588105 100644 --- a/test/python/circuit/test_extensions_standard.py +++ b/test/python/circuit/test_extensions_standard.py @@ -1455,7 +1455,7 @@ def test_to_matrix(self): # gate_class is abstract continue sig = signature(gate_class) - free_params = len(set(sig.parameters) - {"label"}) + free_params = len(set(sig.parameters) - {"label", "ctrl_state"}) try: if gate_class == PauliGate: # special case due to PauliGate using string parameters @@ -1508,7 +1508,7 @@ def test_to_matrix_op(self): # n_qubits argument is no longer supported. free_params = 2 else: - free_params = len(set(sig.parameters) - {"label"}) + free_params = len(set(sig.parameters) - {"label", "ctrl_state"}) try: if gate_class == PauliGate: # special case due to PauliGate using string parameters diff --git a/test/python/circuit/test_instructions.py b/test/python/circuit/test_instructions.py index e346e77d453f..ad2b7a99b702 100644 --- a/test/python/circuit/test_instructions.py +++ b/test/python/circuit/test_instructions.py @@ -692,6 +692,19 @@ def dummy_requester(specifier): for instruction in instruction_list: self.assertIs(instruction.condition[0], sentinel_register) + def test_label_type_enforcement(self): + """Test instruction label type enforcement.""" + with self.subTest("accepts string labels"): + instruction = Instruction("h", 1, 0, [], label="label") + self.assertEqual(instruction.label, "label") + with self.subTest("raises when a non-string label is provided to constructor"): + with self.assertRaisesRegex(TypeError, r"label expects a string or None"): + Instruction("h", 1, 0, [], label=0) + with self.subTest("raises when a non-string label is provided to setter"): + with self.assertRaisesRegex(TypeError, r"label expects a string or None"): + instruction = HGate() + instruction.label = 0 + if __name__ == "__main__": unittest.main() From a4edc11877b4f8a3c5596f4e9960d2cbaa7cd07f Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 18 Apr 2022 20:09:42 -0400 Subject: [PATCH 07/12] Update docstrings for renamed scheduling passes (#7884) * Update docstrings for renamed scheduling passes In #7835 we renamed the passes used in the new scheduler pass workflow to differentiate them from the existing legacy scheduling pass workflow (which we restored pending a future deprecation in that PR). However, while we updated the docstring for the legacy path to update them to point to the new passes, we neglected to update the docstrings for the renamed classes to reflect the new names. The examples in the docstrings still worked because of the old passes, but it wasn't showing an example of how to use the new workflow anymore. This commit corrects this oversight so that they actually explain how to use them. * Add scheduling section to top level transpiler doc * Update reference * Fix lint * Fix typos Co-authored-by: Naoki Kanazawa * Apply suggestions from code review Co-authored-by: Kevin Hartman Co-authored-by: Naoki Kanazawa Co-authored-by: Kevin Hartman Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/transpiler/__init__.py | 239 ++++++++++++++++++ .../scheduling/alignments/reschedule.py | 2 +- .../padding/dynamical_decoupling.py | 10 +- .../passes/scheduling/scheduling/alap.py | 4 +- .../passes/scheduling/scheduling/asap.py | 4 +- .../scheduling/scheduling/base_scheduler.py | 180 +------------ 6 files changed, 250 insertions(+), 189 deletions(-) diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 19aafb51a1bc..8a3b61f17bc3 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -43,6 +43,8 @@ these ready-made routines. +.. _transpiler_supplemental: + Supplementary Information ========================= @@ -354,6 +356,243 @@
+.. dropdown:: Scheduling + :animate: fade-in-slide-down + + After the circuit has been translated to the target basis, mapped to the device, and optimized, + a scheduling phase can be applied to optionally account for all the idle time in the circuit. + At a high level the scheduling can be thought of as inserting delays into the circuit to account + for idle time on the qubits between the execution of instructions. For example, if we start with a + circuit such as: + + .. jupyter-execute:: + + from qiskit import QuantumCircuit, transpile + from qiskit.test.mock import FakeBoeblingen + backend = FakeBoeblingen() + + ghz = QuantumCircuit(5) + ghz.h(0) + ghz.cx(0,range(1,5)) + ghz.draw(output='mpl') + + we can then call :func:`~.transpile` on it with ``scheduling_method`` set: + + .. jupyter-execute:: + + circ = transpile(ghz, backend, scheduling_method="asap") + circ.draw(output='mpl') + + You can see here that the transpiler inserted :class:`~qiskit.circuit.Delay` instructions to + account for idle time on each qubit. To get a better idea of the timing of the circuit we can + also look at it with the :func:`.timeline.draw` function: + + .. jupyter-execute:: + + from qiskit.visualization.timeline import draw as timeline_draw + + timeline_draw(circ) + + The scheduling of a circuit involves two parts, analysis and constraint mapping followed by a + padding pass. The first part requires running a scheduling analysis pass such as + :class:`~.ALAPSchedulingAnalysis` or :class:`~.ASAPSchedulingAnalysis` which analyzes the circuit + and records the start time of each instruction in the circuit using a scheduling algorithm ("as late + as possible" for :class:`~.ALAPSchedulingAnalysis` and "as soon as possible" for + :class:`~.ASAPSchedulingAnalysis`) in the property set. Once the circuit has an initial scheduling + additional passes can be run to account for any timing constraints on the target backend, such + as alignment constraints. This is typically done with the + :class:`~.ConstrainedReschedule` pass which will adjust the scheduling + set in the property set to the contraints of the target backend. Once all + the scheduling and adjustments/rescheduling are finished a padding pass, + such as :class:`~.PadDelay` or :class:`~.PadDynamicalDecoupling` is run + to insert the instructions into the circuit, which completes the scheduling. + + Scheduling Anaylsis with control flow instructions: + + When scheduling analysis passes run there are additional constraints on classical conditions + and control flow instructions in a circuit. This section covers the details of these additional + constraints that any scheduling pass will need to account for. + + Policy of topological node ordering in scheduling: + + The DAG representation of ``QuantumCircuit`` respects the node ordering also in the + classical register wires, though theoretically two conditional instructions + conditioned on the same register are commute, i.e. read-access to the + classical register doesn't change its state. + + .. parsed-literal:: + + qc = QuantumCircuit(2, 1) + qc.delay(100, 0) + qc.x(0).c_if(0, True) + qc.x(1).c_if(0, True) + + The scheduler SHOULD comply with above topological ordering policy of the DAG circuit. + Accordingly, the `asap`-scheduled circuit will become + + .. parsed-literal:: + + ┌────────────────┐ ┌───┐ + q_0: ┤ Delay(100[dt]) ├───┤ X ├────────────── + ├────────────────┤ └─╥─┘ ┌───┐ + q_1: ┤ Delay(100[dt]) ├─────╫────────┤ X ├─── + └────────────────┘ ║ └─╥─┘ + ┌────╨────┐┌────╨────┐ + c: 1/══════════════════╡ c_0=0x1 ╞╡ c_0=0x1 ╞ + └─────────┘└─────────┘ + + Note that this scheduling might be inefficient in some cases, + because the second conditional operation can start without waiting the delay of 100 dt. + However, such optimization should be done by another pass, + otherwise scheduling may break topological ordering of the original circuit. + + Realistic control flow scheduling respecting for microarcitecture: + + In the dispersive QND readout scheme, qubit is measured with microwave stimulus to qubit (Q) + followed by resonator ring-down (depopulation). This microwave signal is recorded + in the buffer memory (B) with hardware kernel, then a discriminated (D) binary value + is moved to the classical register (C). + The sequence from t0 to t1 of the measure instruction interval might be modeled as follows: + + .. parsed-literal:: + + Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ + B ░░▒▒▒▒▒▒▒▒░░░░░░░░░ + D ░░░░░░░░░░▒▒▒▒▒▒░░░ + C ░░░░░░░░░░░░░░░░▒▒░ + + However, ``QuantumCircuit`` representation is not enough accurate to represent + this model. In the circuit representation, thus ``Qubit`` is occupied by the + stimulus microwave signal during the first half of the interval, + and ``Clbit`` is only occupied at the very end of the interval. + + This precise model may induce weird edge case. + + .. parsed-literal:: + + ┌───┐ + q_0: ───┤ X ├────── + └─╥─┘ ┌─┐ + q_1: ─────╫─────┤M├ + ┌────╨────┐└╥┘ + c: 1/╡ c_0=0x1 ╞═╩═ + └─────────┘ 0 + + In this example, user may intend to measure the state of ``q_1``, after ``XGate`` is + applied to the ``q_0``. This is correct interpretation from viewpoint of + the topological node ordering, i.e. x gate node come in front of the measure node. + However, according to the measurement model above, the data in the register + is unchanged during the stimulus, thus two nodes are simultaneously operated. + If one `alap`-schedule this circuit, it may return following circuit. + + .. parsed-literal:: + + ┌────────────────┐ ┌───┐ + q_0: ┤ Delay(500[dt]) ├───┤ X ├────── + └────────────────┘ └─╥─┘ ┌─┐ + q_1: ───────────────────────╫─────┤M├ + ┌────╨────┐└╥┘ + c: 1/══════════════════╡ c_0=0x1 ╞═╩═ + └─────────┘ 0 + + Note that there is no delay on ``q_1`` wire, and the measure instruction immediately + start after t=0, while the conditional gate starts after the delay. + It looks like the topological ordering between the nodes are flipped in the scheduled view. + This behavior can be understood by considering the control flow model described above, + + .. parsed-literal:: + + : Quantum Circuit, first-measure + 0 ░░░░░░░░░░░░▒▒▒▒▒▒░ + 1 ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ + + : In wire q0 + Q ░░░░░░░░░░░░░░░▒▒▒░ + C ░░░░░░░░░░░░▒▒░░░░░ + + : In wire q1 + Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ + B ░░▒▒▒▒▒▒▒▒░░░░░░░░░ + D ░░░░░░░░░░▒▒▒▒▒▒░░░ + C ░░░░░░░░░░░░░░░░▒▒░ + + Since there is no qubit register (Q0, Q1) overlap, the node ordering is determined by the + shared classical register C. As you can see, the execution order is still + preserved on C, i.e. read C then apply ``XGate``, finally store the measured outcome in C. + Because ``DAGOpNode`` cannot define different durations for associated registers, + the time ordering of two nodes is inverted anyways. + + This behavior can be controlled by ``clbit_write_latency`` and ``conditional_latency``. + The former parameter determines the delay of the register write-access from + the beginning of the measure instruction t0, and another parameter determines + the delay of conditional gate operation from t0 which comes from the register read-access. + These information might be found in the backend configuration and then should + be copied to the pass manager property set before the pass is called. + + By default latencies, the `alap`-scheduled circuit of above example may become + + .. parsed-literal:: + + ┌───┐ + q_0: ───┤ X ├────── + └─╥─┘ ┌─┐ + q_1: ─────╫─────┤M├ + ┌────╨────┐└╥┘ + c: 1/╡ c_0=0x1 ╞═╩═ + └─────────┘ 0 + + If the backend microarchitecture supports smart scheduling of the control flow, i.e. + it may separately schedule qubit and classical register, + insertion of the delay yields unnecessary longer total execution time. + + .. parsed-literal:: + : Quantum Circuit, first-xgate + 0 ░▒▒▒░░░░░░░░░░░░░░░ + 1 ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ + + : In wire q0 + Q ░▒▒▒░░░░░░░░░░░░░░░ + C ░░░░░░░░░░░░░░░░░░░ (zero latency) + + : In wire q1 + Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ + C ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ (zero latency, scheduled after C0 read-access) + + However this result is much more intuitive in the topological ordering view. + If finite conditional latency is provided, for example, 30 dt, the circuit + is scheduled as follows. + + .. parsed-literal:: + + ┌───────────────┐ ┌───┐ + q_0: ┤ Delay(30[dt]) ├───┤ X ├────── + ├───────────────┤ └─╥─┘ ┌─┐ + q_1: ┤ Delay(30[dt]) ├─────╫─────┤M├ + └───────────────┘┌────╨────┐└╥┘ + c: 1/═════════════════╡ c_0=0x1 ╞═╩═ + └─────────┘ 0 + + with the timing model: + + .. parsed-literal:: + : Quantum Circuit, first-xgate + 0 ░░▒▒▒░░░░░░░░░░░░░░░ + 1 ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ + + : In wire q0 + Q ░░▒▒▒░░░░░░░░░░░░░░░ + C ░▒░░░░░░░░░░░░░░░░░░ (30dt latency) + + : In wire q1 + Q ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ + C ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ + + See https://arxiv.org/abs/2102.01682 for more details. + + .. raw:: html + +
+ Transpiler API ============== diff --git a/qiskit/transpiler/passes/scheduling/alignments/reschedule.py b/qiskit/transpiler/passes/scheduling/alignments/reschedule.py index ab22b0080a9d..af28fed1adb6 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/reschedule.py +++ b/qiskit/transpiler/passes/scheduling/alignments/reschedule.py @@ -25,7 +25,7 @@ class ConstrainedReschedule(AnalysisPass): """Rescheduler pass that updates node start times to conform to the hardware alignments. This pass shifts DAG node start times previously scheduled with one of - the scheduling passes, e.g. :class:`ASAPSchedule` or :class:`ALAPSchedule`, + the scheduling passes, e.g. :class:`ASAPScheduleAnalysis` or :class:`ALAPScheduleAnalysis`, so that every instruction start time satisfies alignment constraints. Examples: diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py index 828fb49f5bcf..d19727d469a1 100644 --- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py @@ -53,7 +53,7 @@ class PadDynamicalDecoupling(BasePadding): from qiskit.circuit import QuantumCircuit from qiskit.circuit.library import XGate from qiskit.transpiler import PassManager, InstructionDurations - from qiskit.transpiler.passes import ALAPSchedule, DynamicalDecoupling + from qiskit.transpiler.passes import ALAPScheduleAnalysis, PadDynamicalDecoupling from qiskit.visualization import timeline_drawer circ = QuantumCircuit(4) circ.h(0) @@ -71,8 +71,8 @@ class PadDynamicalDecoupling(BasePadding): # balanced X-X sequence on all qubits dd_sequence = [XGate(), XGate()] - pm = PassManager([ALAPSchedule(durations), - DynamicalDecoupling(durations, dd_sequence)]) + pm = PassManager([ALAPScheduleAnalysis(durations), + PadDynamicalDecoupling(durations, dd_sequence)]) circ_dd = pm.run(circ) timeline_drawer(circ_dd) @@ -89,8 +89,8 @@ def uhrig_pulse_location(k): spacing.append(1 - sum(spacing)) pm = PassManager( [ - ALAPSchedule(durations), - DynamicalDecoupling(durations, dd_sequence, qubits=[0], spacing=spacing), + ALAPScheduleAnalysis(durations), + PadDynamicalDecoupling(durations, dd_sequence, qubits=[0], spacing=spacing), ] ) circ_dd = pm.run(circ) diff --git a/qiskit/transpiler/passes/scheduling/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/scheduling/alap.py index e9a26b19e49e..9de6b162ec0b 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/alap.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/alap.py @@ -20,8 +20,8 @@ class ALAPScheduleAnalysis(BaseScheduler): """ALAP Scheduling pass, which schedules the **stop** time of instructions as late as possible. - See :class:`~qiskit.transpiler.passes.scheduling.base_scheduler.BaseScheduler` for the - detailed behavior of the control flow operation, i.e. ``c_if``. + See the Scheduling section in :ref:`transpiler_supplemental` for + the detailed behavior of the control flow operation, i.e. ``c_if``. """ def run(self, dag): diff --git a/qiskit/transpiler/passes/scheduling/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/scheduling/asap.py index e4ed656655a2..c37beb4f967e 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/asap.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/asap.py @@ -20,8 +20,8 @@ class ASAPScheduleAnalysis(BaseScheduler): """ASAP Scheduling pass, which schedules the start time of instructions as early as possible.. - See :class:`~qiskit.transpiler.passes.scheduling.base_scheduler.BaseScheduler` for the - detailed behavior of the control flow operation, i.e. ``c_if``. + See Scheduling section in :ref:`transpiler_supplemental` for + the detailed behavior of the control flow operation, i.e. ``c_if``. """ def run(self, dag): diff --git a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py index 5cedbd0e1ae5..8dacf45f1e83 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py @@ -25,185 +25,7 @@ class BaseScheduler(AnalysisPass): - """Base scheduler pass. - - Policy of topological node ordering in scheduling - - The DAG representation of ``QuantumCircuit`` respects the node ordering also in the - classical register wires, though theoretically two conditional instructions - conditioned on the same register are commute, i.e. read-access to the - classical register doesn't change its state. - - .. parsed-literal:: - - qc = QuantumCircuit(2, 1) - qc.delay(100, 0) - qc.x(0).c_if(0, True) - qc.x(1).c_if(0, True) - - The scheduler SHOULD comply with above topological ordering policy of the DAG circuit. - Accordingly, the `asap`-scheduled circuit will become - - .. parsed-literal:: - - ┌────────────────┐ ┌───┐ - q_0: ┤ Delay(100[dt]) ├───┤ X ├────────────── - ├────────────────┤ └─╥─┘ ┌───┐ - q_1: ┤ Delay(100[dt]) ├─────╫────────┤ X ├─── - └────────────────┘ ║ └─╥─┘ - ┌────╨────┐┌────╨────┐ - c: 1/══════════════════╡ c_0=0x1 ╞╡ c_0=0x1 ╞ - └─────────┘└─────────┘ - - Note that this scheduling might be inefficient in some cases, - because the second conditional operation can start without waiting the delay of 100 dt. - However, such optimization should be done by another pass, - otherwise scheduling may break topological ordering of the original circuit. - - Realistic control flow scheduling respecting for microarcitecture - - In the dispersive QND readout scheme, qubit is measured with microwave stimulus to qubit (Q) - followed by resonator ring-down (depopulation). This microwave signal is recorded - in the buffer memory (B) with hardware kernel, then a discriminated (D) binary value - is moved to the classical register (C). - The sequence from t0 to t1 of the measure instruction interval might be modeled as follows: - - .. parsed-literal:: - - Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - B ░░▒▒▒▒▒▒▒▒░░░░░░░░░ - D ░░░░░░░░░░▒▒▒▒▒▒░░░ - C ░░░░░░░░░░░░░░░░▒▒░ - - However, ``QuantumCircuit`` representation is not enough accurate to represent - this model. In the circuit representation, thus ``Qubit`` is occupied by the - stimulus microwave signal during the first half of the interval, - and ``Clbit`` is only occupied at the very end of the interval. - - This precise model may induce weird edge case. - - .. parsed-literal:: - - ┌───┐ - q_0: ───┤ X ├────── - └─╥─┘ ┌─┐ - q_1: ─────╫─────┤M├ - ┌────╨────┐└╥┘ - c: 1/╡ c_0=0x1 ╞═╩═ - └─────────┘ 0 - - In this example, user may intend to measure the state of ``q_1``, after ``XGate`` is - applied to the ``q_0``. This is correct interpretation from viewpoint of - the topological node ordering, i.e. x gate node come in front of the measure node. - However, according to the measurement model above, the data in the register - is unchanged during the stimulus, thus two nodes are simultaneously operated. - If one `alap`-schedule this circuit, it may return following circuit. - - .. parsed-literal:: - - ┌────────────────┐ ┌───┐ - q_0: ┤ Delay(500[dt]) ├───┤ X ├────── - └────────────────┘ └─╥─┘ ┌─┐ - q_1: ───────────────────────╫─────┤M├ - ┌────╨────┐└╥┘ - c: 1/══════════════════╡ c_0=0x1 ╞═╩═ - └─────────┘ 0 - - Note that there is no delay on ``q_1`` wire, and the measure instruction immediately - start after t=0, while the conditional gate starts after the delay. - It looks like the topological ordering between the nodes are flipped in the scheduled view. - This behavior can be understood by considering the control flow model described above, - - .. parsed-literal:: - - : Quantum Circuit, first-measure - 0 ░░░░░░░░░░░░▒▒▒▒▒▒░ - 1 ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - - : In wire q0 - Q ░░░░░░░░░░░░░░░▒▒▒░ - C ░░░░░░░░░░░░▒▒░░░░░ - - : In wire q1 - Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - B ░░▒▒▒▒▒▒▒▒░░░░░░░░░ - D ░░░░░░░░░░▒▒▒▒▒▒░░░ - C ░░░░░░░░░░░░░░░░▒▒░ - - Since there is no qubit register (Q0, Q1) overlap, the node ordering is determined by the - shared classical register C. As you can see, the execution order is still - preserved on C, i.e. read C then apply ``XGate``, finally store the measured outcome in C. - Because ``DAGOpNode`` cannot define different durations for associated registers, - the time ordering of two nodes is inverted anyways. - - This behavior can be controlled by ``clbit_write_latency`` and ``conditional_latency``. - The former parameter determines the delay of the register write-access from - the beginning of the measure instruction t0, and another parameter determines - the delay of conditional gate operation from t0 which comes from the register read-access. - These information might be found in the backend configuration and then should - be copied to the pass manager property set before the pass is called. - - By default latencies, the `alap`-scheduled circuit of above example may become - - .. parsed-literal:: - - ┌───┐ - q_0: ───┤ X ├────── - └─╥─┘ ┌─┐ - q_1: ─────╫─────┤M├ - ┌────╨────┐└╥┘ - c: 1/╡ c_0=0x1 ╞═╩═ - └─────────┘ 0 - - If the backend microarchitecture supports smart scheduling of the control flow, i.e. - it may separately schedule qubit and classical register, - insertion of the delay yields unnecessary longer total execution time. - - .. parsed-literal:: - : Quantum Circuit, first-xgate - 0 ░▒▒▒░░░░░░░░░░░░░░░ - 1 ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - - : In wire q0 - Q ░▒▒▒░░░░░░░░░░░░░░░ - C ░░░░░░░░░░░░░░░░░░░ (zero latency) - - : In wire q1 - Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - C ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ (zero latency, scheduled after C0 read-access) - - However this result is much more intuitive in the topological ordering view. - If finite conditional latency is provided, for example, 30 dt, the circuit - is scheduled as follows. - - .. parsed-literal:: - - ┌───────────────┐ ┌───┐ - q_0: ┤ Delay(30[dt]) ├───┤ X ├────── - ├───────────────┤ └─╥─┘ ┌─┐ - q_1: ┤ Delay(30[dt]) ├─────╫─────┤M├ - └───────────────┘┌────╨────┐└╥┘ - c: 1/═════════════════╡ c_0=0x1 ╞═╩═ - └─────────┘ 0 - - with the timing model: - - .. parsed-literal:: - : Quantum Circuit, first-xgate - 0 ░░▒▒▒░░░░░░░░░░░░░░░ - 1 ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - - : In wire q0 - Q ░░▒▒▒░░░░░░░░░░░░░░░ - C ░▒░░░░░░░░░░░░░░░░░░ (30dt latency) - - : In wire q1 - Q ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - C ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - - See https://arxiv.org/abs/2102.01682 for more details. - - """ + """Base scheduler pass.""" CONDITIONAL_SUPPORTED = (Gate, Delay) From 57f16ab61a4669f7be8c8beb9c5d3ed79ab65e27 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 18 Apr 2022 21:39:41 -0400 Subject: [PATCH 08/12] Store simulator Backend in FakeBackend instance (#7912) * Initialize simulator at FakeBackend inititialization This commit updates the FakeBackend and FakeBackendV2 classes (FakeLegacyBackend is being removed in #7886) to only build a noise model from the stored properties one. This is done by initializing the internal simulator object only once at storing it as an instance attribute. The noise model is then constructed once at object initialization and used to update the backend at the same time. Fixes #7500 Fixes #7911 * Lazy load simulator backend objects and properties This commit fixes another regression introduced in the previous commit around the import and __init__ performance of fake backends. By parsing the properties payload and creating a noise model we significantly slowed down the initialization of fake backend objects. This commit fixes this by ensuring we are lazy loading the noise model and simulator object creation as well as not parsing the properties (or defaults) payloads until we actually need them. * Use qubit properties in target for backendv2 * Filter all noise model warnings Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/test/mock/fake_backend.py | 163 ++++++++++---------- qiskit/test/mock/utils/backend_converter.py | 15 +- 2 files changed, 89 insertions(+), 89 deletions(-) diff --git a/qiskit/test/mock/fake_backend.py b/qiskit/test/mock/fake_backend.py index c31430a5edf0..ad770f2f595b 100644 --- a/qiskit/test/mock/fake_backend.py +++ b/qiskit/test/mock/fake_backend.py @@ -21,11 +21,11 @@ import json import os -from typing import List, Union +from typing import List from qiskit import circuit from qiskit.providers.models import BackendProperties -from qiskit.providers import BackendV2, BackendV1, BaseBackend, QubitProperties +from qiskit.providers import BackendV2, BackendV1, BaseBackend from qiskit import pulse from qiskit.exceptions import QiskitError from qiskit.test.mock import fake_job @@ -34,10 +34,7 @@ decode_backend_properties, decode_pulse_defaults, ) -from qiskit.test.mock.utils.backend_converter import ( - convert_to_target, - qubit_props_from_props, -) +from qiskit.test.mock.utils.backend_converter import convert_to_target from qiskit.utils import optionals as _optionals from qiskit.providers import basicaer from qiskit.transpiler import Target @@ -76,8 +73,8 @@ class FakeBackendV2(BackendV2): def __init__(self): """FakeBackendV2 initializer.""" self._conf_dict = self._get_conf_dict_from_json() - self._props_dict = self._set_props_dict_from_json() - self._defs_dict = self._set_defs_dict_from_json() + self._props_dict = None + self._defs_dict = None super().__init__( provider=None, name=self._conf_dict.get("backend_name"), @@ -85,14 +82,25 @@ def __init__(self): online_date=self._conf_dict.get("online_date"), backend_version=self._conf_dict.get("backend_version"), ) - self._target = convert_to_target( - conf_dict=self._conf_dict, - props_dict=self._props_dict, - defs_dict=self._defs_dict, - ) - self._qubit_properties = qubit_props_from_props(self._props_dict) + self._target = None + self.sim = None + + def _setup_sim(self): + if _optionals.HAS_AER: + from qiskit.providers import aer - def _get_conf_dict_from_json(self) -> dict: + self.sim = aer.AerSimulator() + if self._props_dict: + noise_model = self._get_noise_model_from_backend_v2() + self.sim.set_options(noise_model=noise_model) + # Update fake backend default too to avoid overwriting + # it when run() is called + self.set_options(noise_model=noise_model) + + else: + self.sim = basicaer.QasmSimulatorPy() + + def _get_conf_dict_from_json(self): if not self.conf_filename: return None conf_dict = self._load_json(self.conf_filename) @@ -100,19 +108,17 @@ def _get_conf_dict_from_json(self) -> dict: conf_dict["backend_name"] = self.backend_name return conf_dict - def _set_props_dict_from_json(self) -> dict: - if not self.props_filename: - return None - props_dict = self._load_json(self.props_filename) - decode_backend_properties(props_dict) - return props_dict + def _set_props_dict_from_json(self): + if self.props_filename: + props_dict = self._load_json(self.props_filename) + decode_backend_properties(props_dict) + self._props_dict = props_dict - def _set_defs_dict_from_json(self) -> dict: - if not self.defs_filename: - return None - defs_dict = self._load_json(self.defs_filename) - decode_pulse_defaults(defs_dict) - return defs_dict + def _set_defs_dict_from_json(self): + if self.defs_filename: + defs_dict = self._load_json(self.defs_filename) + decode_pulse_defaults(defs_dict) + self._defs_dict = defs_dict def _load_json(self, filename: str) -> dict: with open(os.path.join(self.dirname, filename)) as f_json: @@ -125,6 +131,18 @@ def target(self) -> Target: :rtype: Target """ + if self._target is None: + self._get_conf_dict_from_json() + if self._props_dict is None: + self._set_props_dict_from_json() + if self._defs_dict is None: + self._set_defs_dict_from_json() + self._target = convert_to_target( + conf_dict=self._conf_dict, + props_dict=self._props_dict, + defs_dict=self._defs_dict, + ) + return self._target @property @@ -147,7 +165,7 @@ def _default_options(cls): if _optionals.HAS_AER: from qiskit.providers import aer - return aer.QasmSimulator._default_options() + return aer.AerSimulator._default_options() else: return basicaer.QasmSimulatorPy._default_options() @@ -171,28 +189,6 @@ def meas_map(self) -> List[List[int]]: """ return self._conf_dict.get("meas_map") - def qubit_properties( - self, qubit: Union[int, List[int]] - ) -> Union[QubitProperties, List[QubitProperties]]: - """Return QubitProperties for a given qubit. - Args: - qubit: The qubit to get the - :class:`~qiskit.provider.QubitProperties` object for. This can - be a single integer for 1 qubit or a list of qubits and a list - of :class:`~qiskit.provider.QubitProperties` objects will be - returned in the same order - Returns: - qubit_properties: The :class:`~qiskit.provider.QubitProperties` - object for the specified qubit. If a list of qubits is provided a - list will be returned. If properties are missing for a qubit this - can be ``None``. - """ - if isinstance(qubit, int): # type: ignore[unreachable] - return self._qubit_properties.get(qubit) - if isinstance(qubit, List): - return [self._qubit_properties.get(q) for q in qubit] - return None - def run(self, run_input, **options): """Run on the fake backend using a simulator. @@ -243,22 +239,14 @@ def run(self, run_input, **options): "QuantumCircuit, Schedule, or a list of either" % circuits ) if pulse_job: # pulse job - raise QiskitError("Pulse simulation is currently not supported for V2 backends.") + raise QiskitError("Pulse simulation is currently not supported for V2 fake backends.") # circuit job - if _optionals.HAS_AER: - from qiskit.providers import aer - - sim = aer.Aer.get_backend("qasm_simulator") - sim._options = self._options - if self._props_dict: - noise_model = self._get_noise_model_from_backend_v2() - job = sim.run(circuits, noise_model=noise_model, **options) - else: # simulate without noise - job = sim.run(circuits, **options) - else: + if not _optionals.HAS_AER: warnings.warn("Aer not found using BasicAer and no noise", RuntimeWarning) - sim = basicaer.BasicAer.get_backend("qasm_simulator") - job = sim.run(circuits, **options) + if self.sim is None: + self._setup_sim() + self.sim._options = self._options + job = self.sim.run(circuits, **options) return job def _get_noise_model_from_backend_v2( @@ -287,6 +275,9 @@ def _get_noise_model_from_backend_v2( ) from qiskit.providers.aer.noise.passes import RelaxationNoisePass + if self._props_dict is None: + self._set_props_dict_from_json() + properties = BackendProperties.from_dict(self._props_dict) basis_gates = self.operation_names num_qubits = self.num_qubits @@ -303,7 +294,6 @@ def _get_noise_model_from_backend_v2( with warnings.catch_warnings(): warnings.filterwarnings( "ignore", - category=DeprecationWarning, module="qiskit.providers.aer.noise.device.models", ) gate_errors = basic_device_gate_errors( @@ -357,6 +347,22 @@ def __init__(self, configuration, time_alive=10): super().__init__(configuration) self.time_alive = time_alive self._credentials = _Credentials() + self.sim = None + + def _setup_sim(self): + if _optionals.HAS_AER: + from qiskit.providers import aer + from qiskit.providers.aer.noise import NoiseModel + + self.sim = aer.AerSimulator() + if self.properties(): + noise_model = NoiseModel.from_backend(self, warnings=False) + self.sim.set_options(noise_model=noise_model) + # Update fake backend default options too to avoid overwriting + # it when run() is called + self.set_options(noise_model=noise_model) + else: + self.sim = basicaer.QasmSimulatorPy() def properties(self): """Return backend properties""" @@ -436,30 +442,23 @@ def run(self, run_input, **kwargs): "Invalid input object %s, must be either a " "QuantumCircuit, Schedule, or a list of either" % circuits ) - if _optionals.HAS_AER: - from qiskit.providers import aer - - if pulse_job: + if pulse_job: + if _optionals.HAS_AER: + from qiskit.providers import aer from qiskit.providers.aer.pulse import PulseSystemModel system_model = PulseSystemModel.from_backend(self) sim = aer.Aer.get_backend("pulse_simulator") job = sim.run(circuits, system_model=system_model, **kwargs) else: - sim = aer.Aer.get_backend("qasm_simulator") - if self.properties(): - from qiskit.providers.aer.noise import NoiseModel - - noise_model = NoiseModel.from_backend(self, warnings=False) - job = sim.run(circuits, noise_model=noise_model, **kwargs) - else: - job = sim.run(circuits, **kwargs) - else: - if pulse_job: raise QiskitError("Unable to run pulse schedules without qiskit-aer installed") - warnings.warn("Aer not found using BasicAer and no noise", RuntimeWarning) - sim = basicaer.BasicAer.get_backend("qasm_simulator") - job = sim.run(circuits, **kwargs) + else: + if self.sim is None: + self._setup_sim() + if not _optionals.HAS_AER: + warnings.warn("Aer not found using BasicAer and no noise", RuntimeWarning) + self.sim._options = self._options + job = self.sim.run(circuits, **kwargs) return job diff --git a/qiskit/test/mock/utils/backend_converter.py b/qiskit/test/mock/utils/backend_converter.py index db5cfb636e46..3a094ff0f854 100644 --- a/qiskit/test/mock/utils/backend_converter.py +++ b/qiskit/test/mock/utils/backend_converter.py @@ -39,7 +39,10 @@ def convert_to_target(conf_dict: dict, props_dict: dict = None, defs_dict: dict "reset": Reset(), } custom_gates = {} - target = Target() + qubit_props = None + if props_dict: + qubit_props = qubit_props_from_props(props_dict) + target = Target(qubit_properties=qubit_props) # Parse from properties if it exsits if props_dict is not None: # Parse instructions @@ -123,12 +126,11 @@ def convert_to_target(conf_dict: dict, props_dict: dict = None, defs_dict: dict return target -def qubit_props_from_props(properties: dict) -> dict: +def qubit_props_from_props(properties: dict) -> list: """Returns a dictionary of `qiskit.providers.backend.QubitProperties` using a backend properties dictionary created by loading props.json payload. """ - count = 0 - qubit_props_dict = {} + qubit_props = [] for qubit in properties["qubits"]: qubit_properties = {} for prop_dict in qubit: @@ -138,6 +140,5 @@ def qubit_props_from_props(properties: dict) -> dict: qubit_properties["t2"] = apply_prefix(prop_dict["value"], prop_dict["unit"]) elif prop_dict["name"] == "frequency": qubit_properties["frequency"] = apply_prefix(prop_dict["value"], prop_dict["unit"]) - qubit_props_dict[count] = QubitProperties(**qubit_properties) - count += 1 - return qubit_props_dict + qubit_props.append(QubitProperties(**qubit_properties)) + return qubit_props From aee0dc4d538991560f212411db92cde5f511f65b Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Tue, 19 Apr 2022 18:02:26 +0300 Subject: [PATCH 09/12] Add some explanation and example to circuit library documentation (#7354) * Add some explanation and example to circuit library * remove BooleanExpression * missing gates * standard and directives * title mod * generalized gates * boolean logic circuits * Basis Change Circuits and Arithmetic Circuits * section Amplitude Functions is duplicated. (maybe a bad merge in #5142?) * N-local and feature map * dup entry * Template circuits * ups * line too long * address https://github.com/Qiskit/qiskit-terra/pull/7354#discussion_r792019550 * addressing https://github.com/Qiskit/qiskit-terra/pull/7354#discussion_r792020166 * addressing https://github.com/Qiskit/qiskit-terra/pull/7354#discussion_r792047022 * Update qiskit/circuit/library/__init__.py Co-authored-by: Jake Lishman * import to address https://github.com/Qiskit/qiskit-terra/pull/7354#discussion_r792049584 * Update qiskit/circuit/library/__init__.py Co-authored-by: Jake Lishman * Update qiskit/circuit/library/__init__.py Co-authored-by: Jake Lishman * addressing https://github.com/Qiskit/qiskit-terra/pull/7354#discussion_r792078771 * Update qiskit/circuit/library/__init__.py Co-authored-by: Matthew Treinish * Update qiskit/circuit/library/__init__.py Co-authored-by: Matthew Treinish * Update qiskit/circuit/library/__init__.py Co-authored-by: Matthew Treinish * Update qiskit/circuit/library/__init__.py Co-authored-by: Matthew Treinish * Update qiskit/circuit/library/__init__.py Co-authored-by: Matthew Treinish * Apply suggestions from code review Co-authored-by: Matthew Treinish * directives * pretty colors * undo changes in standard_gates/__init__.py * operations * Update qiskit/circuit/library/__init__.py Co-authored-by: Matthew Treinish * Update qiskit/circuit/library/__init__.py Co-authored-by: Julien Gacon * Apply suggestions from code review Co-authored-by: Julien Gacon * Apply suggestions from code review Co-authored-by: Julien Gacon * example * Update qiskit/circuit/library/__init__.py Co-authored-by: Julien Gacon * Fix Sphinx errors * Fix duplicate definitions and indentation * Fix typo Co-authored-by: Jake Lishman Co-authored-by: Matthew Treinish Co-authored-by: Julien Gacon Co-authored-by: Jake Lishman Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/circuit/__init__.py | 3 - qiskit/circuit/library/__init__.py | 155 ++++++++++++++++++++++++----- 2 files changed, 132 insertions(+), 26 deletions(-) diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 140415e7988f..8b7d27f5090e 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -187,9 +187,6 @@ Gate ControlledGate Delay - Barrier - Measure - Reset Instruction InstructionSet EquivalenceLibrary diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 1c3253acb209..bffe75968e1e 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -17,9 +17,45 @@ .. currentmodule:: qiskit.circuit.library -Standard Gates +The circuit library is a collection of well-studied and valuable circuits, directives, and gates. +We call them valuable for different reasons, for instance they can serve as building blocks for +algorithms or they are circuits that we think are hard to simulate classically. + +Each element can be plugged into a circuit using the :meth:`.QuantumCircuit.append` +method and so the circuit library allows users to program at higher levels of abstraction. +For example, to append a multi-controlled CNOT: + +.. jupyter-execute:: + + from qiskit.circuit.library import MCXGate + gate = MCXGate(4) + + from qiskit import QuantumCircuit + circuit = QuantumCircuit(5) + circuit.append(gate, [0, 1, 4, 2, 3]) + circuit.draw('mpl') + +The library is organized in several sections. + +Standard gates ============== +These operations are reversible unitary gates and they all subclass +:class:`~qiskit.circuit.Gate`. As a consequence, they all have the methods +:meth:`~qiskit.circuit.Gate.to_matrix`, :meth:`~qiskit.circuit.Gate.power`, +and :meth:`~qiskit.circuit.Gate.control`, which we can generally only apply to unitary operations. + +For example: + +.. jupyter-execute:: + + from qiskit.circuit.library import XGate + gate = XGate() + print(gate.to_matrix()) # X gate + print(gate.power(1/2).to_matrix()) # √X gate + print(gate.control(1).to_matrix()) # CX (controlled X) gate + + .. autosummary:: :toctree: ../stubs/ :template: autosummary/class_no_inherited_members.rst @@ -44,11 +80,7 @@ CZGate HGate IGate - MCPhaseGate - MCXGate - MCXGrayCode - MCXRecursive - MCXVChain + MSGate PhaseGate RCCXGate RC3XGate @@ -86,15 +118,44 @@ This summary table deliberately does not generate toctree entries; these directives are "owned" by ``qiskit.circuit``. +Directives are operations to the quantum stack that are meant to be interpreted by the backend or +the transpiler. In general, the transpiler or backend might optionally ignore them if there is no +implementation for them. + .. autosummary:: + :toctree: ../stubs/ + + Barrier - ~qiskit.circuit.Barrier - ~qiskit.circuit.Measure - ~qiskit.circuit.Reset +Standard Operations +=================== + +Operations are non-reversible changes in the quantum state of the circuit. + +.. autosummary:: + :toctree: ../stubs/ + + Measure + Reset Generalized Gates ================= +These "gates" (many are :class:`~qiskit.circuit.QuantumCircuit` subclasses) allow to +set the amount of qubits involved at instantiation time. + + +.. jupyter-execute:: + + from qiskit.circuit.library import Diagonal + + diagonal = Diagonal([1, 1]) + print(diagonal.num_qubits) + + diagonal = Diagonal([1, 1, 1, 1]) + print(diagonal.num_qubits) + + .. autosummary:: :toctree: ../stubs/ :template: autosummary/class_no_inherited_members.rst @@ -104,11 +165,15 @@ MCMTVChain Permutation GMS - MSGate GR GRX GRY GRZ + MCPhaseGate + MCXGate + MCXGrayCode + MCXRecursive + MCXVChain RVGate PauliGate LinearFunction @@ -116,6 +181,11 @@ Boolean Logic Circuits ====================== +These are :class:`~qiskit.circuit.QuantumCircuit` subclasses +that implement boolean logic operations, such as the logical +or of a set of qubit states. + + .. autosummary:: :toctree: ../stubs/ :template: autosummary/class_no_inherited_members.rst @@ -128,6 +198,10 @@ Basis Change Circuits ===================== +These circuits allow basis transformations of the qubit states. For example, +in the case of the Quantum Fourier Transform (QFT), it transforms between +the computational basis and the Fourier basis. + .. autosummary:: :toctree: ../stubs/ :template: autosummary/class_no_inherited_members.rst @@ -137,6 +211,9 @@ Arithmetic Circuits =================== +These :class:`~qiskit.circuit.QuantumCircuit`\\ s perform classical arithmetic, +such as addition or multiplication. + Amplitude Functions ------------------- @@ -209,15 +286,6 @@ ExactReciprocal -Amplitude Functions -=================== - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - LinearAmplitudeFunction - Particular Quantum Circuits =========================== @@ -240,6 +308,10 @@ N-local circuits ================ +These :class:`~qiskit.circuit.library.BlueprintCircuit` subclasses are used +as parameterized models (a.k.a. ansatzes or variational forms) in variational algorithms. +They are heavily used in near-term algorithms in e.g. Chemistry, Physics or Optimization. + .. autosummary:: :toctree: ../stubs/ :template: autosummary/class_no_inherited_members.rst @@ -256,6 +328,9 @@ Data encoding circuits ====================== +These :class:`~qiskit.circuit.library.BlueprintCircuit` encode classical +data in quantum states and are used as feature maps for classification. + .. autosummary:: :toctree: ../stubs/ :template: autosummary/class_no_inherited_members.rst @@ -265,8 +340,38 @@ ZZFeatureMap StatePreparation +Template circuits +================= + +Templates are functions that return circuits that compute the identity. They are used at +circuit optimization where matching part of the template allows the compiler +to replace the match with the inverse of the remainder from the template. + +In this example, the identity constant in a template is checked: + +.. jupyter-execute:: + + from qiskit.circuit.library.templates import template_nct_4b_1 + from qiskit.quantum_info import Operator + import numpy as np + + template = template_nct_4b_1() + + identity = np.identity(2 ** len(template.qubits), dtype=complex) + data = Operator(template).data + np.allclose(data, identity) # True, template_nct_4b_1 is the identity + NCT (Not-CNOT-Toffoli) template circuits -======================================== +---------------------------------------- + +Template circuits for :class:`~qiskit.circuit.library.XGate`, +:class:`~qiskit.circuit.library.CXGate`, +and :class:`~qiskit.circuit.library.CCXGate` (Toffoli) gates. + +**Reference:** +Maslov, D. and Dueck, G. W. and Miller, D. M., +Techniques for the synthesis of reversible Toffoli networks, 2007 +http://dx.doi.org/10.1145/1278349.1278355 .. autosummary:: :toctree: ../stubs/ @@ -295,7 +400,6 @@ templates.nct.template_nct_7c_1 templates.nct.template_nct_7d_1 templates.nct.template_nct_7e_1 - templates.nct.template_nct_2a_1 templates.nct.template_nct_9a_1 templates.nct.template_nct_9c_1 templates.nct.template_nct_9c_2 @@ -321,7 +425,9 @@ templates.nct.template_nct_9d_10 Clifford template circuits -========================== +-------------------------- + +Template circuits over Clifford gates. .. autosummary:: :toctree: ../stubs/ @@ -346,7 +452,9 @@ clifford_8_3 RZXGate template circuits -========================= +------------------------- + +Template circuits with :class:`~qiskit.circuit.library.RZXGate`. .. autosummary:: :toctree: ../stubs/ @@ -366,6 +474,7 @@ from ..measure import Measure from ..reset import Reset + from .blueprintcircuit import BlueprintCircuit from .generalized_gates import ( Diagonal, From 5012ee196495a183ac934cbcc697c2b374ad42cb Mon Sep 17 00:00:00 2001 From: "Kevin J. Sung" Date: Tue, 19 Apr 2022 13:58:47 -0400 Subject: [PATCH 10/12] Fix XXPlusYYGate documentation (#7918) * fix XX+YY gate doc * fix doc and add test of exponential formula * lint test * add note that gate definition will change in next Qiskit version * fix qubit order * Add issue release note Co-authored-by: Jake Lishman --- .../library/standard_gates/xx_plus_yy.py | 31 ++++++++++++++----- .../notes/xxplusyy-doc-c6ddcc45044dcdcd.yaml | 8 +++++ .../circuit/test_extensions_standard.py | 18 +++++++++-- 3 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/xxplusyy-doc-c6ddcc45044dcdcd.yaml diff --git a/qiskit/circuit/library/standard_gates/xx_plus_yy.py b/qiskit/circuit/library/standard_gates/xx_plus_yy.py index 1cce49a2092b..46aba193d8be 100644 --- a/qiskit/circuit/library/standard_gates/xx_plus_yy.py +++ b/qiskit/circuit/library/standard_gates/xx_plus_yy.py @@ -42,7 +42,7 @@ class XXPlusYYGate(Gate): \newcommand{\th}{\frac{\theta}{2}} R_{XX+YY}(\theta, \beta)\ q_0, q_1 = - RZ_1(\beta) \cdot \exp\left(-i \frac{\theta}{2} \frac{XX+YY}{2}\right) \cdot RZ_1(-\beta) = + RZ_1(-\beta) \cdot \exp\left(i \frac{\theta}{2} \frac{XX+YY}{2}\right) \cdot RZ_1(\beta) = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\left(\th\right) & i\sin\left(\th\right)e^{i\beta} & 0 \\ @@ -67,17 +67,34 @@ class XXPlusYYGate(Gate): q_1: ┤0 ├ └───────────────┘ + .. math:: + + \newcommand{\th}{\frac{\theta}{2}} + + R_{XX+YY}(\theta, \beta)\ q_1, q_0 = + RZ_0(-\beta) \cdot \exp\left(i \frac{\theta}{2} \frac{XX+YY}{2}\right) \cdot RZ_0(\beta) = + \begin{pmatrix} + 1 & 0 & 0 & 0 \\ + 0 & \cos\left(\th\right) & i\sin\left(\th\right)e^{-i\beta} & 0 \\ + 0 & i\sin\left(\th\right)e^{i\beta} & \cos\left(\th\right) & 0 \\ + 0 & 0 & 0 & 1 + \end{pmatrix} + + .. note:: + + In Qiskit 0.21, the definition of this gate will be changed to + .. math:: \newcommand{\th}{\frac{\theta}{2}} - R_{XX+YY}(\theta, \beta)\ q_1, q_0 = - RZ_0(\beta) \cdot exp(-i \frac{\theta}{2} \frac{XX+YY}{2}) \cdot RZ_0(-\beta) = + R_{XX+YY}(\theta, \beta)\ q_0, q_1 = + RZ_0(-\beta) \cdot \exp\left(-i \frac{\theta}{2} \frac{XX+YY}{2}\right) \cdot RZ_0(\beta) = \begin{pmatrix} - 1 & 0 & 0 & 0 \\ - 0 & \cos(\th) & i\sin(\th)e^{-i\beta} & 0 \\ - 0 & i\sin(\th)e^{i\beta} & \cos(\th) & 0 \\ - 0 & 0 & 0 & 1 + 1 & 0 & 0 & 0 \\ + 0 & \cos\left(\th\right) & -i\sin\left(\th\right)e^{-i\beta} & 0 \\ + 0 & -i\sin\left(\th\right)e^{i\beta} & \cos\left(\th\right) & 0 \\ + 0 & 0 & 0 & 1 \end{pmatrix} """ diff --git a/releasenotes/notes/xxplusyy-doc-c6ddcc45044dcdcd.yaml b/releasenotes/notes/xxplusyy-doc-c6ddcc45044dcdcd.yaml new file mode 100644 index 000000000000..8fc14cd9287e --- /dev/null +++ b/releasenotes/notes/xxplusyy-doc-c6ddcc45044dcdcd.yaml @@ -0,0 +1,8 @@ +--- +issues: + - | + Since its original introduction in Qiskit Terra 0.20, :class:`.XXPlusYYGate` + has used a negative angle convention compared to all other rotation gates. + In Qiskit Terra 0.21, this will be corrected to be brought in line with the + other rotation gates. This does not affect any other rotation gates, nor + :class:`.XXMinusYYGate`. diff --git a/test/python/circuit/test_extensions_standard.py b/test/python/circuit/test_extensions_standard.py index a37d2a588105..38e89b0d6519 100644 --- a/test/python/circuit/test_extensions_standard.py +++ b/test/python/circuit/test_extensions_standard.py @@ -33,6 +33,7 @@ CU1Gate, CU3Gate, XXMinusYYGate, + XXPlusYYGate, RZGate, XGate, YGate, @@ -1387,8 +1388,6 @@ def test_xx_minus_yy_matrix(self, theta: float, beta: float, expected: np.ndarra def test_xx_minus_yy_exponential_formula(self): """Test XX-YY exponential formula.""" theta, beta = np.random.uniform(-10, 10, size=2) - theta = np.pi / 2 - beta = 0.0 gate = XXMinusYYGate(theta, beta) x = np.array(XGate()) y = np.array(YGate()) @@ -1401,6 +1400,21 @@ def test_xx_minus_yy_exponential_formula(self): atol=1e-7, ) + def test_xx_plus_yy_exponential_formula(self): + """Test XX+YY exponential formula.""" + theta, beta = np.random.uniform(-10, 10, size=2) + gate = XXPlusYYGate(theta, beta) + x = np.array(XGate()) + y = np.array(YGate()) + xx = np.kron(x, x) + yy = np.kron(y, y) + rz1 = np.kron(np.array(RZGate(beta)), np.eye(2)) + np.testing.assert_allclose( + np.array(gate), + rz1.T.conj() @ expm(0.25j * theta * (xx + yy)) @ rz1, + atol=1e-7, + ) + class TestStandard3Q(QiskitTestCase): """Standard Extension Test. Gates with three Qubits""" From 38f3705d5f2789adc6fc3ff19bb7cb281f69cb85 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 19 Apr 2022 15:57:41 -0400 Subject: [PATCH 11/12] Make SabreSwap account for clbits in predecessors (#7952) * Make SabreSwap account for clbits in predecessors Previously, SabreSwap only checked that it was valid to add a gate back to the DAG by ensuring that all its qubit predecessors had already been added to the DAG. It did not check for clbit successors. This meant that measurements and (potentially, unverified) classically conditioned gates could be re-ordered out from the correct topological ordering. The most notable effect of this was that measurements writing to the same bit could be re-ordered, changing the output of the circuit. See gh-7950 for more detail. * Add reno * Fix lint * Trim fat in SabreSwap._successors Co-authored-by: Matthew Treinish * Fix _successors method Co-authored-by: Matthew Treinish --- .../transpiler/passes/routing/sabre_swap.py | 14 +++++---- ...fix-sabreswap-clbits-428eb5f3a46063da.yaml | 9 ++++++ test/python/transpiler/test_sabre_swap.py | 29 +++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/fix-sabreswap-clbits-428eb5f3a46063da.yaml diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 3690c763f734..5e44b29e5d13 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -18,7 +18,6 @@ import numpy as np from qiskit.circuit.library.standard_gates import SwapGate -from qiskit.circuit.quantumregister import Qubit from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout @@ -276,15 +275,18 @@ def _reset_qubits_decay(self): self.qubits_decay = {k: 1 for k in self.qubits_decay.keys()} def _successors(self, node, dag): - for _, successor, edge_data in dag.edges(node): - if not isinstance(successor, DAGOpNode): - continue - if isinstance(edge_data, Qubit): + """Return an iterable of the successors along each wire from the given node. + + This yields the same successor multiple times if there are parallel wires (e.g. two adjacent + operations that have one clbit and qubit in common), which is important in the swapping + algorithm for detecting if each wire has been accounted for.""" + for _, successor, _ in dag.edges(node): + if isinstance(successor, DAGOpNode): yield successor def _is_resolved(self, node): """Return True if all of a node's predecessors in dag are applied.""" - return self.applied_predecessors[node] == len(node.qargs) + return self.applied_predecessors[node] == len(node.qargs) + len(node.cargs) def _obtain_extended_set(self, dag, front_layer): """Populate extended_set by looking ahead a fixed number of gates. diff --git a/releasenotes/notes/fix-sabreswap-clbits-428eb5f3a46063da.yaml b/releasenotes/notes/fix-sabreswap-clbits-428eb5f3a46063da.yaml new file mode 100644 index 000000000000..0401e5a36f74 --- /dev/null +++ b/releasenotes/notes/fix-sabreswap-clbits-428eb5f3a46063da.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixed :class:`.SabreSwap`, and by extension :func:`.transpile` with + ``optimization_level=3``, occasionally re-ordering measurements invalidly. + Previously, if two measurements wrote to the same classical bit, + :class:`.SabreSwap` could (depending on the coupling map) re-order them to + produce a non-equivalent circuit. This behaviour was stochastic, so may + not have appeared reliably. diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index a19f574a305d..509225f65e75 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -13,6 +13,7 @@ """Test the Sabre Swap pass""" import unittest +from qiskit.circuit.library import CCXGate, HGate, Measure from qiskit.transpiler.passes import SabreSwap from qiskit.transpiler import CouplingMap, PassManager from qiskit import QuantumRegister, QuantumCircuit @@ -95,6 +96,34 @@ def test_do_not_change_cm(self): self.assertEqual(set(cm_edges), set(coupling.get_edges())) + def test_do_not_reorder_measurements(self): + """Test that SabreSwap doesn't reorder measurements to the same classical bit. + + With the particular coupling map used in this test and the 3q ccx gate, the routing would + invariably the measurements if the classical successors are not accurately tracked. + Regression test of gh-7950.""" + coupling = CouplingMap([(0, 2), (2, 0), (1, 2), (2, 1)]) + qc = QuantumCircuit(3, 1) + qc.compose(CCXGate().definition, [0, 1, 2], []) # Unroll CCX to 2q operations. + qc.h(0) + qc.barrier() + qc.measure(0, 0) # This measure is 50/50 between the Z states. + qc.measure(1, 0) # This measure always overwrites with 0. + passmanager = PassManager(SabreSwap(coupling)) + transpiled = passmanager.run(qc) + + last_h, h_qubits, _ = transpiled.data[-4] + self.assertIsInstance(last_h, HGate) + first_measure, first_measure_qubits, _ = transpiled.data[-2] + second_measure, second_measure_qubits, _ = transpiled.data[-1] + self.assertIsInstance(first_measure, Measure) + self.assertIsInstance(second_measure, Measure) + # Assert that the first measure is on the same qubit that the HGate was applied to, and the + # second measurement is on a different qubit (though we don't care which exactly - that + # depends a little on the randomisation of the pass). + self.assertEqual(h_qubits, first_measure_qubits) + self.assertNotEqual(h_qubits, second_measure_qubits) + if __name__ == "__main__": unittest.main() From 6138ac5f1df3be2dc605b1081f4aedbc8899a902 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 19 Apr 2022 18:29:19 -0400 Subject: [PATCH 12/12] Swap old networkx usage to retworkx (#7958) Since NetworkX 2.8, calling `networkx.adjacency_matrix` issues a FutureWarning that causes the documentation build to fail. There's no reason for us to be using `networkx` since we have `retworkx` now. --- qiskit/circuit/library/graph_state.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/qiskit/circuit/library/graph_state.py b/qiskit/circuit/library/graph_state.py index 83676af37485..f811bea83f73 100644 --- a/qiskit/circuit/library/graph_state.py +++ b/qiskit/circuit/library/graph_state.py @@ -44,11 +44,9 @@ class GraphState(QuantumCircuit): from qiskit.circuit.library import GraphState import qiskit.tools.jupyter - import networkx as nx - G = nx.Graph() - G.add_edges_from([(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)]) - adjmat = nx.adjacency_matrix(G) - circuit = GraphState(adjmat.toarray()) + import retworkx as rx + G = rx.generators.cycle_graph(5) + circuit = GraphState(rx.adjacency_matrix(G)) %circuit_library_info circuit **References:**