Introducing Qiskit Algorithms With Qiskit Primitives!
By Dariusz Lasecki and Stefan Wörner.
The latest release of Qiskit introduced the Qiskit Primitives as a new way to interact with quantum computers. In addition to introducing the primitives, the new release came with a completely updated algorithms module, which provides state-of-the-art quantum algorithms for the open-source community, all powered by the newly introduced primitives.
Today, we are taking a closer look at what changed in the Qiskit’s algorithms module. To see the full release notes for Qiskit v0.39 (which includes the 0.22 version of Qiskit Terra), go here.
The TL;DR
We enabled all Qiskit algorithms to use suitable primitives and introduced a few new ones: pVQD
,AdaptVQE
, and VQD
. Tutorials covering updated algorithms can be found here.
Below, find a summary of the top new features and improvements.
Near-term algorithms:
Near-term algorithms are how we refer to promising candidates for running on near-term devices, such as devices limited in the number of qubits and coherence times or those without error-correcting codes. They might lead to a quantum advantage over classical methods as the hardware matures and develops. If interested, refer to IBM Quantum’s recently updated roadmap for more on near-term devices. The Qiskit algorithms module provides cutting-edge near-term quantum algorithms and lays the foundation for using these methods in promising applications areas — Quantum Machine Learning, Natural Sciences, Optimization, and Finance (do not forget to check out our Qiskit Applications Modules: Qiskit Nature, Finance , Optimization and Machine Learning ).
qiskit.algorithms.minimum_eigensolvers (formerly qiskit.algorithms.minimum_eigen_solvers)
The minimum_eigensolvers
package is responsible for interfaces and quantum algorithms that compute the ground state of a Hamiltonian. The update fixes a small naming inconsistency of the old minimum_eigen_solvers
, and contains the new primitive-based algorithms. It features popular optimization algorithms like VQE and QAOA which now support the Qiskit Primitives.
It is worth noting that from this release on, two versions of VQE are available — SamplingVQE
, which uses the sampler primitive and is optimized for diagonal Hamiltonians, and VQE
, which uses the estimator primitive. The choice of the algorithm depends on the use case — whether we are interested in accessing the probability distribution corresponding to a quantum state or an estimation of the ground state energy which might require, for example, measurements in multiple bases.
In the case of the updated VQE algorithm, we now use an estimator instead of a quantum instance, and the SparsePauliOp
instead of the PauliSumOp
(although the PauliSumOp
will still be supported). We recommend you use the more efficient SparsePauliOp
and will update also the application modules in the future accordingly. The comparison of the current and the old version of VQE
on a simple example can be seen below.
New VQE:
from qiskit.algorithms.minimum_eigensolvers import VQE
from qiskit.algorithms.optimizers import SLSQP
from qiskit.circuit.library import TwoLocal
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives import Estimator
hamiltonian = SparsePauliOp.from_list([(“II”, -1), (“IZ”, 0.3),
(“XI”, -0.3), (“ZY”, -0.01), (“YX”, 0.1)])
estimator = Estimator()
optimizer = SLSQP()
ansatz = TwoLocal(rotation_blocks=[“ry”, “rz”], entanglement_blocks=”cz”)
vqe = VQE(estimator, ansatz, optimizer)
result = vqe.compute_minimum_eigenvalue(operator=hamiltonian)
eigenvalue = result.eigenvalue
Old VQE (now deprecated):
from qiskit import BasicAer
from qiskit.algorithms.minimum_eigen_solvers import VQE
from qiskit.algorithms.optimizers import SLSQP
from qiskit.circuit.library import TwoLocal
from qiskit.opflow import PauliSumOp
from qiskit.quantum_info import SparsePauliOp
hamiltonian = PauliSumOp(SparsePauliOp.from_list([(“II”, -1), (“IZ”, 0.3),
(“XI”, -0.3), (“ZY”, -0.01), (“YX”, 0.1)]))
quantum_instance = BasicAer.get_backend(“statevector_simulator”)
optimizer = SLSQP()
ansatz = TwoLocal(rotation_blocks=[“ry”, “rz”], entanglement_blocks=”cz”)
vqe = VQE(ansatz, optimizer, quantum_instance=quantum_instance)
result = vqe.compute_minimum_eigenvalue(operator=hamiltonian)
eigenvalue = result.eigenvalue
When it comes to using VQE in the sampling context, the updated code can look like the one below. Note that SamplingVQE
is only applicable to diagonal Hamiltonians, as only then can the energy be evaluated directly from the sampled bitstrings, and only then do the bitstrings directly correspond to eigenstates, i.e., correspond to potential solutions of the considered problem.
from qiskit.algorithms.minimum_eigensolvers import SamplingVQE
from qiskit.algorithms.optimizers import SLSQP
from qiskit.circuit.library import TwoLocal
from qiskit.primitives import Sampler
from qiskit.quantum_info import SparsePauliOp
operator = SparsePauliOp.from_list([(“ZZ”, 1), (“IZ”, -0.5), (“II”, 0.12)])
sampler = Sampler()
ansatz = TwoLocal(rotation_blocks=[“ry”, “rz”], entanglement_blocks=”cz”)
optimizer = SLSQP()
sampling_vqe = SamplingVQE(sampler, ansatz, optimizer)
result = sampling_vqe.compute_minimum_eigenvalue(operator)
eigenvalue = result.eigenvalue
As a new algorithm introduced in this release, we moved AdaptVQE
from Qiskit Nature to Qiskit (Terra) and generalized it beyond the chemistry applications. It computes the ground state energy of a system by creating a compact ansatz from a set of evolution operators. To learn the theory behind it, please refer to this publication.
Below you can find the example of how to use it.
from qiskit.algorithms.minimum_eigensolvers import AdaptVQE, VQE
from qiskit.algorithms.optimizers import SLSQP
from qiskit.primitives import Estimator
from qiskit.circuit.library import EvolvedOperatorAnsatz
# get your Hamiltonian
hamiltonian = …
# construct your ansatz
ansatz = EvolvedOperatorAnsatz(…)
vqe = VQE(Estimator(), ansatz, SLSQP())
adapt_vqe = AdaptVQE(vqe)
result = adapt_vqe.compute_minimum_eigenvalue(hamiltonian)
The qiskit.algorithms.minimum_eigen_solvers
package, which includes algorithms without the Qiskit Primitives, is now deprecated.
qiskit.algorithms.eigensolvers (formerly qiskit.algorithms.eigen_solvers)
The eigensolvers
package provides interfaces and algorithms for calculating eigenvalues of operators. Again, this update fixes a small naming inconsistency in the old eigen_solvers
module and contains the new primitive-based algorithms. They are useful for finding energy spectra of quantum systems, for example. Currently, Qiskit offers a reference classical implementation based on NumPy and a quantum algorithm called Variational Quantum Deflation (VQD). VQD computes excited state energies of Hamiltonians and is a new addition to the module in this version — see the documentation. To learn more about the algorithm, refer to the relevant publication.
The VQD algorithm can be used as follows:
from qiskit.algorithms.eigensolvers import VQD
from qiskit.algorithms.optimizers import SLSQP
from qiskit.circuit.library import TwoLocal
from qiskit.primitives import Sampler, Estimator
from qiskit.algorithms.state_fidelities import ComputeUncompute
from qiskit.opflow import PauliSumOp
from qiskit.quantum_info import SparsePauliOp
h2_op = PauliSumOp(SparsePauliOp(
[“II”, “IZ”, “ZI”, “ZZ”, “XX”],
coeffs=[
-1.052373245772859,
0.39793742484318045,
-0.39793742484318045,
-0.01128010425623538,
0.18093119978423156,
],
))
estimator = Estimator()
ansatz = TwoLocal(rotation_blocks=[“ry”, “rz”], entanglement_blocks=”cz”)
optimizer = SLSQP()
fidelity = ComputeUncompute(Sampler())
vqd = VQD(estimator, fidelity, ansatz, optimizer, k=2)
result = vqd.compute_eigenvalues(h2_op)
eigenvalues = result.eigenvalues
All the algorithms in the qiskit.algorithms.eigensolvers
package use the Qiskit Primitives. The old qiskit.algorithms.eigen_solvers
package is now deprecated.
qiskit.algorithms.time_evolvers (formerly qiskit.algorithms.evolvers)
Primitive-enabled time evolution algorithms and interfaces now reside in the time_evolvers
package. This includes the Trotterization-based Quantum Real Time Evolution algorithm and a new addition in this release — the Projected Variational Quantum Dynamics (pVQD) algorithm. To learn more about the latter one, see the relevant publication.
You can use pVQD as follows:
import numpy as np
from qiskit.algorithms.state_fidelities import ComputeUncompute
from qiskit.algorithms.evolvers import EvolutionProblem
from qiskit.algorithms.time_evolvers.pvqd import PVQD
from qiskit.primitives import Estimator, Sampler
from qiskit import BasicAer
from qiskit.circuit.library import EfficientSU2
from qiskit.quantum_info import Pauli, SparsePauliOp
from qiskit.algorithms.optimizers import L_BFGS_B
sampler = Sampler()
fidelity = ComputeUncompute(sampler)
estimator = Estimator()
hamiltonian = 0.1 * SparsePauliOp([Pauli(“ZZ”), Pauli(“IX”), Pauli(“XI”)])
observable = Pauli(“ZZ”)
ansatz = EfficientSU2(2, reps=1)
initial_parameters = np.zeros(ansatz.num_parameters)
time = 1
optimizer = L_BFGS_B()
pvqd = PVQD(
fidelity,
ansatz,
initial_parameters,
estimator,
num_timesteps=100,
optimizer=optimizer,
)
problem = EvolutionProblem(
hamiltonian, time, aux_operators=[hamiltonian, observable]
)
result = pvqd.evolve(problem)
The qiskit.algorithms.evolvers
package, with algorithms not supporting Qiskit Primitives, is now deprecated.
qiskit.algorithms.phase_estimators, amplitude_estimators, and amplitude_amplifiers
Phase estimation and amplitude amplification/estimation algorithms now accept Qiskit primitive programs in their signatures. Otherwise, their logic and structure remain mostly unchanged. Throughout the deprecation period, they still accept the old QuantumInstance
argument.
qiskit.algorithms.optimizers
In the optimizers package, the QN-SPSA optimizer now supports Qiskit Primitives by accepting a sampler in its get_fidelity
method. Throughout the deprecation period, it still accepts the old QuantumInstance
argument. Other optimizers in the package did not require changes.
New subroutines
With the introduction of Qiskit primitive programs, our developers used them to port features from the gradient framework, already available in Qiskit (Terra)’s opflow, to a new module with enhanced capabilities. They also implemented another subroutine useful in quantum algorithms development: calculating quantum state fidelities. They allow every algorithm developer to harvest the benefits of Qiskit Primitives in their work.
qiskit.algorithms.gradients
Many quantum algorithms require the computation of gradients of, e.g., sampling probabilities or expectation values. To make it easier to do that with Qiskit Primitives, we introduced a new module in algorithms
calledgradients
. Estimator gradients allow computing gradients of observables and sampler gradients allow computing gradients of sampled probabilities.
The supported gradient methods are: finite difference, linear combination of unitaries, parameters shift, and SPSA. A newly introduced class also enables calculating Quantum Fisher Information (QFI).
For example, to calculate the QFI of a quantum circuit with parametrized Z-rotation and X-rotation gates, one would do the following:
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.algorithms.gradients import LinCombQFI
estimator = Estimator()
a, b = Parameter(“a”), Parameter(“b”)
qc = QuantumCircuit(1)
qc.h(0)
qc.rz(a, 0)
qc.rx(b, 0)
qfi = LinCombQFI(estimator)
parameter_value = [[np.pi / 4, 0]]
qfi_result = qfi.run([qc], parameter_value).result()
qiskit.algorithms.state_fidelities
Determining the overlap (or fidelity) between quantum states is a fundamental routine used in multiple settings — to assess the quality of time evolution algorithms of quantum states, to evaluate the cost functions or kernels in Quantum Machine Learning, or to characterize the noise of QPUs. In this release, we introduced a routine to do that using Qiskit Primitives. Currently, the compute-uncompute fidelity calculation method is provided in the state_fidelities
package and you can read more about it here.
For example, to calculate the fidelity between two quantum states given as quantum circuits, one would do it in the following way:
import numpy as np
from qiskit.primitives import Sampler
from qiskit.algorithms.state_fidelities import ComputeUncompute
from qiskit.circuit.library import RealAmplitudes
sampler = Sampler(…)
fidelity = ComputeUncompute(sampler)
circuit = RealAmplitudes(2)
values = np.random.random(circuit.num_parameters)
shift = np.ones_like(values) * 0.01
job = fidelity.run([circuit], [circuit], [values], [values+shift])
fidelities = job.result().fidelities
Deprecations
The use of QuantumInstance
The use of the quantum_instance
argument throughout the module was deprecated and primitives-based arguments were introduced. Algorithms will still support the use of a QuantumInstance
throughout a deprecation period.
qiskit.algorithms.factorizers and linear_solvers
These packages that include Shor’s and HHL algorithms become deprecated. For working Qiskit implementations as well as the corresponding theory we refer to the Qiskit Textbook here and here.
Many people contributed code for this release, special thanks to (in alphabetical order): a-matsuo, Declan Millar, dlasecki, ElePT, Ikko Hamamura, Jake Lishman, Julien Gacon, Manoel Marques, Steve Wood.