diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6b375adc132..0ad6b7413e6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -28,6 +28,7 @@ opflow/ @Qiskit/terra-core @manoelmarques @woodsp-ibm @ikkoham qiskit/utils/ @Qiskit/terra-core @manoelmarques @woodsp-ibm providers/ @Qiskit/terra-core @jyu00 quantum_info/ @Qiskit/terra-core @ikkoham +qpy/ @Qiskit/terra-core @nkanazawa1989 pulse/ @Qiskit/terra-core @eggerdj @nkanazawa1989 @danpuzzuoli scheduler/ @Qiskit/terra-core @eggerdj @nkanazawa1989 @danpuzzuoli visualization/ @Qiskit/terra-core @nonhermitian @nkanazawa1989 diff --git a/Cargo.lock b/Cargo.lock index fe80a05d987..e031cd32671 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ "ahash", "rayon", @@ -111,6 +111,16 @@ dependencies = [ "libc", ] +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "indoc" version = "1.0.4" @@ -229,9 +239,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e051c9e722a66c2e619b2a30f731e80b75f172d98ae3518b61b08a1d3d850a3" +checksum = "e0b4680aec093ae7b5522c849b3e949de3d92bbca52acdb047fd5be8e3b668db" dependencies = [ "libc", "ndarray", @@ -296,6 +306,7 @@ dependencies = [ "hashbrown", "indoc", "libc", + "num-complex", "parking_lot", "pyo3-build-config", "pyo3-ffi", @@ -350,8 +361,11 @@ dependencies = [ name = "qiskit-terra" version = "0.20.0" dependencies = [ + "ahash", "hashbrown", + "indexmap", "ndarray", + "num-complex", "numpy", "pyo3", "rand", diff --git a/Cargo.toml b/Cargo.toml index 23df62a2ee7..573cb65c2d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,21 +11,24 @@ crate-type = ["cdylib"] [dependencies] rayon = "1.5" -numpy = "0.16.0" +numpy = "0.16.1" rand = "0.8" rand_pcg = "0.3" rand_distr = "0.4.3" +indexmap = "1.8.0" +ahash = "0.7.6" +num-complex = "0.4" [dependencies.pyo3] version = "0.16.1" -features = ["extension-module", "hashbrown"] +features = ["extension-module", "hashbrown", "num-complex"] [dependencies.ndarray] version = "^0.15.0" features = ["rayon"] [dependencies.hashbrown] -version = "0.12.0" +version = "0.11.2" features = ["rayon"] [profile.release] diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 45bf6fa4985..08559cc77a1 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -25,7 +25,8 @@ # manually define them on import so people can directly import # qiskit._accelerate.* submodules and not have to rely on attribute access sys.modules["qiskit._accelerate.stochastic_swap"] = qiskit._accelerate.stochastic_swap - +sys.modules["qiskit._accelerate.pauli_expval"] = qiskit._accelerate.pauli_expval +sys.modules["qiskit._accelerate.dense_layout"] = qiskit._accelerate.dense_layout # qiskit errors operator from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index ffbb5459d61..d20c4f2e42c 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2021. +# (C) Copyright IBM 2018, 2022. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -47,6 +47,7 @@ :nosignatures: AmplificationProblem + AmplitudeAmplifier Grover GroverResult @@ -92,6 +93,22 @@ NumPyEigensolver +Evolvers +-------- + +Algorithms to evolve quantum states in time. Both real and imaginary time evolution is possible +with algorithms that support them. For machine learning, Quantum Imaginary Time Evolution might be +used to train Quantum Boltzmann Machine Neural Networks for example. + +.. autosummary:: + :toctree: ../stubs/ + :nosignatures: + + RealEvolver + ImaginaryEvolver + EvolutionResult + EvolutionProblem + Factorizers ----------- @@ -175,8 +192,11 @@ """ from .algorithm_result import AlgorithmResult +from .evolvers import EvolutionResult, EvolutionProblem +from .evolvers.real.real_evolver import RealEvolver +from .evolvers.imaginary.imaginary_evolver import ImaginaryEvolver from .variational_algorithm import VariationalAlgorithm, VariationalResult -from .amplitude_amplifiers import Grover, GroverResult, AmplificationProblem +from .amplitude_amplifiers import Grover, GroverResult, AmplificationProblem, AmplitudeAmplifier from .amplitude_estimators import ( AmplitudeEstimator, AmplitudeEstimatorResult, @@ -215,6 +235,7 @@ "AlgorithmResult", "VariationalAlgorithm", "VariationalResult", + "AmplitudeAmplifier", "AmplificationProblem", "Grover", "GroverResult", @@ -230,6 +251,10 @@ "MaximumLikelihoodAmplitudeEstimationResult", "EstimationProblem", "NumPyEigensolver", + "RealEvolver", + "ImaginaryEvolver", + "EvolutionResult", + "EvolutionProblem", "LinearSolverResult", "Eigensolver", "EigensolverResult", diff --git a/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py b/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py index ca9293afa35..5759fe377f3 100644 --- a/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py +++ b/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py @@ -25,7 +25,7 @@ class AmplitudeAmplifier(ABC): """The interface for amplification algorithms.""" @abstractmethod - def amplify(self, amplification_problem: AmplificationProblem) -> "AmplificationResult": + def amplify(self, amplification_problem: AmplificationProblem) -> "AmplitudeAmplifierResult": """Run the amplification algorithm. Args: diff --git a/qiskit/algorithms/amplitude_estimators/amplitude_estimator.py b/qiskit/algorithms/amplitude_estimators/amplitude_estimator.py index 6f78257d095..93136b03473 100644 --- a/qiskit/algorithms/amplitude_estimators/amplitude_estimator.py +++ b/qiskit/algorithms/amplitude_estimators/amplitude_estimator.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2020. +# (C) Copyright IBM 2018, 2022. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,7 +12,7 @@ """The Amplitude Estimation interface.""" -from abc import abstractmethod +from abc import abstractmethod, ABC from typing import Union, Optional, Dict, Callable, Tuple import numpy as np @@ -20,7 +20,7 @@ from ..algorithm_result import AlgorithmResult -class AmplitudeEstimator: +class AmplitudeEstimator(ABC): """The Amplitude Estimation interface.""" @abstractmethod diff --git a/qiskit/algorithms/eigen_solvers/eigen_solver.py b/qiskit/algorithms/eigen_solvers/eigen_solver.py index bdb8933ac5e..28ca3e9d1f8 100644 --- a/qiskit/algorithms/eigen_solvers/eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/eigen_solver.py @@ -13,15 +13,13 @@ """The Eigensolver interface""" from abc import ABC, abstractmethod -from typing import Dict, Optional, List, Union, Tuple, TypeVar +from typing import Optional, List, Tuple import numpy as np + from qiskit.opflow import OperatorBase from ..algorithm_result import AlgorithmResult - -# Introduced new type to maintain readability. -_T = TypeVar("_T") # Pylint does not allow single character class names. -ListOrDict = Union[List[Optional[_T]], Dict[str, _T]] +from ..list_or_dict import ListOrDict class Eigensolver(ABC): diff --git a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py index c30504b676d..4abbe7ed4a2 100755 --- a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py +++ b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py @@ -21,7 +21,8 @@ from qiskit.opflow import I, ListOp, OperatorBase, StateFn from qiskit.utils.validation import validate_min from ..exceptions import AlgorithmError -from .eigen_solver import Eigensolver, EigensolverResult, ListOrDict +from .eigen_solver import Eigensolver, EigensolverResult +from ..list_or_dict import ListOrDict logger = logging.getLogger(__name__) diff --git a/qiskit/algorithms/evolvers/__init__.py b/qiskit/algorithms/evolvers/__init__.py new file mode 100644 index 00000000000..990c787b7c1 --- /dev/null +++ b/qiskit/algorithms/evolvers/__init__.py @@ -0,0 +1,21 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Quantum Time Evolution package.""" + +from .evolution_result import EvolutionResult +from .evolution_problem import EvolutionProblem + +__all__ = [ + "EvolutionResult", + "EvolutionProblem", +] diff --git a/qiskit/algorithms/evolvers/evolution_problem.py b/qiskit/algorithms/evolvers/evolution_problem.py new file mode 100644 index 00000000000..f926dbdf3d9 --- /dev/null +++ b/qiskit/algorithms/evolvers/evolution_problem.py @@ -0,0 +1,58 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Evolution problem class.""" + +from typing import Union, Optional, Dict + +from qiskit import QuantumCircuit +from qiskit.circuit import Parameter +from qiskit.opflow import OperatorBase, StateFn +from ..list_or_dict import ListOrDict + + +class EvolutionProblem: + """Evolution problem class. + + This class is the input to time evolution algorithms and contains + information on e.g. the total evolution time and under which Hamiltonian + the state is evolved. + """ + + def __init__( + self, + hamiltonian: OperatorBase, + time: float, + initial_state: Union[StateFn, QuantumCircuit], + aux_operators: Optional[ListOrDict[OperatorBase]] = None, + t_param: Optional[Parameter] = None, + hamiltonian_value_dict: Optional[Dict[Parameter, Union[complex]]] = None, + ): + """ + Args: + hamiltonian: The Hamiltonian under which to evolve the system. + time: Total time of evolution. + initial_state: Quantum state to be evolved. + aux_operators: Optional list of auxiliary operators to be evaluated with the + evolved ``initial_state`` and their expectation values returned. + t_param: Time parameter in case of a time-dependent Hamiltonian. This + free parameter must be within the ``hamiltonian``. + hamiltonian_value_dict: If the Hamiltonian contains free parameters, this + dictionary maps all these parameters to values. + """ + + self.hamiltonian = hamiltonian + self.time = time + self.initial_state = initial_state + self.aux_operators = aux_operators + self.t_param = t_param + self.hamiltonian_value_dict = hamiltonian_value_dict diff --git a/qiskit/algorithms/evolvers/evolution_result.py b/qiskit/algorithms/evolvers/evolution_result.py new file mode 100644 index 00000000000..ead37fd98df --- /dev/null +++ b/qiskit/algorithms/evolvers/evolution_result.py @@ -0,0 +1,40 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Class for holding evolution result.""" + +from typing import Optional, Union, Tuple + +from qiskit import QuantumCircuit +from qiskit.algorithms.list_or_dict import ListOrDict +from qiskit.opflow import StateFn +from ..algorithm_result import AlgorithmResult + + +class EvolutionResult(AlgorithmResult): + """Class for holding evolution result.""" + + def __init__( + self, + evolved_state: Union[StateFn, QuantumCircuit], + aux_ops_evaluated: Optional[ListOrDict[Tuple[complex, complex]]] = None, + ): + """ + Args: + evolved_state: An evolved quantum state. + aux_ops_evaluated: Optional list of observables for which expected values on an evolved + state are calculated. These values are in fact tuples formatted as (mean, standard + deviation). + """ + + self.evolved_state = evolved_state + self.aux_ops_evaluated = aux_ops_evaluated diff --git a/qiskit/algorithms/evolvers/evolver.py b/qiskit/algorithms/evolvers/evolver.py new file mode 100644 index 00000000000..06b1f22d56b --- /dev/null +++ b/qiskit/algorithms/evolvers/evolver.py @@ -0,0 +1,36 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Interface for quantum time evolution.""" + +from abc import ABC, abstractmethod + +from . import EvolutionProblem +from . import EvolutionResult + + +class Evolver(ABC): + """Interface class for quantum time evolution.""" + + @abstractmethod + def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult: + """ + Evolves an initial state in the evolution_problem according to a Hamiltonian provided. + + Args: + evolution_problem: ``EvolutionProblem`` instance that includes definition of an evolution + problem. + + Returns: + Evolution result which includes an evolved quantum state. + """ + raise NotImplementedError() diff --git a/qiskit/algorithms/evolvers/imaginary/__init__.py b/qiskit/algorithms/evolvers/imaginary/__init__.py new file mode 100644 index 00000000000..b3ac36d2a6d --- /dev/null +++ b/qiskit/algorithms/evolvers/imaginary/__init__.py @@ -0,0 +1,11 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/qiskit/algorithms/evolvers/imaginary/imaginary_evolver.py b/qiskit/algorithms/evolvers/imaginary/imaginary_evolver.py new file mode 100644 index 00000000000..4b1ab96eecd --- /dev/null +++ b/qiskit/algorithms/evolvers/imaginary/imaginary_evolver.py @@ -0,0 +1,21 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Base class for Quantum Imaginary Time Evolution used for typing purposes.""" + +from abc import ABC + +from ..evolver import Evolver + + +class ImaginaryEvolver(Evolver, ABC): + """Base class for Quantum Imaginary Time Evolution used for typing purposes.""" diff --git a/qiskit/algorithms/evolvers/real/__init__.py b/qiskit/algorithms/evolvers/real/__init__.py new file mode 100644 index 00000000000..b3ac36d2a6d --- /dev/null +++ b/qiskit/algorithms/evolvers/real/__init__.py @@ -0,0 +1,11 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/qiskit/algorithms/evolvers/real/real_evolver.py b/qiskit/algorithms/evolvers/real/real_evolver.py new file mode 100644 index 00000000000..c1663ed3868 --- /dev/null +++ b/qiskit/algorithms/evolvers/real/real_evolver.py @@ -0,0 +1,21 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Base class for Quantum Real Time Evolution used for typing purposes.""" + +from abc import ABC + +from ..evolver import Evolver + + +class RealEvolver(Evolver, ABC): + """Base class for Quantum Real Time Evolution used for typing purposes.""" diff --git a/qiskit/algorithms/linear_solvers/hhl.py b/qiskit/algorithms/linear_solvers/hhl.py index 363fc0a5a6d..cf6dbd6e59d 100644 --- a/qiskit/algorithms/linear_solvers/hhl.py +++ b/qiskit/algorithms/linear_solvers/hhl.py @@ -383,7 +383,7 @@ def construct_circuit( # Update the number of qubits required to represent the eigenvalues # The +neg_vals is to register negative eigenvalues because # e^{-2 \pi i \lambda} = e^{2 \pi i (1 - \lambda)} - nl = max(nb + 1, int(np.log2(kappa)) + 1) + neg_vals + nl = max(nb + 1, int(np.ceil(np.log2(kappa + 1)))) + neg_vals # check if the matrix can calculate bounds for the eigenvalues if hasattr(matrix_circuit, "eigs_bounds") and matrix_circuit.eigs_bounds() is not None: diff --git a/qiskit/algorithms/list_or_dict.py b/qiskit/algorithms/list_or_dict.py new file mode 100644 index 00000000000..95314dd79a3 --- /dev/null +++ b/qiskit/algorithms/list_or_dict.py @@ -0,0 +1,18 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Introduced new type to maintain readability.""" + +from typing import TypeVar, List, Union, Optional, Dict + +_T = TypeVar("_T") # Pylint does not allow single character class names. +ListOrDict = Union[List[Optional[_T]], Dict[str, _T]] diff --git a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py index 545cf289643..7cdd245b707 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py @@ -13,15 +13,13 @@ """The Minimum Eigensolver interface""" from abc import ABC, abstractmethod -from typing import Dict, Optional, List, Union, Tuple, TypeVar +from typing import Optional, Tuple import numpy as np + from qiskit.opflow import OperatorBase from ..algorithm_result import AlgorithmResult - -# Introduced new type to maintain readability. -_T = TypeVar("_T") # Pylint does not allow single character class names. -ListOrDict = Union[List[Optional[_T]], Dict[str, _T]] +from ..list_or_dict import ListOrDict class MinimumEigensolver(ABC): diff --git a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py index 369485976cd..5515ba92d74 100644 --- a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py +++ b/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py @@ -18,7 +18,8 @@ from qiskit.opflow import OperatorBase from ..eigen_solvers.numpy_eigen_solver import NumPyEigensolver -from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult, ListOrDict +from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult +from ..list_or_dict import ListOrDict logger = logging.getLogger(__name__) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index b87d481f80a..12a69b9af62 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -39,9 +39,10 @@ from qiskit.utils.validation import validate_min from qiskit.utils.backend_utils import is_aer_provider from qiskit.utils import QuantumInstance, algorithm_globals +from ..list_or_dict import ListOrDict from ..optimizers import Optimizer, SLSQP from ..variational_algorithm import VariationalAlgorithm, VariationalResult -from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult, ListOrDict +from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult from ..exceptions import AlgorithmError logger = logging.getLogger(__name__) diff --git a/qiskit/opflow/primitive_ops/pauli_op.py b/qiskit/opflow/primitive_ops/pauli_op.py index e74595404f0..7b5e57a4b4e 100644 --- a/qiskit/opflow/primitive_ops/pauli_op.py +++ b/qiskit/opflow/primitive_ops/pauli_op.py @@ -87,10 +87,16 @@ def adjoint(self) -> "PauliOp": return PauliOp(self.primitive, coeff=self.coeff.conjugate()) def equals(self, other: OperatorBase) -> bool: - if not isinstance(other, PauliOp) or not self.coeff == other.coeff: - return False + if isinstance(other, PauliOp) and self.coeff == other.coeff: + return self.primitive == other.primitive - return self.primitive == other.primitive + # pylint: disable=cyclic-import + from .pauli_sum_op import PauliSumOp + + if isinstance(other, PauliSumOp): + return other == self + + return False def _expand_dim(self, num_qubits: int) -> "PauliOp": return PauliOp(Pauli("I" * num_qubits).expand(self.primitive), coeff=self.coeff) diff --git a/qiskit/opflow/primitive_ops/pauli_sum_op.py b/qiskit/opflow/primitive_ops/pauli_sum_op.py index c514ad8be09..d5069f135ad 100644 --- a/qiskit/opflow/primitive_ops/pauli_sum_op.py +++ b/qiskit/opflow/primitive_ops/pauli_sum_op.py @@ -148,6 +148,11 @@ def adjoint(self) -> "PauliSumOp": def equals(self, other: OperatorBase) -> bool: self_reduced, other_reduced = self.reduce(), other.reduce() + if isinstance(other_reduced, PauliOp): + other_reduced = PauliSumOp( + SparsePauliOp(other_reduced.primitive, coeffs=[other_reduced.coeff]) + ) + if not isinstance(other_reduced, PauliSumOp): return False diff --git a/qiskit/primitives/base_estimator.py b/qiskit/primitives/base_estimator.py index 61745dfc440..63ebf29d817 100644 --- a/qiskit/primitives/base_estimator.py +++ b/qiskit/primitives/base_estimator.py @@ -10,9 +10,9 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. r""" -========= -Estimator -========= +===================== +Overview of Estimator +===================== Estimator class estimates expectation values of quantum circuits and observables. @@ -41,15 +41,15 @@ to be bound to the parameters of the quantum circuits. (list of list of float) -The output is an EstimatorResult which contains a list of expectation values plus -optional metadata like confidence intervals for the estimation. +The output is an :class:`~qiskit.primitives.EstimatorResult` which contains a list of +expectation values plus optional metadata like confidence intervals for the estimation. .. math:: \langle\psi_i(\theta_k)|H_j|\psi_i(\theta_k)\rangle -The estimator object is expected to be `close()` d after use or +The estimator object is expected to be ``close()`` d after use or accessed inside "with" context and the objects are called with parameter values and run options (e.g., ``shots`` or number of shots). @@ -77,36 +77,36 @@ theta3 = [1, 2, 3, 4, 5, 6] # calculate [ ] - psi1_H1_result = e([0], [0], [theta1]) - print(psi1_H1_result) + result = e([0], [0], [theta1]) + print(result) # calculate [ , ] - psi1_H23_result = e([0, 0], [1, 2], [theta1]*2) - print(psi1_H23_result) + result2 = e([0, 0], [1, 2], [theta1]*2) + print(result2) # calculate [ ] - psi2_H2_result = e([1], [1], [theta2]) - print(psi2_H2_result) + result3 = e([1], [1], [theta2]) + print(result3) # calculate [ , ] - psi1_H1_result2 = e([0, 0], [0, 0], [theta1, theta3]) - print(psi1_H1_result2) + result4 = e([0, 0], [0, 0], [theta1, theta3]) + print(result4) # calculate [ , # , # ] - psi12_H123_result = e([0, 0, 0], [0, 1, 2], [theta1, theta2, theta3]) - print(psi12_H23_result) + result5 = e([0, 1, 0], [0, 1, 2], [theta1, theta2, theta3]) + print(result5) """ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Iterable, Optional, Sequence +from collections.abc import Iterable, Sequence from qiskit.circuit import Parameter, QuantumCircuit from qiskit.circuit.parametertable import ParameterView from qiskit.exceptions import QiskitError -from qiskit.quantum_info import SparsePauliOp +from qiskit.quantum_info.operators import SparsePauliOp from .estimator_result import EstimatorResult @@ -121,7 +121,7 @@ def __init__( self, circuits: Iterable[QuantumCircuit], observables: Iterable[SparsePauliOp], - parameters: Optional[Iterable[Iterable[Parameter]]] = None, + parameters: Iterable[Iterable[Parameter]] | None = None, ): """ Creating an instance of an Estimator, or using one in a ``with`` context opens a session that @@ -150,6 +150,12 @@ def __init__( f"Different number of parameters ({len(self._parameters)} and " f"circuits ({len(self._circuits)}" ) + for i, (circ, params) in enumerate(zip(self._circuits, self._parameters)): + if circ.num_parameters != len(params): + raise QiskitError( + f"Different numbers of parameters of {i}-th circuit: " + f"expected {circ.num_parameters}, actual {len(params)}." + ) def __enter__(self): return self diff --git a/qiskit/primitives/base_sampler.py b/qiskit/primitives/base_sampler.py index 584d626ddb0..0e60a74d67c 100644 --- a/qiskit/primitives/base_sampler.py +++ b/qiskit/primitives/base_sampler.py @@ -10,9 +10,9 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. r""" -======= -Sampler -======= +=================== +Overview of Sampler +=================== Sampler class calculates probabilities or quasi-probabilities of bitstrings from quantum circuits. @@ -25,7 +25,7 @@ (:class:`~qiskit.circuit.parametertable.ParameterView` or a list of :class:`~qiskit.circuit.Parameter`). -The estimator is run with the following inputs. +The sampler is run with the following inputs. * circuit indexes: a list of indices of the circuits to evaluate. @@ -33,7 +33,8 @@ to be bound to the parameters of the quantum circuits. (list of list of float) -The output is a SamplerResult which contains probabilities or quasi-probabilities of bitstrings, +The output is a :class:`~qiskit.primitives.SamplerResult` which contains probabilities +or quasi-probabilities of bitstrings, plus optional metadata like error bars in the samples. The sampler object is expected to be closed after use or @@ -89,7 +90,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Iterable, Sequence +from collections.abc import Iterable, Sequence from qiskit.circuit import Parameter, QuantumCircuit from qiskit.circuit.parametertable import ParameterView diff --git a/qiskit/primitives/estimator_result.py b/qiskit/primitives/estimator_result.py index 07ea38224e3..e4dc26dadff 100644 --- a/qiskit/primitives/estimator_result.py +++ b/qiskit/primitives/estimator_result.py @@ -25,14 +25,16 @@ @dataclass(frozen=True) class EstimatorResult: """ - Result of ExpectationValue + Result of Estimator .. code-block:: python result = estimator(circuits, observables, params) - where the i-th elements of `result` correspond to the expectation using the circuit and - observable given by `circuits[i]`, `observables[i]`, and the parameters bounds by `params[i]`. + where the i-th elements of ``result`` correspond to the circuit and observable given by + `circuits[i]`, `observables[i]`, and the parameters bounds by `params[i]`. + For example, ``results.values[i]`` gives the expectation value, and ``result.metadata[i]`` + is a metadata dictionary for this circuit and parameters. Args: values (np.ndarray): the array of the expectation values. diff --git a/qiskit/primitives/sampler_result.py b/qiskit/primitives/sampler_result.py index ea3d86d5dcf..6a2d44c72ea 100644 --- a/qiskit/primitives/sampler_result.py +++ b/qiskit/primitives/sampler_result.py @@ -30,8 +30,14 @@ class SamplerResult: result = sampler(circuits, params) - where the i-th elements of `result` correspond to the expectation using the circuit - given by `circuits[i]` and the parameters bounds by `params[i]`. + where the i-th elements of ``result`` correspond to the circuit given by ``circuits[i]``, + and the parameters bounds by ``params[i]``. + For example, ``results.quasi_dists[i]`` gives the quasi-probabilities of bitstrings, and + ``result.metadata[i]`` is a metadata dictionary for this circuit and parameters. + + Args: + quasi_dists (list[QuasiDistribution]): list of the quasi-probabilities. + metadata (list[dict]): list of the metadata. """ quasi_dists: list[QuasiDistribution] diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py index 83c439dbd10..5d76f2d5b4d 100644 --- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2020. +# (C) Copyright IBM 2017, 2022. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -286,7 +286,10 @@ def compose(self, other, qargs=None, front=False): z4[:, qargs] = z3 pauli_list = PauliList(BasePauli(z4, x4, phase)) - coeffs = np.kron(self.coeffs, other.coeffs) + # note: the following is a faster code equivalent to + # `coeffs = np.kron(self.coeffs, other.coeffs)` + # since `self.coeffs` and `other.coeffs` are both 1d arrays. + coeffs = np.multiply.outer(self.coeffs, other.coeffs).ravel() return SparsePauliOp(pauli_list, coeffs, copy=False) def tensor(self, other): diff --git a/qiskit/quantum_info/states/cython/exp_value.pyx b/qiskit/quantum_info/states/cython/exp_value.pyx deleted file mode 100644 index 7e374566d64..00000000000 --- a/qiskit/quantum_info/states/cython/exp_value.pyx +++ /dev/null @@ -1,135 +0,0 @@ -#!python -#cython: language_level = 3 -#distutils: language = c++ - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -cimport cython -import numpy as np - -cdef unsigned long long m1 = 0x5555555555555555 -cdef unsigned long long m2 = 0x3333333333333333 -cdef unsigned long long m4 = 0x0f0f0f0f0f0f0f0f -cdef unsigned long long m8 = 0x00ff00ff00ff00ff -cdef unsigned long long m16 = 0x0000ffff0000ffff -cdef unsigned long long m32 = 0x00000000ffffffff - -cdef unsigned long long popcount(unsigned long long count): - count = (count & m1) + ((count >> 1) & m1); - count = (count & m2) + ((count >> 2) & m2); - count = (count & m4) + ((count >> 4) & m4); - count = (count & m8) + ((count >> 8) & m8); - count = (count & m16) + ((count >> 16) & m16); - count = (count & m32) + ((count >> 32) & m32); - return count - - -def expval_pauli_no_x(complex[::1] data, - unsigned long long num_qubits, - unsigned long long z_mask): - cdef double val = 0 - cdef int i - cdef current_val - cdef unsigned long long size = 1 << num_qubits - for i in range(size): - current_val = (data[i].real*data[i].real+data[i].imag*data[i].imag).real - if popcount(i & z_mask) & 1 != 0: - current_val *= -1 - val += current_val - return val - - -def expval_pauli_with_x(complex[::1] data, - unsigned long long num_qubits, - unsigned long long z_mask, - unsigned long long x_mask, - complex phase, - unsigned int x_max): - cdef unsigned long long mask_u = ~(2 ** (x_max + 1) - 1) - cdef unsigned long long mask_l = 2**(x_max) - 1 - cdef double val = 0 - cdef unsigned int i - cdef unsigned long long index_0 - cdef unsigned long long index_1 - cdef double current_val_0 - cdef double current_val_1 - cdef unsigned long long size = 1 << (num_qubits - 1) - for i in range(size): - index_0 = ((i << 1) & mask_u) | (i & mask_l) - index_1 = index_0 ^ x_mask - - current_val_0 = (phase * - (data[index_1].real*data[index_0].real + - data[index_1].imag*data[index_0].imag + - 1j*(data[index_1].imag*data[index_0].real - - data[index_1].real*data[index_0].imag)) - ).real - - current_val_1 = (phase * - (data[index_0].real*data[index_1].real + - data[index_0].imag*data[index_1].imag + - 1j*(data[index_0].imag*data[index_1].real - - data[index_0].real*data[index_1].imag)) - ).real - - if popcount(index_0 & z_mask) & 1 != 0: - val -= current_val_0 - else: - val += current_val_0 - - if popcount(index_1 & z_mask) & 1 != 0: - val -= current_val_1 - else: - val += current_val_1 - return val - - -def density_expval_pauli_no_x(complex[::1] data, - unsigned long long num_qubits, - unsigned long long z_mask): - cdef double val = 0 - cdef int i - cdef unsigned long long nrows = 1 << num_qubits - cdef unsigned long long stride = 1 + nrows - cdef unsigned long long index - for i in range(nrows): - index = i * stride - current_val = (data[index]).real - if popcount(i & z_mask) & 1 != 0: - current_val *= -1 - val += current_val - return val - - -def density_expval_pauli_with_x(complex[::1] data, - unsigned long long num_qubits, - unsigned long long z_mask, - unsigned long long x_mask, - complex phase, - unsigned int x_max): - cdef unsigned long long mask_u = ~(2 ** (x_max + 1) - 1) - cdef unsigned long long mask_l = 2**(x_max) - 1 - cdef double val = 0 - cdef unsigned int i - cdef double current_val - cdef unsigned long long nrows = 1 << num_qubits - cdef unsigned long long index_vec - cdef unsigned long long index_mat - for i in range(nrows >> 1): - index_vec = ((i << 1) & mask_u) | (i & mask_l) - index_mat = index_vec ^ x_mask + nrows * index_vec - current_val = 2 * (phase * data[index_mat]).real - if popcount(index_vec & z_mask) & 1 != 0: - current_val *= -1 - val += current_val - return val diff --git a/qiskit/quantum_info/states/densitymatrix.py b/qiskit/quantum_info/states/densitymatrix.py index 0119c4533b1..4e500e42a44 100644 --- a/qiskit/quantum_info/states/densitymatrix.py +++ b/qiskit/quantum_info/states/densitymatrix.py @@ -32,8 +32,8 @@ from qiskit.quantum_info.operators.channel.quantum_channel import QuantumChannel from qiskit.quantum_info.operators.channel.superop import SuperOp -# pylint: disable=no-name-in-module -from .cython.exp_value import density_expval_pauli_no_x, density_expval_pauli_with_x +# pylint: disable=import-error +from qiskit._accelerate.pauli_expval import density_expval_pauli_no_x, density_expval_pauli_with_x class DensityMatrix(QuantumState, TolerancesMixin): diff --git a/qiskit/quantum_info/states/statevector.py b/qiskit/quantum_info/states/statevector.py index d41f0848ae5..65120537a8c 100644 --- a/qiskit/quantum_info/states/statevector.py +++ b/qiskit/quantum_info/states/statevector.py @@ -31,8 +31,11 @@ from qiskit.quantum_info.operators.op_shape import OpShape from qiskit.quantum_info.operators.predicates import matrix_equal -# pylint: disable=no-name-in-module -from .cython.exp_value import expval_pauli_no_x, expval_pauli_with_x +# pylint: disable=import-error +from qiskit._accelerate.pauli_expval import ( + expval_pauli_no_x, + expval_pauli_with_x, +) class Statevector(QuantumState, TolerancesMixin): diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 7d76f8ff865..f341c9fef31 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -229,6 +229,7 @@ from .scheduling import DynamicalDecoupling from .scheduling import AlignMeasures from .scheduling import ValidatePulseGates +from .scheduling import PadDelay # additional utility passes from .utils import CheckMap diff --git a/qiskit/transpiler/passes/layout/dense_layout.py b/qiskit/transpiler/passes/layout/dense_layout.py index cb60e1937de..ca6e7257a26 100644 --- a/qiskit/transpiler/passes/layout/dense_layout.py +++ b/qiskit/transpiler/passes/layout/dense_layout.py @@ -14,11 +14,14 @@ import numpy as np +import retworkx from qiskit.transpiler.layout import Layout from qiskit.transpiler.basepasses import AnalysisPass from qiskit.transpiler.exceptions import TranspilerError +from qiskit._accelerate.dense_layout import best_subset # pylint: disable=import-error + class DenseLayout(AnalysisPass): """Choose a Layout by finding the most connected subset of qubits. @@ -42,10 +45,29 @@ def __init__(self, coupling_map, backend_prop=None): super().__init__() self.coupling_map = coupling_map self.backend_prop = backend_prop - self.cx_mat = None - self.meas_arr = None - self.num_cx = 0 - self.num_meas = 0 + num_qubits = 0 + self.adjacency_matrix = None + if self.coupling_map: + num_qubits = self.coupling_map.size() + self.adjacency_matrix = retworkx.adjacency_matrix(self.coupling_map.graph) + self.error_mat = np.zeros((num_qubits, num_qubits)) + if self.backend_prop and self.coupling_map: + error_dict = { + tuple(gate.qubits): gate.parameters[0].value + for gate in self.backend_prop.gates + if len(gate.qubits) == 2 + } + for edge in self.coupling_map.get_edges(): + gate_error = error_dict.get(edge) + if gate_error is not None: + self.error_mat[edge[0]][edge[1]] = gate_error + for index, qubit_data in enumerate(self.backend_prop.qubits): + # Handle faulty qubits edge case + if index >= num_qubits: + break + for item in qubit_data: + if item.name == "readout_error": + self.error_mat[index][index] = item.value def run(self, dag): """Run the DenseLayout pass on `dag`. @@ -59,52 +81,20 @@ def run(self, dag): Raises: TranspilerError: if dag wider than self.coupling_map """ - from scipy.sparse import coo_matrix - - num_dag_qubits = sum(qreg.size for qreg in dag.qregs.values()) + num_dag_qubits = len(dag.qubits) if num_dag_qubits > self.coupling_map.size(): raise TranspilerError("Number of qubits greater than device.") + num_cx = 0 + num_meas = 0 # Get avg number of cx and meas per qubit ops = dag.count_ops() if "cx" in ops.keys(): - self.num_cx = ops["cx"] + num_cx = ops["cx"] if "measure" in ops.keys(): - self.num_meas = ops["measure"] - - # Compute the sparse cx_err matrix and meas array - device_qubits = self.coupling_map.size() - if self.backend_prop: - rows = [] - cols = [] - cx_err = [] - - for edge in self.coupling_map.get_edges(): - for gate in self.backend_prop.gates: - if gate.qubits == edge: - rows.append(edge[0]) - cols.append(edge[1]) - cx_err.append(gate.parameters[0].value) - break - else: - continue - - self.cx_mat = coo_matrix( - (cx_err, (rows, cols)), shape=(device_qubits, device_qubits) - ).tocsr() - - # Set measurement array - meas_err = [] - for qubit_data in self.backend_prop.qubits: - for item in qubit_data: - if item.name == "readout_error": - meas_err.append(item.value) - break - else: - continue - self.meas_arr = np.asarray(meas_err) + num_meas = ops["measure"] - best_sub = self._best_subset(num_dag_qubits) + best_sub = self._best_subset(num_dag_qubits, num_meas, num_cx) layout = Layout() map_iter = 0 for qreg in dag.qregs.values(): @@ -114,7 +104,7 @@ def run(self, dag): layout.add_register(qreg) self.property_set["layout"] = layout - def _best_subset(self, num_qubits): + def _best_subset(self, num_qubits, num_meas, num_cx): """Computes the qubit mapping with the best connectivity. Args: @@ -130,66 +120,15 @@ def _best_subset(self, num_qubits): if num_qubits == 0: return [] - device_qubits = self.coupling_map.size() - - cmap = np.asarray(self.coupling_map.get_edges()) - data = np.ones_like(cmap[:, 0]) - sp_cmap = coo_matrix( - (data, (cmap[:, 0], cmap[:, 1])), shape=(device_qubits, device_qubits) - ).tocsr() - best = 0 - best_map = None - best_error = np.inf - best_sub = None - # do bfs with each node as starting point - for k in range(sp_cmap.shape[0]): - bfs = csgraph.breadth_first_order( - sp_cmap, i_start=k, directed=False, return_predecessors=False - ) - - connection_count = 0 - sub_graph = [] - for i in range(num_qubits): - node_idx = bfs[i] - for j in range(sp_cmap.indptr[node_idx], sp_cmap.indptr[node_idx + 1]): - node = sp_cmap.indices[j] - for counter in range(num_qubits): - if node == bfs[counter]: - connection_count += 1 - sub_graph.append([node_idx, node]) - break - - if self.backend_prop: - curr_error = 0 - # compute meas error for subset - avg_meas_err = np.mean(self.meas_arr) - meas_diff = np.mean(self.meas_arr[bfs[0:num_qubits]]) - avg_meas_err - if meas_diff > 0: - curr_error += self.num_meas * meas_diff - - cx_err = np.mean([self.cx_mat[edge[0], edge[1]] for edge in sub_graph]) - if self.coupling_map.is_symmetric: - cx_err /= 2 - curr_error += self.num_cx * cx_err - if connection_count >= best and curr_error < best_error: - best = connection_count - best_error = curr_error - best_map = bfs[0:num_qubits] - best_sub = sub_graph - - else: - if connection_count > best: - best = connection_count - best_map = bfs[0:num_qubits] - best_sub = sub_graph - - # Return a best mapping that has reduced bandwidth - mapping = {} - for edge in range(best_map.shape[0]): - mapping[best_map[edge]] = edge - new_cmap = [[mapping[c[0]], mapping[c[1]]] for c in best_sub] - rows = [edge[0] for edge in new_cmap] - cols = [edge[1] for edge in new_cmap] + rows, cols, best_map = best_subset( + num_qubits, + self.adjacency_matrix, + num_meas, + num_cx, + bool(self.backend_prop), + self.coupling_map.is_symmetric, + self.error_mat, + ) data = [1] * len(rows) sp_sub_graph = coo_matrix((data, (rows, cols)), shape=(num_qubits, num_qubits)).tocsr() perm = csgraph.reverse_cuthill_mckee(sp_sub_graph) diff --git a/qiskit/transpiler/passes/scheduling/__init__.py b/qiskit/transpiler/passes/scheduling/__init__.py index f8270d23da5..da0056d03a7 100644 --- a/qiskit/transpiler/passes/scheduling/__init__.py +++ b/qiskit/transpiler/passes/scheduling/__init__.py @@ -17,3 +17,4 @@ from .time_unit_conversion import TimeUnitConversion from .dynamical_decoupling import DynamicalDecoupling from .instruction_alignment import AlignMeasures, ValidatePulseGates +from .padding import PadDelay diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py index 86fa679b38f..980c917e9dc 100644 --- a/qiskit/transpiler/passes/scheduling/alap.py +++ b/qiskit/transpiler/passes/scheduling/alap.py @@ -11,8 +11,7 @@ # that they have been altered from the originals. """ALAP Scheduling.""" -from qiskit.circuit import Delay, Qubit, Measure -from qiskit.dagcircuit import DAGCircuit +from qiskit.circuit import Measure from qiskit.transpiler.exceptions import TranspilerError from .base_scheduler import BaseScheduler @@ -41,13 +40,7 @@ def run(self, dag): if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: raise TranspilerError("ALAP schedule runs on physical circuits only") - time_unit = self.property_set["time_unit"] - new_dag = DAGCircuit() - for qreg in dag.qregs.values(): - new_dag.add_qreg(qreg) - for creg in dag.cregs.values(): - new_dag.add_creg(creg) - + node_start_time = dict() idle_before = {q: 0 for q in dag.qubits + dag.clbits} bit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for node in reversed(list(dag.topological_op_nodes())): @@ -117,26 +110,15 @@ def run(self, dag): t1 = t0 + op_duration for bit in node.qargs: - delta = t0 - idle_before[bit] - if delta > 0: - new_dag.apply_operation_front(Delay(delta, time_unit), [bit], []) idle_before[bit] = t1 - new_dag.apply_operation_front(node.op, node.qargs, node.cargs) + node_start_time[node] = t1 + # Compute maximum instruction available time, i.e. very end of t1 circuit_duration = max(idle_before.values()) - for bit, before in idle_before.items(): - delta = circuit_duration - before - if not (delta > 0 and isinstance(bit, Qubit)): - continue - new_dag.apply_operation_front(Delay(delta, time_unit), [bit], []) - - new_dag.name = dag.name - new_dag.metadata = dag.metadata - new_dag.calibrations = dag.calibrations - - # set circuit duration and unit to indicate it is scheduled - new_dag.duration = circuit_duration - new_dag.unit = time_unit - return new_dag + # Note that ALAP pass is inversely schedule, thus + # t0 is computed by subtracting entire circuit duration from t1. + self.property_set["node_start_time"] = { + n: circuit_duration - t1 for n, t1 in node_start_time.items() + } diff --git a/qiskit/transpiler/passes/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/asap.py index 5c3be528fb8..e0b5cdad154 100644 --- a/qiskit/transpiler/passes/scheduling/asap.py +++ b/qiskit/transpiler/passes/scheduling/asap.py @@ -11,8 +11,7 @@ # that they have been altered from the originals. """ASAP Scheduling.""" -from qiskit.circuit import Delay, Qubit, Measure -from qiskit.dagcircuit import DAGCircuit +from qiskit.circuit import Measure from qiskit.transpiler.exceptions import TranspilerError from .base_scheduler import BaseScheduler @@ -41,14 +40,7 @@ def run(self, dag): if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: raise TranspilerError("ASAP schedule runs on physical circuits only") - time_unit = self.property_set["time_unit"] - - new_dag = DAGCircuit() - for qreg in dag.qregs.values(): - new_dag.add_qreg(qreg) - for creg in dag.cregs.values(): - new_dag.add_creg(creg) - + node_start_time = dict() idle_after = {q: 0 for q in dag.qubits + dag.clbits} bit_indices = {q: index for index, q in enumerate(dag.qubits)} for node in dag.topological_op_nodes(): @@ -128,27 +120,9 @@ def run(self, dag): t0 = max(idle_after[bit] for bit in node.qargs + node.cargs) t1 = t0 + op_duration - # Add delay to qubit wire for bit in node.qargs: - delta = t0 - idle_after[bit] - if delta > 0 and isinstance(bit, Qubit): - new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) idle_after[bit] = t1 - new_dag.apply_operation_back(node.op, node.qargs, node.cargs) - - circuit_duration = max(idle_after.values()) - for bit, after in idle_after.items(): - delta = circuit_duration - after - if not (delta > 0 and isinstance(bit, Qubit)): - continue - new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) - - new_dag.name = dag.name - new_dag.metadata = dag.metadata - new_dag.calibrations = dag.calibrations + node_start_time[node] = t0 - # set circuit duration and unit to indicate it is scheduled - new_dag.duration = circuit_duration - new_dag.unit = time_unit - return new_dag + self.property_set["node_start_time"] = node_start_time diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index b0b5581a0d7..d582822b53d 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -12,9 +12,11 @@ """Base circuit scheduling pass.""" +import warnings + from typing import Dict from qiskit.transpiler import InstructionDurations -from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.basepasses import AnalysisPass from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion from qiskit.dagcircuit import DAGOpNode, DAGCircuit from qiskit.circuit import Delay, Gate @@ -22,7 +24,7 @@ from qiskit.transpiler.exceptions import TranspilerError -class BaseScheduler(TransformationPass): +class BaseScheduler(AnalysisPass): """Base scheduler pass. Policy of topological node ordering in scheduling @@ -239,6 +241,15 @@ def __init__( # Ensure op node durations are attached and in consistent unit self.requires.append(TimeUnitConversion(durations)) + # Initialize timeslot + if "node_start_time" in self.property_set: + warnings.warn( + "This circuit has been already scheduled. " + "The output of previous scheduling pass will be overridden.", + UserWarning, + ) + self.property_set["node_start_time"] = dict() + @staticmethod def _get_node_duration( node: DAGOpNode, @@ -252,6 +263,9 @@ def _get_node_duration( # If node has calibration, this value should be the highest priority cal_key = tuple(indices), tuple(float(p) for p in node.op.params) duration = dag.calibrations[node.op.name][cal_key].duration + + # Note that node duration is updated (but this is analysis pass) + node.op.duration = duration else: duration = node.op.duration diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py index ed6e806eb4a..55f7e14f838 100644 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -25,6 +25,8 @@ from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError +from .padding import PadDelay + class DynamicalDecoupling(TransformationPass): """Dynamical decoupling insertion pass. @@ -119,6 +121,9 @@ def __init__(self, durations, dd_sequence, qubits=None, spacing=None, skip_reset self._spacing = spacing self._skip_reset_qubits = skip_reset_qubits + # temporary code until DD pass is updated + self.requires = [PadDelay()] + def run(self, dag): """Run the DynamicalDecoupling pass on dag. diff --git a/qiskit/transpiler/passes/scheduling/padding.py b/qiskit/transpiler/passes/scheduling/padding.py new file mode 100644 index 00000000000..499410dc0e5 --- /dev/null +++ b/qiskit/transpiler/passes/scheduling/padding.py @@ -0,0 +1,215 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Padding pass to fill empty timeslot.""" + +from qiskit.circuit import Qubit +from qiskit.circuit.delay import Delay +from qiskit.dagcircuit import DAGCircuit, DAGNode, DAGOutNode +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.exceptions import TranspilerError + + +class BasePadding(TransformationPass): + """The base class of padding pass. + + This pass requires one of scheduling passes to be executed before itself. + Since there are multiple scheduling strategies, the selection of scheduling + pass is left in the hands of the pass manager designer. + Once a scheduling analysis pass is run, ``node_start_time`` is generated + in the :attr:`property_set`. This information is represented by a python dictionary of + the expected instruction execution times keyed on the node instances. + Entries in the dictionary are only created for non-delay nodes. + The padding pass expects all ``DAGOpNode`` in the circuit to be scheduled. + + This base class doesn't define any sequence to interleave, but it manages + the location where the sequence is inserted, and provides a set of information necessary + to construct the proper sequence. Thus, a subclass of this pass just needs to implement + :meth:`_pad` method, in which the subclass constructs a circuit block to insert. + This mechanism removes lots of boilerplate logic to manage whole DAG circuits. + + Note that padding pass subclasses should define interleaving sequences satisfying: + + - Interleaved sequence does not change start time of other nodes + - Interleaved sequence should have total duration of the provided ``time_interval``. + + Any manipulation violating these constraints may prevent this base pass from correctly + tracking the start time of each instruction, + which may result in violation of hardware alignment constraints. + """ + + def run(self, dag: DAGCircuit): + """Run the padding pass on ``dag``. + + Args: + dag (DAGCircuit): DAG to be checked. + + Returns: + DAGCircuit: DAG with idle time filled with instructions. + + Raises: + TranspilerError: If the whole circuit or instruction is not scheduled. + """ + if "node_start_time" not in self.property_set: + raise TranspilerError( + f"The input circuit {dag.name} is not scheduled. Call one of scheduling passes " + f"before running the {self.__class__.__name__} pass." + ) + node_start_time = self.property_set["node_start_time"] + + new_dag = DAGCircuit() + + for qreg in dag.qregs.values(): + new_dag.add_qreg(qreg) + for creg in dag.cregs.values(): + new_dag.add_creg(creg) + + new_dag.name = dag.name + new_dag.metadata = dag.metadata + new_dag.unit = self.property_set["time_unit"] + new_dag.calibrations = dag.calibrations + + idle_after = {bit: 0 for bit in dag.qubits} + + # Compute fresh circuit duration from the node start time dictionary and op duration. + # Note that pre-scheduled duration may change within the alignment passes, i.e. + # if some instruction time t0 violating the hardware alignment constraint, + # the alignment pass may delay t0 and accordingly the circuit duration changes. + circuit_duration = 0 + for node in dag.topological_op_nodes(): + if node in node_start_time: + t0 = node_start_time[node] + t1 = t0 + node.op.duration + circuit_duration = max(circuit_duration, t1) + + if isinstance(node.op, Delay): + # The padding class considers a delay instruction as idle time + # rather than instruction. Delay node is removed so that + # we can extract non-delay predecessors. + dag.remove_op_node(node) + continue + + for bit in node.qargs: + # Find idle time from the latest instruction on the wire + idle_time = t0 - idle_after[bit] + + # Fill idle time with some sequence + if idle_time > 0: + # Find previous node on the wire, i.e. always the latest node on the wire + prev_node = next(new_dag.predecessors(new_dag.output_map[bit])) + self._pad( + dag=new_dag, + qubit=bit, + time_interval=idle_time, + next_node=node, + prev_node=prev_node, + ) + + idle_after[bit] = t1 + + new_dag.apply_operation_back(node.op, node.qargs, node.cargs) + else: + raise TranspilerError( + f"Operation {repr(node)} is likely added after the circuit is scheduled. " + "Schedule the circuit again if you transformed it." + ) + + # Add delays until the end of circuit. + for bit in new_dag.qubits: + idle_time = circuit_duration - idle_after[bit] + node = new_dag.output_map[bit] + prev_node = next(new_dag.predecessors(node)) + if idle_time > 0: + self._pad( + dag=new_dag, + qubit=bit, + time_interval=idle_time, + next_node=node, + prev_node=prev_node, + ) + + new_dag.duration = circuit_duration + + return new_dag + + def _pad( + self, + dag: DAGCircuit, + qubit: Qubit, + time_interval: int, + next_node: DAGNode, + prev_node: DAGNode, + ): + """Interleave instruction sequence in between two nodes. + + Args: + dag: DAG circuit that sequence is applied. + qubit: The wire that the sequence is applied on. + time_interval: Duration of idle time in between two nodes. + next_node: Node that follows the sequence. + prev_node: Node ahead of the sequence. + """ + raise NotImplementedError + + +class PadDelay(BasePadding): + """Padding idle time with Delay instructions. + + Consecutive delays will be merged in the output of this pass. + + .. code-block::python + + durations = InstructionDurations([("x", None, 160), ("cx", None, 800)]) + + qc = QuantumCircuit(2) + qc.delay(100, 0) + qc.x(1) + qc.cx(0, 1) + + The ASAP-scheduled circuit output may become + + .. parsed-literal:: + + ┌────────────────┐ + q_0: ┤ Delay(160[dt]) ├──■── + └─────┬───┬──────┘┌─┴─┐ + q_1: ──────┤ X ├───────┤ X ├ + └───┘ └───┘ + + Note that the additional idle time of 60dt on the ``q_0`` wire coming from the duration difference + between ``Delay`` of 100dt (``q_0``) and ``XGate`` of 160 dt (``q_1``) is absorbed in + the delay instruction on the ``q_0`` wire, i.e. in total 160 dt. + + See :class:`BasePadding` pass for details. + """ + + def __init__(self, fill_very_end: bool = True): + """Create new padding delay pass. + + Args: + fill_very_end: Set ``True`` to fill the end of circuit with delay. + """ + super().__init__() + self.fill_very_end = fill_very_end + + def _pad( + self, + dag: DAGCircuit, + qubit: Qubit, + time_interval: int, + next_node: DAGNode, + prev_node: DAGNode, + ): + if not self.fill_very_end and isinstance(next_node, DAGOutNode): + return + + dag.apply_operation_back(Delay(time_interval, dag.unit), [qubit]) diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 168d6515540..498b58acce5 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -49,6 +49,7 @@ from qiskit.transpiler.passes import AlignMeasures from qiskit.transpiler.passes import ValidatePulseGates from qiskit.transpiler.passes import PulseGates +from qiskit.transpiler.passes import PadDelay from qiskit.transpiler.passes import Error from qiskit.transpiler.passes import ContainsInstruction @@ -221,9 +222,9 @@ def _contains_delay(property_set): if scheduling_method: _scheduling += _time_unit_conversion if scheduling_method in {"alap", "as_late_as_possible"}: - _scheduling += [ALAPSchedule(instruction_durations)] + _scheduling += [ALAPSchedule(instruction_durations), PadDelay()] elif scheduling_method in {"asap", "as_soon_as_possible"}: - _scheduling += [ASAPSchedule(instruction_durations)] + _scheduling += [ASAPSchedule(instruction_durations), PadDelay()] else: raise TranspilerError("Invalid scheduling method %s." % scheduling_method) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 86d2de27cb9..04d00292ac6 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -55,6 +55,7 @@ from qiskit.transpiler.passes import AlignMeasures from qiskit.transpiler.passes import ValidatePulseGates from qiskit.transpiler.passes import PulseGates +from qiskit.transpiler.passes import PadDelay from qiskit.transpiler.passes import Error from qiskit.transpiler.passes import ContainsInstruction from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason @@ -290,9 +291,9 @@ def _contains_delay(property_set): if scheduling_method: _scheduling += _time_unit_conversion if scheduling_method in {"alap", "as_late_as_possible"}: - _scheduling += [ALAPSchedule(instruction_durations)] + _scheduling += [ALAPSchedule(instruction_durations), PadDelay()] elif scheduling_method in {"asap", "as_soon_as_possible"}: - _scheduling += [ASAPSchedule(instruction_durations)] + _scheduling += [ASAPSchedule(instruction_durations), PadDelay()] else: raise TranspilerError("Invalid scheduling method %s." % scheduling_method) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index a091b992d85..ede15de5444 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -55,6 +55,7 @@ from qiskit.transpiler.passes import AlignMeasures from qiskit.transpiler.passes import ValidatePulseGates from qiskit.transpiler.passes import PulseGates +from qiskit.transpiler.passes import PadDelay from qiskit.transpiler.passes import Error from qiskit.transpiler.passes import ContainsInstruction from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason @@ -278,9 +279,9 @@ def _contains_delay(property_set): if scheduling_method: _scheduling += _time_unit_conversion if scheduling_method in {"alap", "as_late_as_possible"}: - _scheduling += [ALAPSchedule(instruction_durations)] + _scheduling += [ALAPSchedule(instruction_durations), PadDelay()] elif scheduling_method in {"asap", "as_soon_as_possible"}: - _scheduling += [ASAPSchedule(instruction_durations)] + _scheduling += [ASAPSchedule(instruction_durations), PadDelay()] else: raise TranspilerError("Invalid scheduling method %s." % scheduling_method) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index d8e6a70448b..62adb0f10c6 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -58,6 +58,7 @@ from qiskit.transpiler.passes import AlignMeasures from qiskit.transpiler.passes import ValidatePulseGates from qiskit.transpiler.passes import PulseGates +from qiskit.transpiler.passes import PadDelay from qiskit.transpiler.passes import Error from qiskit.transpiler.passes import ContainsInstruction from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason @@ -288,9 +289,9 @@ def _contains_delay(property_set): if scheduling_method: _scheduling += _time_unit_conversion if scheduling_method in {"alap", "as_late_as_possible"}: - _scheduling += [ALAPSchedule(instruction_durations)] + _scheduling += [ALAPSchedule(instruction_durations), PadDelay()] elif scheduling_method in {"asap", "as_soon_as_possible"}: - _scheduling += [ASAPSchedule(instruction_durations)] + _scheduling += [ASAPSchedule(instruction_durations), PadDelay()] else: raise TranspilerError("Invalid scheduling method %s." % scheduling_method) diff --git a/releasenotes/notes/fix-algorithms-7f1b969e5b2447f9.yaml b/releasenotes/notes/fix-algorithms-7f1b969e5b2447f9.yaml new file mode 100644 index 00000000000..6dd7ab4ae46 --- /dev/null +++ b/releasenotes/notes/fix-algorithms-7f1b969e5b2447f9.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + :class:`qiskit.algorithms.amplitude_estimators.AmplitudeEstimator` now inherits from ABC. + :class:`qiskit.algorithms.amplitude_amplifiers.AmplitudeAmplifier` is now included in + ``qiskit/algorithms/__init__.py`` file. diff --git a/releasenotes/notes/fix-hhl_construct_circuit-nl-size-03cbfba9ed50a57a.yaml b/releasenotes/notes/fix-hhl_construct_circuit-nl-size-03cbfba9ed50a57a.yaml new file mode 100644 index 00000000000..aadfc51ab11 --- /dev/null +++ b/releasenotes/notes/fix-hhl_construct_circuit-nl-size-03cbfba9ed50a57a.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Fixes an issue where HHL().construct_circuit() didn't always return a correct circuit. Issue arose for some matrices due to not allocating enough qubits to represent the eigenvalues. diff --git a/releasenotes/notes/paulisumop-may-equal-pauliop-af86de94020fba22.yaml b/releasenotes/notes/paulisumop-may-equal-pauliop-af86de94020fba22.yaml new file mode 100644 index 00000000000..9482d8450dc --- /dev/null +++ b/releasenotes/notes/paulisumop-may-equal-pauliop-af86de94020fba22.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + :class:`~qiskit.opflow.PauliSumOp` equality tests now handle the case when + one of the compared items is a single :class:`~qiskit.opflow.PauliOp`. + For example, ``0 * X + I == I`` now evaluates to True, whereas it was + False prior to this release. diff --git a/releasenotes/notes/rust-denselayout-bc0f08874ad778d6.yaml b/releasenotes/notes/rust-denselayout-bc0f08874ad778d6.yaml new file mode 100644 index 00000000000..6aebf82d7ff --- /dev/null +++ b/releasenotes/notes/rust-denselayout-bc0f08874ad778d6.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + The :class:`~.DenseLayout` transpiler pass is now multithreaded, which + greatly improves the runtime performance of the pass. By default, it will + use the number of logical CPUs on your local system, but you can control + the number of threads used by the pass by setting the + ``RAYON_NUM_THREADS`` environment variable to an integer value. For + example, setting ``RAYON_NUM_THREADS=4`` will run the + :class:`~.DenseLayout` pass with 4 threads. diff --git a/releasenotes/notes/rust-pauli-expval-f2aa06c5bab85768.yaml b/releasenotes/notes/rust-pauli-expval-f2aa06c5bab85768.yaml new file mode 100644 index 00000000000..bd2ece87999 --- /dev/null +++ b/releasenotes/notes/rust-pauli-expval-f2aa06c5bab85768.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + The internal computations of :class:`.Statevector.expectation_value` and + :class:`.DensityMatrix.expectation_value methods have been reimplemented + in the Rust programming language. This new implementation is multithreaded + and by default for a :class:`~.Statevector` or :class:`~.DensityMatrix` + >= 19 qubits will spawn a thread pool with the number of logical CPUs + available on the local system. You can you can control the number of + threads used by setting the ``RAYON_NUM_THREADS`` environment variable to + an integer value. For example, setting ``RAYON_NUM_THREADS=4`` will only + use 4 threads in the thread pool. +upgrade: + - | + Cython is no longer a build dependency of Qiskit Terra and is no longer + required to be installed when building Qiskit Terra from source. diff --git a/releasenotes/notes/time-evo-framework-9d58827bdbbebd62.yaml b/releasenotes/notes/time-evo-framework-9d58827bdbbebd62.yaml new file mode 100644 index 00000000000..06bc667a96d --- /dev/null +++ b/releasenotes/notes/time-evo-framework-9d58827bdbbebd62.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + Interfaces for the unified framework for Quantum Time Evolution are introduced. + :class:`~qiskit.algorithms.EvolutionProblem` defines an evolution problem and + can be passed to any evolution algorithm available in the framework. + :class:`~qiskit.algorithms.ImaginaryEvolver` and + :class:`~qiskit.algorithms.RealEvolver` are interfaces for + imaginary and real time evolution cases respectively. They serve as a base for any time + evolution algorithm for evolving quantum states, including evolutions based on + time-dependent Hamiltonians. + :class:`~qiskit.algorithms.EvolutionResult` is introduced as a result object + for quantum time evolution algorithms. diff --git a/releasenotes/notes/upgrade-alap-asap-passes-bcacc0f1053c9828.yaml b/releasenotes/notes/upgrade-alap-asap-passes-bcacc0f1053c9828.yaml index 2199eb68651..6a958d5ba26 100644 --- a/releasenotes/notes/upgrade-alap-asap-passes-bcacc0f1053c9828.yaml +++ b/releasenotes/notes/upgrade-alap-asap-passes-bcacc0f1053c9828.yaml @@ -38,7 +38,7 @@ upgrade: from qiskit import QuantumCircuit from qiskit.transpiler import InstructionDurations, PassManager - from qiskit.transpiler.passes import ALAPSchedule + from qiskit.transpiler.passes import ALAPSchedule, PadDelay from qiskit.visualization.timeline import draw circuit = QuantumCircuit(3, 1) @@ -50,7 +50,10 @@ upgrade: durations = InstructionDurations([("x", None, 160), ("measure", None, 800)]) pm = PassManager( - ALAPSchedule(durations, clbit_write_latency=800, conditional_latency=0) + [ + ALAPSchedule(durations, clbit_write_latency=800, conditional_latency=0), + PadDelay(), + ] ) draw(pm.run(circuit)) @@ -70,7 +73,7 @@ upgrade: from qiskit import QuantumCircuit from qiskit.transpiler import InstructionDurations, PassManager - from qiskit.transpiler.passes import ALAPSchedule + from qiskit.transpiler.passes import ALAPSchedule, PadDelay from qiskit.visualization.timeline import draw circuit = QuantumCircuit(3, 1) @@ -81,7 +84,7 @@ upgrade: durations = InstructionDurations([("x", None, 160), ("measure", None, 800)]) - pm = PassManager(ALAPSchedule(durations)) + pm = PassManager([ALAPSchedule(durations), PadDelay()]) draw(pm.run(circuit)) Note that clbit is locked throughout the measurement instruction interval. diff --git a/releasenotes/notes/upgrade-convert-scheduling-passes-to-analysis-04333b6fef524d21.yaml b/releasenotes/notes/upgrade-convert-scheduling-passes-to-analysis-04333b6fef524d21.yaml new file mode 100644 index 00000000000..eef4e6c7fa3 --- /dev/null +++ b/releasenotes/notes/upgrade-convert-scheduling-passes-to-analysis-04333b6fef524d21.yaml @@ -0,0 +1,41 @@ +--- +features: + - | + New transpiler pass :class:`~qiskit.transpiler.passes.scheduling.padding.PadDelay` + has been added. This pass fills idle time on the qubit wire with ``Delay`` instruction. + The input DAG should be scheduled in advance. +upgrade: + - | + Scheduling passes :class:`~qiskit.transpiler.passes.scheduling.asap.ASAPSchedule` and + :class:`~qiskit.transpiler.passes.scheduling.alap.ALAPSchedule` have been turned into + analysis pass that does not modify the input DAG circuit. + Now these passes just analyze the time slot allocation of every ``DAGOpNode``s and + create a ``node_start_time`` dictionary in the pass manager property set, + instead of immediately injecting delays in between nodes with idle time. + + Note that when directly assembling one's own pass manager, one should add + :class:`~qiskit.transpiler.passes.scheduling.padding.PadDelay` pass after the + scheduling pass for the delays to be applied to the circuit. + + In Qiskit Terra < 0.20 + + .. code-block::python + + from qiskit.transpiler.passes import ASAPSchedule + from qiskit.transpiler.passmanager import PassManager + + pm = PassManager([ASAPSchedule(durations)]) + + In Qiskit Terra >= 0.20 + + .. code-block::python + + from qiskit.transpiler.passes import ASAPSchedule, PadDelay + from qiskit.transpiler.passmanager import PassManager + + pm = PassManager([ASAPSchedule(durations), PadDelay()]) + + This upgrade improves the performance of the entire pass manager, especially with + one of the alignment passes, or with a dynamical decoupling pass since + input DAG circuit is regenerated only once after the scheduling, + and the instruction start time is explicitly tracked in the pass manager. diff --git a/setup.py b/setup.py index b40376fbc29..ff6195e8478 100755 --- a/setup.py +++ b/setup.py @@ -18,51 +18,10 @@ from setuptools import setup, find_packages, Extension from setuptools_rust import Binding, RustExtension -try: - from Cython.Build import cythonize -except ImportError: - import subprocess - - subprocess.call([sys.executable, "-m", "pip", "install", "Cython>=0.27.1"]) - from Cython.Build import cythonize - with open("requirements.txt") as f: REQUIREMENTS = f.read().splitlines() -# Add Cython extensions here -CYTHON_EXTS = { - "qiskit/quantum_info/states/cython/exp_value": "qiskit.quantum_info.states.cython.exp_value", -} - -INCLUDE_DIRS = [] -# Extra link args -LINK_FLAGS = [] -# If on Win and not in MSYS2 (i.e. Visual studio compile) -if sys.platform == "win32" and os.environ.get("MSYSTEM") is None: - COMPILER_FLAGS = ["/O2"] -# Everything else -else: - COMPILER_FLAGS = ["-O2", "-funroll-loops", "-std=c++11"] - if sys.platform == "darwin": - # These are needed for compiling on OSX 10.14+ - COMPILER_FLAGS.append("-mmacosx-version-min=10.9") - LINK_FLAGS.append("-mmacosx-version-min=10.9") - - -EXT_MODULES = [] -# Add Cython Extensions -for src, module in CYTHON_EXTS.items(): - ext = Extension( - module, - sources=[src + ".pyx"], - include_dirs=INCLUDE_DIRS, - extra_compile_args=COMPILER_FLAGS, - extra_link_args=LINK_FLAGS, - language="c++", - ) - EXT_MODULES.append(ext) - # Read long description from README. README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), "README.md") with open(README_PATH) as readme_file: @@ -117,7 +76,6 @@ keywords="qiskit sdk quantum", packages=find_packages(exclude=["test*"]), install_requires=REQUIREMENTS, - setup_requires=["Cython>=0.27.1"], include_package_data=True, python_requires=">=3.7", extras_require={ @@ -134,7 +92,6 @@ "Documentation": "https://qiskit.org/documentation/", "Source Code": "https://github.com/Qiskit/qiskit-terra", }, - ext_modules=cythonize(EXT_MODULES), rust_extensions=[RustExtension("qiskit._accelerate", "Cargo.toml", binding=Binding.PyO3)], zip_safe=False, entry_points={ diff --git a/src/dense_layout.rs b/src/dense_layout.rs new file mode 100644 index 00000000000..487b061ad0b --- /dev/null +++ b/src/dense_layout.rs @@ -0,0 +1,231 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2022 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. +#![allow(clippy::too_many_arguments)] + +use ahash::RandomState; +use hashbrown::{HashMap, HashSet}; +use indexmap::IndexSet; +use ndarray::prelude::*; +use numpy::PyReadonlyArray2; +use numpy::ToPyArray; +use rayon::prelude::*; + +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; +use pyo3::Python; + +use crate::getenv_use_multiple_threads; + +struct SubsetResult { + pub count: usize, + pub error: f64, + pub map: Vec, + pub subgraph: Vec<[usize; 2]>, +} + +fn bfs_sort(adj_matrix: ArrayView2, start: usize, num_qubits: usize) -> Vec { + let n = adj_matrix.shape()[0]; + let mut next_level: IndexSet = + IndexSet::with_hasher(RandomState::default()); + let mut bfs_order = Vec::with_capacity(num_qubits); + let mut seen: HashSet = HashSet::with_capacity(n); + next_level.insert(start); + while !next_level.is_empty() { + let this_level = next_level; + next_level = IndexSet::with_hasher(RandomState::default()); + let mut found: Vec = Vec::new(); + for v in this_level { + if !seen.contains(&v) { + seen.insert(v); + found.push(v); + bfs_order.push(v); + if bfs_order.len() == num_qubits { + return bfs_order; + } + } + } + if seen.len() == n { + return bfs_order; + } + for node in found { + for (idx, v) in adj_matrix.index_axis(Axis(0), node).iter().enumerate() { + if *v != 0. { + next_level.insert(idx); + } + } + for (idx, v) in adj_matrix.index_axis(Axis(1), node).iter().enumerate() { + if *v != 0. { + next_level.insert(idx); + } + } + } + } + bfs_order +} + +/// Find the best subset in the coupling graph +/// +/// This function will find the best densely connected subgraph in the +/// coupling graph to run the circuit on. It factors in measurement error and +/// cx error if specified. +/// +/// Args: +/// +/// num_qubits (int): The number of circuit qubits +/// coupling_adjacency (numpy.ndarray): An adjacency matrix for the +/// coupling graph. +/// num_meas (int): The number of measurement operations in the circuit +/// num_cx (int): The number of CXGates that are in the circuit +/// use_error (bool): Set to True to use the error +/// symmetric_coupling_map (bool): Is the coupling graph symmetric +/// error_matrix (numpy.ndarray): A 2D array that represents the error +/// rates on the target device, where the indices are physical qubits. +/// The diagonal (i.e. ``error_matrix[i][i]``) is the measurement error rate +/// for each qubit (``i``) and the positions where the indices differ are the +/// 2q/cx error rate for the corresponding qubit pair. +/// +/// Returns: +/// (rows, cols, best_map): A tuple of the rows, columns and the best +/// mapping found by the function. This can be used to efficiently create +/// a sparse matrix that maps the layout of virtual qubits +/// (0 to ``num_qubits``) to the physical qubits on the coupling graph. +#[pyfunction] +pub fn best_subset( + py: Python, + num_qubits: usize, + coupling_adjacency: PyReadonlyArray2, + num_meas: usize, + num_cx: usize, + use_error: bool, + symmetric_coupling_map: bool, + error_matrix: PyReadonlyArray2, +) -> PyResult<(PyObject, PyObject, PyObject)> { + let coupling_adj_mat = coupling_adjacency.as_array(); + let coupling_shape = coupling_adj_mat.shape(); + let err = error_matrix.as_array(); + let avg_meas_err = err.diag().mean().unwrap(); + + let map_fn = |k| -> SubsetResult { + let mut subgraph: Vec<[usize; 2]> = Vec::with_capacity(num_qubits); + let bfs = bfs_sort(coupling_adj_mat, k, num_qubits); + let bfs_set: HashSet = bfs.iter().copied().collect(); + let mut connection_count = 0; + for node_idx in &bfs { + coupling_adj_mat + .index_axis(Axis(0), *node_idx) + .into_iter() + .enumerate() + .filter_map(|(node, j)| { + if *j != 0. && bfs_set.contains(&node) { + Some(node) + } else { + None + } + }) + .for_each(|node| { + connection_count += 1; + subgraph.push([*node_idx, node]); + }); + } + let error = if use_error { + let mut ret_error = 0.; + let meas_avg = bfs + .iter() + .map(|i| { + let idx = *i; + err[[idx, idx]] + }) + .sum::() + / num_qubits as f64; + let meas_diff = meas_avg - avg_meas_err; + if meas_diff > 0. { + ret_error += num_meas as f64 * meas_diff; + } + let cx_sum: f64 = subgraph.iter().map(|edge| err[[edge[0], edge[1]]]).sum(); + let mut cx_err = cx_sum / subgraph.len() as f64; + if symmetric_coupling_map { + cx_err /= 2.; + } + ret_error += num_cx as f64 * cx_err; + ret_error + } else { + 0. + }; + SubsetResult { + count: connection_count, + error, + map: bfs, + subgraph, + } + }; + + let reduce_identity_fn = || -> SubsetResult { + SubsetResult { + count: 0, + map: Vec::new(), + error: std::f64::INFINITY, + subgraph: Vec::new(), + } + }; + + let reduce_fn = |best: SubsetResult, curr: SubsetResult| -> SubsetResult { + if use_error { + if curr.count >= best.count && curr.error < best.error { + curr + } else { + best + } + } else if curr.count > best.count { + curr + } else { + best + } + }; + + let best_result = if getenv_use_multiple_threads() { + (0..coupling_shape[0]) + .into_par_iter() + .map(map_fn) + .reduce(reduce_identity_fn, reduce_fn) + } else { + (0..coupling_shape[0]) + .into_iter() + .map(map_fn) + .reduce(reduce_fn) + .unwrap() + }; + let best_map: Vec = best_result.map; + let mapping: HashMap = best_map + .iter() + .enumerate() + .map(|(best_edge, edge)| (*edge, best_edge)) + .collect(); + let new_cmap: Vec<[usize; 2]> = best_result + .subgraph + .iter() + .map(|c| [mapping[&c[0]], mapping[&c[1]]]) + .collect(); + let rows: Vec = new_cmap.iter().map(|edge| edge[0]).collect(); + let cols: Vec = new_cmap.iter().map(|edge| edge[1]).collect(); + + Ok(( + rows.to_pyarray(py).into(), + cols.to_pyarray(py).into(), + best_map.to_pyarray(py).into(), + )) +} + +#[pymodule] +pub fn dense_layout(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(best_subset))?; + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 46e140a2b63..12ff7e26830 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,16 +10,35 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +use std::env; + use pyo3::prelude::*; use pyo3::wrap_pymodule; use pyo3::Python; +mod dense_layout; mod edge_collections; mod nlayout; +mod pauli_exp_val; mod stochastic_swap; +#[inline] +pub fn getenv_use_multiple_threads() -> bool { + let parallel_context = env::var("QISKIT_IN_PARALLEL") + .unwrap_or_else(|_| "FALSE".to_string()) + .to_uppercase() + == "TRUE"; + let force_threads = env::var("QISKIT_FORCE_THREADS") + .unwrap_or_else(|_| "FALSE".to_string()) + .to_uppercase() + == "TRUE"; + !parallel_context || force_threads +} + #[pymodule] fn _accelerate(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(stochastic_swap::stochastic_swap))?; + m.add_wrapped(wrap_pymodule!(pauli_exp_val::pauli_expval))?; + m.add_wrapped(wrap_pymodule!(dense_layout::dense_layout))?; Ok(()) } diff --git a/src/pauli_exp_val.rs b/src/pauli_exp_val.rs new file mode 100644 index 00000000000..c821757cf5d --- /dev/null +++ b/src/pauli_exp_val.rs @@ -0,0 +1,222 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2022 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use std::convert::TryInto; + +use num_complex::Complex64; +use numpy::PyReadonlyArray1; +use pyo3::exceptions::PyOverflowError; +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; +use rayon::prelude::*; + +use crate::getenv_use_multiple_threads; + +const LANES: usize = 8; +const PARALLEL_THRESHOLD: usize = 19; + +// Based on the sum implementation in: +// https://stackoverflow.com/a/67191480/14033130 +// and adjust for f64 usage +#[inline] +fn fast_sum(values: &[f64]) -> f64 { + let chunks = values.chunks_exact(LANES); + let remainder = chunks.remainder(); + + let sum = chunks.fold([0.; LANES], |mut acc, chunk| { + let chunk: [f64; LANES] = chunk.try_into().unwrap(); + for i in 0..LANES { + acc[i] += chunk[i]; + } + acc + }); + let remainder: f64 = remainder.iter().copied().sum(); + + let mut reduced = 0.; + for val in sum { + reduced += val; + } + reduced + remainder +} + +/// Compute the pauli expectatation value of a statevector without x +#[pyfunction] +#[pyo3(text_signature = "(data, num_qubits, z_mask, /)")] +pub fn expval_pauli_no_x( + data: PyReadonlyArray1, + num_qubits: usize, + z_mask: usize, +) -> PyResult { + if num_qubits >= usize::BITS as usize { + return Err(PyOverflowError::new_err(format!( + "The value for num_qubits, {}, is too large and would overflow", + num_qubits + ))); + } + let data_arr = data.as_slice()?; + let size = 1_usize << num_qubits; + let run_in_parallel = getenv_use_multiple_threads(); + let map_fn = |i: usize| -> f64 { + let mut val: f64 = data_arr[i].re * data_arr[i].re + data_arr[i].im * data_arr[i].im; + if (i & z_mask).count_ones() & 1 != 0 { + val *= -1.; + } + val + }; + + if num_qubits < PARALLEL_THRESHOLD || !run_in_parallel { + Ok(fast_sum(&(0..size).map(map_fn).collect::>())) + } else { + Ok((0..size).into_par_iter().map(map_fn).sum()) + } +} + +/// Compute the pauli expectatation value of a statevector with x +#[pyfunction] +#[pyo3(text_signature = "(data, num_qubits, z_mask, x_mask, phase, x_max, /)")] +pub fn expval_pauli_with_x( + data: PyReadonlyArray1, + num_qubits: usize, + z_mask: usize, + x_mask: usize, + phase: Complex64, + x_max: u32, +) -> PyResult { + if num_qubits > usize::BITS as usize { + return Err(PyOverflowError::new_err(format!( + "The value for num_qubits, {}, is too large and would overflow", + num_qubits + ))); + } + let data_arr = data.as_slice()?; + let mask_u = !(2_usize.pow(x_max + 1) - 1); + let mask_l = 2_usize.pow(x_max) - 1; + let size = 1_usize << (num_qubits - 1); + let run_in_parallel = getenv_use_multiple_threads(); + let map_fn = |i: usize| -> f64 { + let index_0 = ((i << 1) & mask_u) | (i & mask_l); + let index_1 = index_0 ^ x_mask; + let val_0 = (phase + * Complex64::new( + data_arr[index_1].re * data_arr[index_0].re + + data_arr[index_1].im * data_arr[index_0].im, + data_arr[index_1].im * data_arr[index_0].re + - data_arr[index_1].re * data_arr[index_0].im, + )) + .re; + let val_1 = (phase + * Complex64::new( + data_arr[index_0].re * data_arr[index_1].re + + data_arr[index_0].im * data_arr[index_1].im, + data_arr[index_0].im * data_arr[index_1].re + - data_arr[index_0].re * data_arr[index_1].im, + )) + .re; + let mut val = val_0; + if (index_0 & z_mask).count_ones() & 1 != 0 { + val *= -1. + } + if (index_1 & z_mask).count_ones() & 1 != 0 { + val -= val_1; + } else { + val += val_1; + } + val + }; + if num_qubits < PARALLEL_THRESHOLD || !run_in_parallel { + Ok(fast_sum(&(0..size).map(map_fn).collect::>())) + } else { + Ok((0..size).into_par_iter().map(map_fn).sum()) + } +} + +/// Compute the pauli expectatation value of a density matrix without x +#[pyfunction] +#[pyo3(text_signature = "(data, num_qubits, z_mask, /)")] +pub fn density_expval_pauli_no_x( + data: PyReadonlyArray1, + num_qubits: usize, + z_mask: usize, +) -> PyResult { + if num_qubits >= usize::BITS as usize { + return Err(PyOverflowError::new_err(format!( + "The value for num_qubits, {}, is too large and would overflow", + num_qubits + ))); + } + let data_arr = data.as_slice()?; + let num_rows = 1_usize << num_qubits; + let stride = 1 + num_rows; + let run_in_parallel = getenv_use_multiple_threads(); + let map_fn = |i: usize| -> f64 { + let index = i * stride; + let mut val = data_arr[index].re; + if (i & z_mask).count_ones() & 1 != 0 { + val *= -1.; + } + val + }; + if num_qubits < PARALLEL_THRESHOLD || !run_in_parallel { + Ok(fast_sum(&(0..num_rows).map(map_fn).collect::>())) + } else { + Ok((0..num_rows).into_par_iter().map(map_fn).sum()) + } +} + +/// Compute the pauli expectatation value of a density matrix with x +#[pyfunction] +#[pyo3(text_signature = "(data, num_qubits, z_mask, x_mask, phase, x_max, /)")] +pub fn density_expval_pauli_with_x( + data: PyReadonlyArray1, + num_qubits: usize, + z_mask: usize, + x_mask: usize, + phase: Complex64, + x_max: u32, +) -> PyResult { + if num_qubits >= usize::BITS as usize { + return Err(PyOverflowError::new_err(format!( + "The value for num_qubits, {}, is too large and would overflow", + num_qubits + ))); + } + let data_arr = data.as_slice()?; + let mask_u = !(2_usize.pow(x_max + 1) - 1); + let mask_l = 2_usize.pow(x_max) - 1; + let num_rows = 1_usize << num_qubits; + let run_in_parallel = getenv_use_multiple_threads(); + let map_fn = |i: usize| -> f64 { + let index_vec = ((i << 1) & mask_u) | (i & mask_l); + let index_mat = (index_vec ^ x_mask) + num_rows * index_vec; + let mut val = 2. * (phase * data_arr[index_mat]).re; + if (index_vec & z_mask).count_ones() & 1 != 0 { + val *= -1. + } + val + }; + if num_qubits < PARALLEL_THRESHOLD || !run_in_parallel { + Ok(fast_sum( + &(0..num_rows >> 1).map(map_fn).collect::>(), + )) + } else { + Ok((0..num_rows >> 1).into_par_iter().map(map_fn).sum()) + } +} + +#[pymodule] +pub fn pauli_expval(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(expval_pauli_no_x))?; + m.add_wrapped(wrap_pyfunction!(expval_pauli_with_x))?; + m.add_wrapped(wrap_pyfunction!(density_expval_pauli_with_x))?; + m.add_wrapped(wrap_pyfunction!(density_expval_pauli_no_x))?; + Ok(()) +} diff --git a/src/stochastic_swap.rs b/src/stochastic_swap.rs index 03333502483..c03b8f1ca97 100644 --- a/src/stochastic_swap.rs +++ b/src/stochastic_swap.rs @@ -15,7 +15,6 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] -use std::env; use std::sync::RwLock; use hashbrown::HashSet; @@ -33,6 +32,7 @@ use rand_distr::{Distribution, Normal}; use rand_pcg::Pcg64Mcg; use crate::edge_collections::EdgeCollection; +use crate::getenv_use_multiple_threads; use crate::nlayout::NLayout; #[inline] @@ -268,15 +268,7 @@ pub fn swap_trials( .collect(); // Run in parallel only if we're not already in a multiprocessing context // unless force threads is set. - let parallel_context = env::var("QISKIT_IN_PARALLEL") - .unwrap_or_else(|_| "FALSE".to_string()) - .to_uppercase() - == "TRUE"; - let force_threads = env::var("QISKIT_FORCE_THREADS") - .unwrap_or_else(|_| "FALSE".to_string()) - .to_uppercase() - == "TRUE"; - let run_in_parallel = !parallel_context || force_threads; + let run_in_parallel = getenv_use_multiple_threads(); let mut best_depth = std::usize::MAX; let mut best_edges: Option = None; diff --git a/test/python/algorithms/evolvers/__init__.py b/test/python/algorithms/evolvers/__init__.py new file mode 100644 index 00000000000..fdb172d367f --- /dev/null +++ b/test/python/algorithms/evolvers/__init__.py @@ -0,0 +1,11 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/test/python/algorithms/evolvers/test_evolution_problem.py b/test/python/algorithms/evolvers/test_evolution_problem.py new file mode 100644 index 00000000000..cdd47224704 --- /dev/null +++ b/test/python/algorithms/evolvers/test_evolution_problem.py @@ -0,0 +1,76 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test evolver problem class.""" +import unittest + +from test.python.algorithms import QiskitAlgorithmsTestCase +from qiskit.algorithms.evolvers.evolution_problem import EvolutionProblem +from qiskit.circuit import Parameter +from qiskit.opflow import Y, Z, One, X + + +class TestEvolutionProblem(QiskitAlgorithmsTestCase): + """Test evolver problem class.""" + + def test_init_default(self): + """Tests that all default fields are initialized correctly.""" + hamiltonian = Y + time = 2.5 + initial_state = One + + evo_problem = EvolutionProblem(hamiltonian, time, initial_state) + + expected_hamiltonian = Y + expected_time = 2.5 + expected_initial_state = One + expected_aux_operators = None + expected_t_param = None + expected_hamiltonian_value_dict = None + + self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) + self.assertEqual(evo_problem.time, expected_time) + self.assertEqual(evo_problem.initial_state, expected_initial_state) + self.assertEqual(evo_problem.aux_operators, expected_aux_operators) + self.assertEqual(evo_problem.t_param, expected_t_param) + self.assertEqual(evo_problem.hamiltonian_value_dict, expected_hamiltonian_value_dict) + + def test_init_all(self): + """Tests that all fields are initialized correctly.""" + t_parameter = Parameter("t") + hamiltonian = t_parameter * Z + Y + time = 2 + initial_state = One + aux_operators = [X, Y] + hamiltonian_value_dict = {t_parameter: 3.2} + + evo_problem = EvolutionProblem( + hamiltonian, time, initial_state, aux_operators, t_parameter, hamiltonian_value_dict + ) + + expected_hamiltonian = Y + t_parameter * Z + expected_time = 2 + expected_initial_state = One + expected_aux_operators = [X, Y] + expected_t_param = t_parameter + expected_hamiltonian_value_dict = {t_parameter: 3.2} + + self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) + self.assertEqual(evo_problem.time, expected_time) + self.assertEqual(evo_problem.initial_state, expected_initial_state) + self.assertEqual(evo_problem.aux_operators, expected_aux_operators) + self.assertEqual(evo_problem.t_param, expected_t_param) + self.assertEqual(evo_problem.hamiltonian_value_dict, expected_hamiltonian_value_dict) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/algorithms/evolvers/test_evolution_result.py b/test/python/algorithms/evolvers/test_evolution_result.py new file mode 100644 index 00000000000..5500b283a1c --- /dev/null +++ b/test/python/algorithms/evolvers/test_evolution_result.py @@ -0,0 +1,48 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +"""Class for testing evolution result.""" +import unittest + +from test.python.algorithms import QiskitAlgorithmsTestCase +from qiskit.algorithms.evolvers.evolution_result import EvolutionResult +from qiskit.opflow import Zero + + +class TestEvolutionResult(QiskitAlgorithmsTestCase): + """Class for testing evolution result and relevant metadata.""" + + def test_init_state(self): + """Tests that a class is initialized correctly with an evolved_state.""" + evolved_state = Zero + evo_result = EvolutionResult(evolved_state=evolved_state) + + expected_state = Zero + expected_aux_ops_evaluated = None + + self.assertEqual(evo_result.evolved_state, expected_state) + self.assertEqual(evo_result.aux_ops_evaluated, expected_aux_ops_evaluated) + + def test_init_observable(self): + """Tests that a class is initialized correctly with an evolved_observable.""" + evolved_state = Zero + evolved_aux_ops_evaluated = [(5j, 5j), (1.0, 8j), (5 + 1j, 6 + 1j)] + evo_result = EvolutionResult(evolved_state, evolved_aux_ops_evaluated) + + expected_state = Zero + expected_aux_ops_evaluated = [(5j, 5j), (1.0, 8j), (5 + 1j, 6 + 1j)] + + self.assertEqual(evo_result.evolved_state, expected_state) + self.assertEqual(evo_result.aux_ops_evaluated, expected_aux_ops_evaluated) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/algorithms/test_linear_solvers.py b/test/python/algorithms/test_linear_solvers.py index a63b2a7faf2..f25c19376e2 100644 --- a/test/python/algorithms/test_linear_solvers.py +++ b/test/python/algorithms/test_linear_solvers.py @@ -236,6 +236,12 @@ class TestLinearSolver(QiskitAlgorithmsTestCase): [1.0, -2.1, 3.2, -4.3], MatrixFunctional(1, 1 / 2), ], + [ + np.array([[82, 34], [34, 58]]), + np.array([[1], [0]]), + AbsoluteAverage(), + 3, + ], [ TridiagonalToeplitz(3, 1, -1 / 2, trotter_steps=2), [-9 / 4, -0.3, 8 / 7, 10, -5, 11.1, 13 / 11, -27 / 12], @@ -244,7 +250,7 @@ class TestLinearSolver(QiskitAlgorithmsTestCase): ] ) @unpack - def test_hhl(self, matrix, right_hand_side, observable): + def test_hhl(self, matrix, right_hand_side, observable, decimal=1): """Test the HHL class.""" if isinstance(matrix, QuantumCircuit): num_qubits = matrix.num_state_qubits @@ -272,7 +278,7 @@ def test_hhl(self, matrix, right_hand_side, observable): exact_x = np.dot(np.linalg.inv(matrix), rhs) exact_result = observable.evaluate_classically(exact_x) - np.testing.assert_almost_equal(approx_result, exact_result, decimal=1) + np.testing.assert_almost_equal(approx_result, exact_result, decimal=decimal) def test_hhl_qi(self): """Test the HHL quantum instance getter and setter.""" diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index 502d6899e50..d95807e5d9e 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -46,20 +46,20 @@ def test_schedule_circuit_when_backend_tells_dt(self): sc = transpile(qc, self.backend_with_dt, scheduling_method="alap", layout_method="trivial") self.assertEqual(sc.duration, 450610) self.assertEqual(sc.unit, "dt") - self.assertEqual(sc.data[1][0].name, "delay") - self.assertEqual(sc.data[1][0].duration, 450) + self.assertEqual(sc.data[0][0].name, "delay") + self.assertEqual(sc.data[0][0].duration, 450450) + self.assertEqual(sc.data[0][0].unit, "dt") + self.assertEqual(sc.data[1][0].name, "rz") + self.assertEqual(sc.data[1][0].duration, 0) self.assertEqual(sc.data[1][0].unit, "dt") - self.assertEqual(sc.data[2][0].name, "rz") - self.assertEqual(sc.data[2][0].duration, 0) - self.assertEqual(sc.data[2][0].unit, "dt") - self.assertEqual(sc.data[5][0].name, "delay") - self.assertEqual(sc.data[5][0].duration, 450450) - self.assertEqual(sc.data[5][0].unit, "dt") + self.assertEqual(sc.data[4][0].name, "delay") + self.assertEqual(sc.data[4][0].duration, 450450) + self.assertEqual(sc.data[4][0].unit, "dt") qobj = assemble(sc, self.backend_with_dt) - self.assertEqual(qobj.experiments[0].instructions[1].name, "delay") - self.assertEqual(qobj.experiments[0].instructions[1].params[0], 450) - self.assertEqual(qobj.experiments[0].instructions[5].name, "delay") - self.assertEqual(qobj.experiments[0].instructions[5].params[0], 450450) + self.assertEqual(qobj.experiments[0].instructions[0].name, "delay") + self.assertEqual(qobj.experiments[0].instructions[0].params[0], 450450) + self.assertEqual(qobj.experiments[0].instructions[4].name, "delay") + self.assertEqual(qobj.experiments[0].instructions[4].params[0], 450450) def test_schedule_circuit_when_transpile_option_tells_dt(self): """dt is known to transpiler by transpile option""" @@ -77,15 +77,15 @@ def test_schedule_circuit_when_transpile_option_tells_dt(self): ) self.assertEqual(sc.duration, 450610) self.assertEqual(sc.unit, "dt") - self.assertEqual(sc.data[1][0].name, "delay") - self.assertEqual(sc.data[1][0].duration, 450) + self.assertEqual(sc.data[0][0].name, "delay") + self.assertEqual(sc.data[0][0].duration, 450450) + self.assertEqual(sc.data[0][0].unit, "dt") + self.assertEqual(sc.data[1][0].name, "rz") + self.assertEqual(sc.data[1][0].duration, 0) self.assertEqual(sc.data[1][0].unit, "dt") - self.assertEqual(sc.data[2][0].name, "rz") - self.assertEqual(sc.data[2][0].duration, 0) - self.assertEqual(sc.data[2][0].unit, "dt") - self.assertEqual(sc.data[5][0].name, "delay") - self.assertEqual(sc.data[5][0].duration, 450450) - self.assertEqual(sc.data[5][0].unit, "dt") + self.assertEqual(sc.data[4][0].name, "delay") + self.assertEqual(sc.data[4][0].duration, 450450) + self.assertEqual(sc.data[4][0].unit, "dt") def test_schedule_circuit_in_sec_when_no_one_tells_dt(self): """dt is unknown and all delays and gate times are in SI""" @@ -99,15 +99,15 @@ def test_schedule_circuit_in_sec_when_no_one_tells_dt(self): ) self.assertAlmostEqual(sc.duration, 450610 * self.dt) self.assertEqual(sc.unit, "s") - self.assertEqual(sc.data[1][0].name, "delay") - self.assertAlmostEqual(sc.data[1][0].duration, 1.0e-7) + self.assertEqual(sc.data[0][0].name, "delay") + self.assertAlmostEqual(sc.data[0][0].duration, 1.0e-4 + 1.0e-7) + self.assertEqual(sc.data[0][0].unit, "s") + self.assertEqual(sc.data[1][0].name, "rz") + self.assertAlmostEqual(sc.data[1][0].duration, 160 * self.dt) self.assertEqual(sc.data[1][0].unit, "s") - self.assertEqual(sc.data[2][0].name, "rz") - self.assertAlmostEqual(sc.data[2][0].duration, 160 * self.dt) - self.assertEqual(sc.data[2][0].unit, "s") - self.assertEqual(sc.data[5][0].name, "delay") - self.assertAlmostEqual(sc.data[5][0].duration, 1.0e-4 + 1.0e-7) - self.assertEqual(sc.data[5][0].unit, "s") + self.assertEqual(sc.data[4][0].name, "delay") + self.assertAlmostEqual(sc.data[4][0].duration, 1.0e-4 + 1.0e-7) + self.assertEqual(sc.data[4][0].unit, "s") with self.assertRaises(QiskitError): assemble(sc, self.backend_without_dt) diff --git a/test/python/opflow/test_pauli_sum_op.py b/test/python/opflow/test_pauli_sum_op.py index 34853311238..3a5b8de3d52 100644 --- a/test/python/opflow/test_pauli_sum_op.py +++ b/test/python/opflow/test_pauli_sum_op.py @@ -137,6 +137,8 @@ def test_equals(self): self.assertNotEqual((X ^ X) + (Y ^ Y), X + Y) self.assertEqual((X ^ X) + (Y ^ Y), (Y ^ Y) + (X ^ X)) + self.assertEqual(0 * X + I, I) + self.assertEqual(I, 0 * X + I) theta = ParameterVector("theta", 2) pauli_sum0 = theta[0] * (X + Z) diff --git a/test/python/transpiler/test_instruction_alignments.py b/test/python/transpiler/test_instruction_alignments.py index 4822300ffce..233fea1efd4 100644 --- a/test/python/transpiler/test_instruction_alignments.py +++ b/test/python/transpiler/test_instruction_alignments.py @@ -14,13 +14,13 @@ from qiskit import QuantumCircuit, pulse from qiskit.test import QiskitTestCase -from qiskit.transpiler import InstructionDurations +from qiskit.transpiler import InstructionDurations, PassManager from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.passes import ( AlignMeasures, ValidatePulseGates, ALAPSchedule, - TimeUnitConversion, + PadDelay, ) @@ -29,8 +29,8 @@ class TestAlignMeasures(QiskitTestCase): def setUp(self): super().setUp() - instruction_durations = InstructionDurations() - instruction_durations.update( + + self.instruction_durations = InstructionDurations( [ ("rz", (0,), 0), ("rz", (1,), 0), @@ -43,15 +43,6 @@ def setUp(self): ("measure", None, 1600), ] ) - self.time_conversion_pass = TimeUnitConversion(inst_durations=instruction_durations) - # reproduce old behavior of 0.20.0 before #7655 - # currently default write latency is 0 - self.scheduling_pass = ALAPSchedule( - durations=instruction_durations, - clbit_write_latency=1600, - conditional_latency=0, - ) - self.align_measure_pass = AlignMeasures(alignment=16) def test_t1_experiment_type(self): """Test T1 experiment type circuit. @@ -80,12 +71,22 @@ def test_t1_experiment_type(self): circuit.delay(100, 0, unit="dt") circuit.measure(0, 0) - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} + pm = PassManager( + [ + # reproduce old behavior of 0.20.0 before #7655 + # currently default write latency is 0 + ALAPSchedule( + durations=self.instruction_durations, + clbit_write_latency=1600, + conditional_latency=0, + ), + PadDelay(), + AlignMeasures(alignment=16), + ] ) + aligned_circuit = pm.run(circuit) + ref_circuit = QuantumCircuit(1, 1) ref_circuit.x(0) ref_circuit.delay(112, 0, unit="dt") @@ -124,12 +125,22 @@ def test_hanh_echo_experiment_type(self): circuit.sx(0) circuit.measure(0, 0) - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} + pm = PassManager( + [ + # reproduce old behavior of 0.20.0 before #7655 + # currently default write latency is 0 + ALAPSchedule( + durations=self.instruction_durations, + clbit_write_latency=1600, + conditional_latency=0, + ), + PadDelay(), + AlignMeasures(alignment=16), + ] ) + aligned_circuit = pm.run(circuit) + ref_circuit = QuantumCircuit(1, 1) ref_circuit.sx(0) ref_circuit.delay(100, 0, unit="dt") @@ -172,12 +183,22 @@ def test_mid_circuit_measure(self): circuit.delay(120, 0, unit="dt") circuit.measure(0, 1) - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} + pm = PassManager( + [ + # reproduce old behavior of 0.20.0 before #7655 + # currently default write latency is 0 + ALAPSchedule( + durations=self.instruction_durations, + clbit_write_latency=1600, + conditional_latency=0, + ), + PadDelay(), + AlignMeasures(alignment=16), + ] ) + aligned_circuit = pm.run(circuit) + ref_circuit = QuantumCircuit(1, 2) ref_circuit.x(0) ref_circuit.delay(112, 0, unit="dt") @@ -231,12 +252,22 @@ def test_mid_circuit_multiq_gates(self): circuit.cx(0, 1) circuit.measure(0, 0) - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} + pm = PassManager( + [ + # reproduce old behavior of 0.20.0 before #7655 + # currently default write latency is 0 + ALAPSchedule( + durations=self.instruction_durations, + clbit_write_latency=1600, + conditional_latency=0, + ), + PadDelay(), + AlignMeasures(alignment=16), + ] ) + aligned_circuit = pm.run(circuit) + ref_circuit = QuantumCircuit(2, 2) ref_circuit.x(0) ref_circuit.delay(112, 0, unit="dt") @@ -264,9 +295,10 @@ def test_alignment_is_not_processed(self): # pre scheduling is not necessary because alignment is skipped # this is to minimize breaking changes to existing code. - transpiled = self.align_measure_pass(circuit, property_set={"time_unit": "dt"}) + pm = PassManager(AlignMeasures(alignment=16)) + aligned_circuit = pm.run(circuit) - self.assertEqual(transpiled, circuit) + self.assertEqual(aligned_circuit, circuit) def test_circuit_using_clbit(self): """Test a circuit with instructions using a common clbit. @@ -306,11 +338,22 @@ def test_circuit_using_clbit(self): circuit.x(1).c_if(0, 1) circuit.measure(2, 0) - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} + pm = PassManager( + [ + # reproduce old behavior of 0.20.0 before #7655 + # currently default write latency is 0 + ALAPSchedule( + durations=self.instruction_durations, + clbit_write_latency=1600, + conditional_latency=0, + ), + PadDelay(), + AlignMeasures(alignment=16), + ] ) + + aligned_circuit = pm.run(circuit) + self.assertEqual(aligned_circuit.duration, 2032) ref_circuit = QuantumCircuit(3, 1) diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index e8429cef7bf..c2f24cde183 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -629,22 +629,22 @@ def test_layout_tokyo_fully_connected_cx(self, level): } dense_layout = { - 2: qr[0], + 11: qr[0], 6: qr[1], - 1: qr[2], - 5: qr[3], - 0: qr[4], - 3: ancilla[0], - 4: ancilla[1], - 7: ancilla[2], - 8: ancilla[3], - 9: ancilla[4], - 10: ancilla[5], - 11: ancilla[6], - 12: ancilla[7], - 13: ancilla[8], - 14: ancilla[9], - 15: ancilla[10], + 5: qr[2], + 10: qr[3], + 15: qr[4], + 0: ancilla[0], + 1: ancilla[1], + 2: ancilla[2], + 3: ancilla[3], + 4: ancilla[4], + 7: ancilla[5], + 8: ancilla[6], + 9: ancilla[7], + 12: ancilla[8], + 13: ancilla[9], + 14: ancilla[10], 16: ancilla[11], 17: ancilla[12], 18: ancilla[13], diff --git a/test/python/transpiler/test_scheduling_pass.py b/test/python/transpiler/test_scheduling_padding_pass.py similarity index 75% rename from test/python/transpiler/test_scheduling_pass.py rename to test/python/transpiler/test_scheduling_padding_pass.py index 5693ba0e4f2..e3dfc280916 100644 --- a/test/python/transpiler/test_scheduling_pass.py +++ b/test/python/transpiler/test_scheduling_padding_pass.py @@ -10,20 +10,22 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test the Scheduling passes""" +"""Test the Scheduling/PadDelay passes""" import unittest from ddt import ddt, data, unpack from qiskit import QuantumCircuit +from qiskit.pulse import Schedule, Play, Constant, DriveChannel from qiskit.test import QiskitTestCase from qiskit.transpiler.instruction_durations import InstructionDurations -from qiskit.transpiler.passes import ASAPSchedule, ALAPSchedule +from qiskit.transpiler.passes import ASAPSchedule, ALAPSchedule, PadDelay from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.exceptions import TranspilerError @ddt -class TestSchedulingPass(QiskitTestCase): +class TestSchedulingAndPaddingPass(QiskitTestCase): """Tests the Scheduling passes""" def test_alap_agree_with_reverse_asap_reverse(self): @@ -38,10 +40,10 @@ def test_alap_agree_with_reverse_asap_reverse(self): [("h", 0, 200), ("cx", [0, 1], 700), ("measure", None, 1000)] ) - pm = PassManager(ALAPSchedule(durations)) + pm = PassManager([ALAPSchedule(durations), PadDelay()]) alap_qc = pm.run(qc) - pm = PassManager(ASAPSchedule(durations)) + pm = PassManager([ASAPSchedule(durations), PadDelay()]) new_qc = pm.run(qc.reverse_ops()) new_qc = new_qc.reverse_ops() new_qc.name = new_qc.name @@ -78,7 +80,7 @@ def test_classically_controlled_gate_after_measure(self, schedule_pass): qc.x(1).c_if(0, True) durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) + pm = PassManager([schedule_pass(durations), PadDelay()]) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -118,7 +120,7 @@ def test_measure_after_measure(self, schedule_pass): qc.measure(1, 0) durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) + pm = PassManager([schedule_pass(durations), PadDelay()]) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -165,7 +167,7 @@ def test_c_if_on_different_qubits(self, schedule_pass): qc.x(2).c_if(0, True) durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) + pm = PassManager([schedule_pass(durations), PadDelay()]) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -205,7 +207,7 @@ def test_shorter_measure_after_measure(self, schedule_pass): qc.measure(1, 0) durations = InstructionDurations([("measure", [0], 1000), ("measure", [1], 700)]) - pm = PassManager(schedule_pass(durations)) + pm = PassManager([schedule_pass(durations), PadDelay()]) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -248,7 +250,7 @@ def test_measure_after_c_if(self, schedule_pass): qc.measure(2, 0) durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) + pm = PassManager([schedule_pass(durations), PadDelay()]) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -302,7 +304,7 @@ def test_parallel_gate_different_length(self): durations = InstructionDurations( [("x", [0], 200), ("x", [1], 400), ("measure", None, 1000)] ) - pm = PassManager(ALAPSchedule(durations)) + pm = PassManager([ALAPSchedule(durations), PadDelay()]) qc_alap = pm.run(qc) alap_expected = QuantumCircuit(2, 2) @@ -314,7 +316,7 @@ def test_parallel_gate_different_length(self): self.assertEqual(qc_alap, alap_expected) - pm = PassManager(ASAPSchedule(durations)) + pm = PassManager([ASAPSchedule(durations), PadDelay()]) qc_asap = pm.run(qc) asap_expected = QuantumCircuit(2, 2) @@ -366,7 +368,7 @@ def test_parallel_gate_different_length_with_barrier(self): durations = InstructionDurations( [("x", [0], 200), ("x", [1], 400), ("measure", None, 1000)] ) - pm = PassManager(ALAPSchedule(durations)) + pm = PassManager([ALAPSchedule(durations), PadDelay()]) qc_alap = pm.run(qc) alap_expected = QuantumCircuit(2, 2) @@ -379,7 +381,7 @@ def test_parallel_gate_different_length_with_barrier(self): self.assertEqual(qc_alap, alap_expected) - pm = PassManager(ASAPSchedule(durations)) + pm = PassManager([ASAPSchedule(durations), PadDelay()]) qc_asap = pm.run(qc) asap_expected = QuantumCircuit(2, 2) @@ -441,8 +443,12 @@ def test_measure_after_c_if_on_edge_locking(self): durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) # lock at the end edge - actual_asap = PassManager(ASAPSchedule(durations, clbit_write_latency=1000)).run(qc) - actual_alap = PassManager(ALAPSchedule(durations, clbit_write_latency=1000)).run(qc) + actual_asap = PassManager( + [ASAPSchedule(durations, clbit_write_latency=1000), PadDelay()] + ).run(qc) + actual_alap = PassManager( + [ALAPSchedule(durations, clbit_write_latency=1000), PadDelay()] + ).run(qc) # start times of 2nd measure depends on ASAP/ALAP expected_asap = QuantumCircuit(3, 1) @@ -491,10 +497,20 @@ def test_active_reset_circuit(self, write_lat, cond_lat): durations = InstructionDurations([("x", None, 100), ("measure", None, 1000)]) actual_asap = PassManager( - ASAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) + [ + ASAPSchedule( + durations, clbit_write_latency=write_lat, conditional_latency=cond_lat + ), + PadDelay(), + ] ).run(qc) actual_alap = PassManager( - ALAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) + [ + ALAPSchedule( + durations, clbit_write_latency=write_lat, conditional_latency=cond_lat + ), + PadDelay(), + ] ).run(qc) expected = QuantumCircuit(1, 1) @@ -538,62 +554,62 @@ def test_random_complicated_circuit(self): « 0 └─────────┘ (ASAP scheduled) duration = 2800 dt - ┌────────────────┐┌────────────────┐ ┌───┐ ░ ┌─────────────────┐» - q_0: ┤ Delay(100[dt]) ├┤ Delay(100[dt]) ├───┤ X ├────░─┤ Delay(1400[dt]) ├» - ├────────────────┤└────────────────┘ └─╥─┘ ░ ├─────────────────┤» - q_1: ┤ Delay(300[dt]) ├───────────────────────╫──────░─┤ Delay(1200[dt]) ├» - ├────────────────┤ ║ ░ └───────┬─┬───────┘» - q_2: ┤ Delay(300[dt]) ├───────────────────────╫──────░─────────┤M├────────» - └────────────────┘ ┌────╨────┐ ░ └╥┘ » - c: 1/════════════════════════════════════╡ c_0=0x1 ╞════════════╩═════════» - └─────────┘ 0 » - « ┌───┐ ┌────────────────┐» - «q_0: ────────────────────────────────┤ X ├───┤ Delay(300[dt]) ├» - « ┌───┐ └─╥─┘ └────────────────┘» - «q_1: ───┤ X ├──────────────────────────╫─────────────■─────────» - « └─╥─┘ ┌────────────────┐ ║ ┌─┴─┐ » - «q_2: ─────╫─────┤ Delay(300[dt]) ├─────╫───────────┤ X ├───────» - « ┌────╨────┐└────────────────┘┌────╨────┐ └───┘ » - «c: 1/╡ c_0=0x0 ╞══════════════════╡ c_0=0x0 ╞══════════════════» - « └─────────┘ └─────────┘ » - « ┌───┐ ┌────────────────┐ - «q_0: ──────┤ X ├────────────■─────┤ Delay(700[dt]) ├ - « ┌─────┴───┴──────┐ ┌─┴─┐ ├────────────────┤ - «q_1: ┤ Delay(400[dt]) ├───┤ X ├───┤ Delay(700[dt]) ├ - « ├────────────────┤ └─╥─┘ └──────┬─┬───────┘ - «q_2: ┤ Delay(300[dt]) ├─────╫────────────┤M├──────── - « └────────────────┘┌────╨────┐ └╥┘ - «c: 1/══════════════════╡ c_0=0x0 ╞════════╩═════════ - « └─────────┘ 0 + ┌────────────────┐ ┌───┐ ░ ┌─────────────────┐ » + q_0: ┤ Delay(200[dt]) ├───┤ X ├────░─┤ Delay(1400[dt]) ├───────────» + ├────────────────┤ └─╥─┘ ░ ├─────────────────┤ ┌───┐ » + q_1: ┤ Delay(300[dt]) ├─────╫──────░─┤ Delay(1200[dt]) ├───┤ X ├───» + ├────────────────┤ ║ ░ └───────┬─┬───────┘ └─╥─┘ » + q_2: ┤ Delay(300[dt]) ├─────╫──────░─────────┤M├─────────────╫─────» + └────────────────┘┌────╨────┐ ░ └╥┘ ┌────╨────┐» + c: 1/══════════════════╡ c_0=0x1 ╞════════════╩═════════╡ c_0=0x0 ╞» + └─────────┘ 0 └─────────┘» + « ┌───┐ ┌────────────────┐ ┌───┐ » + «q_0: ─────────────────────┤ X ├───┤ Delay(300[dt]) ├──────┤ X ├───────» + « └─╥─┘ └────────────────┘┌─────┴───┴──────┐» + «q_1: ───────────────────────╫─────────────■─────────┤ Delay(400[dt]) ├» + « ┌────────────────┐ ║ ┌─┴─┐ ├────────────────┤» + «q_2: ┤ Delay(300[dt]) ├─────╫───────────┤ X ├───────┤ Delay(300[dt]) ├» + « └────────────────┘┌────╨────┐ └───┘ └────────────────┘» + «c: 1/══════════════════╡ c_0=0x0 ╞════════════════════════════════════» + « └─────────┘ » + « ┌────────────────┐ + «q_0: ─────■─────┤ Delay(700[dt]) ├ + « ┌─┴─┐ ├────────────────┤ + «q_1: ───┤ X ├───┤ Delay(700[dt]) ├ + « └─╥─┘ └──────┬─┬───────┘ + «q_2: ─────╫────────────┤M├──────── + « ┌────╨────┐ └╥┘ + «c: 1/╡ c_0=0x0 ╞════════╩═════════ + « └─────────┘ 0 (ALAP scheduled) duration = 3100 - ┌────────────────┐┌────────────────┐ ┌───┐ ░ ┌─────────────────┐» - q_0: ┤ Delay(100[dt]) ├┤ Delay(100[dt]) ├───┤ X ├────░─┤ Delay(1400[dt]) ├» - ├────────────────┤└────────────────┘ └─╥─┘ ░ ├─────────────────┤» - q_1: ┤ Delay(300[dt]) ├───────────────────────╫──────░─┤ Delay(1200[dt]) ├» - ├────────────────┤ ║ ░ └───────┬─┬───────┘» - q_2: ┤ Delay(300[dt]) ├───────────────────────╫──────░─────────┤M├────────» - └────────────────┘ ┌────╨────┐ ░ └╥┘ » - c: 1/════════════════════════════════════╡ c_0=0x1 ╞════════════╩═════════» - └─────────┘ 0 » - « ┌───┐ ┌────────────────┐» - «q_0: ────────────────────────────────┤ X ├───┤ Delay(300[dt]) ├» - « ┌───┐ ┌────────────────┐ └─╥─┘ └────────────────┘» - «q_1: ───┤ X ├───┤ Delay(300[dt]) ├─────╫─────────────■─────────» - « └─╥─┘ ├────────────────┤ ║ ┌─┴─┐ » - «q_2: ─────╫─────┤ Delay(600[dt]) ├─────╫───────────┤ X ├───────» - « ┌────╨────┐└────────────────┘┌────╨────┐ └───┘ » - «c: 1/╡ c_0=0x0 ╞══════════════════╡ c_0=0x0 ╞══════════════════» - « └─────────┘ └─────────┘ » - « ┌───┐ ┌────────────────┐ - «q_0: ──────┤ X ├────────────■─────┤ Delay(700[dt]) ├ - « ┌─────┴───┴──────┐ ┌─┴─┐ ├────────────────┤ - «q_1: ┤ Delay(100[dt]) ├───┤ X ├───┤ Delay(700[dt]) ├ - « └──────┬─┬───────┘ └─╥─┘ └────────────────┘ - «q_2: ───────┤M├─────────────╫─────────────────────── - « └╥┘ ┌────╨────┐ - «c: 1/════════╩═════════╡ c_0=0x0 ╞══════════════════ - « 0 └─────────┘ + ┌────────────────┐ ┌───┐ ░ ┌─────────────────┐ » + q_0: ┤ Delay(200[dt]) ├───┤ X ├────░─┤ Delay(1400[dt]) ├───────────» + ├────────────────┤ └─╥─┘ ░ ├─────────────────┤ ┌───┐ » + q_1: ┤ Delay(300[dt]) ├─────╫──────░─┤ Delay(1200[dt]) ├───┤ X ├───» + ├────────────────┤ ║ ░ └───────┬─┬───────┘ └─╥─┘ » + q_2: ┤ Delay(300[dt]) ├─────╫──────░─────────┤M├─────────────╫─────» + └────────────────┘┌────╨────┐ ░ └╥┘ ┌────╨────┐» + c: 1/══════════════════╡ c_0=0x1 ╞════════════╩═════════╡ c_0=0x0 ╞» + └─────────┘ 0 └─────────┘» + « ┌───┐ ┌────────────────┐ ┌───┐ » + «q_0: ─────────────────────┤ X ├───┤ Delay(300[dt]) ├──────┤ X ├───────» + « ┌────────────────┐ └─╥─┘ └────────────────┘┌─────┴───┴──────┐» + «q_1: ┤ Delay(300[dt]) ├─────╫─────────────■─────────┤ Delay(100[dt]) ├» + « ├────────────────┤ ║ ┌─┴─┐ └──────┬─┬───────┘» + «q_2: ┤ Delay(600[dt]) ├─────╫───────────┤ X ├──────────────┤M├────────» + « └────────────────┘┌────╨────┐ └───┘ └╥┘ » + «c: 1/══════════════════╡ c_0=0x0 ╞══════════════════════════╩═════════» + « └─────────┘ 0 » + « ┌────────────────┐ + «q_0: ─────■─────┤ Delay(700[dt]) ├ + « ┌─┴─┐ ├────────────────┤ + «q_1: ───┤ X ├───┤ Delay(700[dt]) ├ + « └─╥─┘ └────────────────┘ + «q_2: ─────╫─────────────────────── + « ┌────╨────┐ + «c: 1/╡ c_0=0x0 ╞══════════════════ + « └─────────┘ """ qc = QuantumCircuit(3, 1) @@ -614,15 +630,14 @@ def test_random_complicated_circuit(self): ) actual_asap = PassManager( - ASAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) + [ASAPSchedule(durations, clbit_write_latency=100, conditional_latency=200), PadDelay()] ).run(qc) actual_alap = PassManager( - ALAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) + [ALAPSchedule(durations, clbit_write_latency=100, conditional_latency=200), PadDelay()] ).run(qc) expected_asap = QuantumCircuit(3, 1) - expected_asap.delay(100, 0) - expected_asap.delay(100, 0) # due to conditional latency of 200dt + expected_asap.delay(200, 0) # due to conditional latency of 200dt expected_asap.delay(300, 1) expected_asap.delay(300, 2) expected_asap.x(0).c_if(0, 1) @@ -648,8 +663,7 @@ def test_random_complicated_circuit(self): self.assertEqual(actual_asap.duration, 3100) expected_alap = QuantumCircuit(3, 1) - expected_alap.delay(100, 0) - expected_alap.delay(100, 0) # due to conditional latency of 200dt + expected_alap.delay(200, 0) # due to conditional latency of 200dt expected_alap.delay(300, 1) expected_alap.delay(300, 2) expected_alap.x(0).c_if(0, 1) @@ -707,7 +721,7 @@ def test_dag_introduces_extra_dependency_between_conditionals(self): qc.x(1).c_if(0, True) durations = InstructionDurations([("x", None, 160)]) - pm = PassManager(ASAPSchedule(durations)) + pm = PassManager([ASAPSchedule(durations), PadDelay()]) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -718,6 +732,71 @@ def test_dag_introduces_extra_dependency_between_conditionals(self): self.assertEqual(expected, scheduled) + def test_scheduling_with_calibration(self): + """Test if calibrated instruction can update node duration.""" + qc = QuantumCircuit(2) + qc.x(0) + qc.cx(0, 1) + qc.x(1) + qc.cx(0, 1) + + xsched = Schedule(Play(Constant(300, 0.1), DriveChannel(0))) + qc.add_calibration("x", (0,), xsched) + + durations = InstructionDurations([("x", None, 160), ("cx", None, 600)]) + pm = PassManager([ASAPSchedule(durations), PadDelay()]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2) + expected.x(0) + expected.delay(300, 1) + expected.cx(0, 1) + expected.x(1) + expected.delay(160, 0) + expected.cx(0, 1) + expected.add_calibration("x", (0,), xsched) + + self.assertEqual(expected, scheduled) + + def test_padding_not_working_without_scheduling(self): + """Test padding fails when un-scheduled DAG is input.""" + qc = QuantumCircuit(1, 1) + qc.delay(100, 0) + qc.x(0) + qc.measure(0, 0) + + with self.assertRaises(TranspilerError): + PassManager(PadDelay()).run(qc) + + def test_no_pad_very_end_of_circuit(self): + """Test padding option that inserts no delay at the very end of circuit. + + This circuit will be unchanged after ASAP-schedule/padding. + + ┌────────────────┐┌─┐ + q_0: ┤ Delay(100[dt]) ├┤M├ + └─────┬───┬──────┘└╥┘ + q_1: ──────┤ X ├────────╫─ + └───┘ ║ + c: 1/═══════════════════╩═ + 0 + """ + qc = QuantumCircuit(2, 1) + qc.delay(100, 0) + qc.x(1) + qc.measure(0, 0) + + durations = InstructionDurations([("x", None, 160), ("measure", None, 1000)]) + + scheduled = PassManager( + [ + ASAPSchedule(durations), + PadDelay(fill_very_end=False), + ] + ).run(qc) + + self.assertEqual(scheduled, qc) + if __name__ == "__main__": unittest.main()