Opflow migration guide
Deprecation Notice
This guide precedes the introduction of the V2 primitives interface. Following the introduction of the V2 primitives, some providers have deprecated V1 primitive implementations in favor of the V2 alternatives. If you are interested in following this guide, we recommend combining it with the Migrate to V2 primitives guide to bring your code to the most updated state.
The new qiskit.primitives
, in combination with the qiskit.quantum_info
module, have superseded
functionality of qiskit.opflow
, which is being deprecated.
This migration guide contains instructions and code examples to migrate Qiskit code that uses
the qiskit.opflow
module to the qiskit.primitives
and qiskit.quantum_info
modules.
The qiskit.opflow
module was tightly coupled to the qiskit.utils.QuantumInstance
class, which
is also being deprecated. For information about migrating the qiskit.utils.QuantumInstance
, see
the Quantum instance migration guide.
Most references to the qiskit.primitives.Sampler
or qiskit.primitives.Estimator
in this guide
can be replaced with instances of any primitive implementation. For example, Aer primitives (qiskit_aer.primitives.Sampler
/qiskit_aer.primitives.Estimator
) or the Qiskit Runtime primitives (qiskit_ibm_runtime.Sampler
/qiskit_ibm_runtime.Estimator
).
Specific QPUs (quantum processing units) can be wrapped with (qiskit.primitives.BackendSampler
, qiskit.primitives.BackendEstimator
) to also present primitive-compatible interfaces.
Certain classes, such as the
qiskit.opflow.expectations.AerPauliExpectation
, can only be replaced by a specific primitive instance
(in this case, qiskit_aer.primitives.Estimator
), or require a specific option configuration.
If this is the case, it will be explicitly indicated in the corresponding section.
Background
The qiskit.opflow
module was originally introduced as a layer between circuits and algorithms, a series of building blocks
for quantum algorithm research and development.
The release of the qiskit.primitives
introduced a new paradigm for interacting with quantum computers. Instead of
preparing a circuit to execute with a backend.run()
type of method, algorithms can leverage the qiskit.primitives.Sampler
and
qiskit.primitives.Estimator
primitives, send parametrized circuits and observables, and directly receive quasi-probability distributions or
expectation values (respectively). This workflow simplifies the pre-processing and post-processing steps
that previously relied on this module; allowing us to move away from qiskit.opflow
and find new paths for developing algorithms based on the qiskit.primitives
interface and
the qiskit.quantum_info
module.
This guide describes the opflow submodules and provides either a direct alternative
(for example, using qiskit.quantum_info
), or an explanation of how to replace their functionality in algorithms.
The functional equivalency can be roughly summarized as follows:
Opflow Module | Alternative |
---|---|
Operators (qiskit.opflow.OperatorBase , Operator Globals , qiskit.opflow.primitive_ops , qiskit.opflow.list_ops ) | qiskit.quantum_info Operators |
qiskit.opflow.state_fns | qiskit.quantum_info States |
qiskit.opflow.converters | qiskit.primitives |
qiskit.opflow.evolutions | qiskit.synthesis Evolution |
qiskit.opflow.expectations | qiskit.primitives.Estimator |
qiskit.opflow.gradients | qiskit.algorithms.gradients |
Operator base class
The qiskit.opflow.OperatorBase
abstract class can be replaced with qiskit.quantum_info.BaseOperator
,
keeping in mind that qiskit.quantum_info.BaseOperator
is more generic than its opflow counterpart.
Opflow | Alternative |
---|---|
qiskit.opflow.OperatorBase | qiskit.quantum_info.BaseOperator |
Despite the similar class names, qiskit.opflow.OperatorBase
and
qiskit.quantum_info.BaseOperator
are not completely equivalent, and the transition
should be handled with care. Namely:
-
qiskit.opflow.OperatorBase
implements a broader algebra mixin. Some operator overloads that were commonly used inqiskit.opflow
(for example~
for.adjoint()
) are not defined forqiskit.quantum_info.BaseOperator
. You might want to check the specificqiskit.quantum_info
subclass instead. -
qiskit.opflow.OperatorBase
also implements methods such as.to_matrix()
or.to_spmatrix()
, which are only found in some of theqiskit.quantum_info.BaseOperator
subclasses.
See the qiskit.opflow.OperatorBase
and qiskit.quantum_info.BaseOperator
API references
for more information.
Operator globals
Opflow provided shortcuts to define common single qubit states, operators, and non-parametrized gates in the
operator_globals
module.
These were mainly used for didactic purposes or quick prototyping and can easily be replaced by their corresponding
qiskit.quantum_info
class: qiskit.quantum_info.Pauli
, qiskit.quantum_info.Clifford
or
qiskit.quantum_info.Statevector
.
Single-qubit Paulis
The single-qubit Paulis were commonly used for algorithm testing, as they could be combined to create more complex operators
(for example, 0.39 * (I ^ Z) + 0.5 * (X ^ X)
).
These operations implicitly created operators of type qiskit.opflow.primitive_ops.PauliSumOp
, and can be replaced by
directly creating a corresponding qiskit.quantum_info.SparsePauliOp
, as shown in the following examples.
Opflow | Alternative |
---|---|
qiskit.opflow.X , qiskit.opflow.Y , qiskit.opflow.Z , qiskit.opflow.I | qiskit.quantum_info.Pauli Note For direct compatibility with classes in qiskit.algorithms , wrap in qiskit.quantum_info.SparsePauliOp . |
Example 1: Define the XX operator
Opflow:
from qiskit.opflow import X
operator = X ^ X
print(repr(operator))
PauliOp(Pauli('XX'), coeff=1.0)
Alternative:
from qiskit.quantum_info import Pauli, SparsePauliOp
operator = Pauli('XX')
# equivalent to:
X = Pauli('X')
operator = X ^ X
print("As Pauli Op: ", repr(operator))
# another alternative is:
operator = SparsePauliOp('XX')
print("As Sparse Pauli Op: ", repr(operator))
As Pauli Op: Pauli('XX')
As Sparse Pauli Op: SparsePauliOp(['XX'],
coeffs=[1.+0.j])
Example 2: Define a more complex operator
Opflow:
from qiskit.opflow import I, X, Z, PauliSumOp
operator = 0.39 * (I ^ Z ^ I) + 0.5 * (I ^ X ^ X)
# equivalent to:
operator = PauliSumOp.from_list([("IZI", 0.39), ("IXX", 0.5)])
print(repr(operator))
PauliSumOp(SparsePauliOp(['IZI', 'IXX'],
coeffs=[0.39+0.j, 0.5 +0.j]), coeff=1.0)
Alternative:
from qiskit.quantum_info import SparsePauliOp
operator = SparsePauliOp(["IZI", "IXX"], coeffs = [0.39, 0.5])
# equivalent to:
operator = SparsePauliOp.from_list([("IZI", 0.39), ("IXX", 0.5)])
# equivalent to:
operator = SparsePauliOp.from_sparse_list([("Z", [1], 0.39), ("XX", [0,1], 0.5)], num_qubits = 3)
print(repr(operator))
SparsePauliOp(['IZI', 'IXX'],
coeffs=[0.39+0.j, 0.5 +0.j])
Common non-parametrized gates (Clifford)
Opflow | Alternative |
---|---|
qiskit.opflow.CX , qiskit.opflow.S , qiskit.opflow.H , qiskit.opflow.T , qiskit.opflow.CZ , qiskit.opflow.Swap | Append corresponding gate to qiskit.circuit.QuantumCircuit . If necessary, a qiskit.quantum_info.Operator can be directly constructed from quantum circuits. Another alternative is to wrap the circuit in qiskit.quantum_info.Clifford and call Clifford.to_operator() Note Constructing qiskit.quantum_info operators from circuits is not efficient, as it is a dense operation and scales exponentially with the size of the circuit. |
Example 1: Define the HH operator
Opflow:
from qiskit.opflow import H
operator = H ^ H
print(operator)
┌───┐
q_0: ┤ H ├
├───┤
q_1: ┤ H ├
└───┘
Alternative:
from qiskit import QuantumCircuit
from qiskit.quantum_info import Clifford, Operator
qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)
print(qc)
┌───┐
q_0: ┤ H ├
├───┤
q_1: ┤ H ├
└───┘
To turn this circuit into an operator, you can do the following:
operator = Clifford(qc).to_operator()
# or, directly
operator = Operator(qc)
print(operator)
Operator([[ 0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j],
[ 0.5+0.j, -0.5+0.j, 0.5+0.j, -0.5+0.j],
[ 0.5+0.j, 0.5+0.j, -0.5+0.j, -0.5+0.j],
[ 0.5+0.j, -0.5+0.j, -0.5+0.j, 0.5+0.j]],
input_dims=(2, 2), output_dims=(2, 2))
1-qubit states
Opflow | Alternative |
---|---|
qiskit.opflow.Zero , qiskit.opflow.One , qiskit.opflow.Plus , qiskit.opflow.Minus | qiskit.quantum_info.Statevector or qiskit.circuit.QuantumCircuit , depending on the use case. Note To efficiently simulate stabilizer states, qiskit.quantum_info includes a qiskit.quantum_info.StabilizerState class. See the qiskit.quantum_info.StabilizerState API reference for more information. |
Example 1: Stabilizer states
Opflow:
from qiskit.opflow import Zero, One, Plus, Minus
# Zero, One, Plus, Minus are all stabilizer states
state1 = Zero ^ One
state2 = Plus ^ Minus
print("State 1: ", state1)
print("State 2: ", state2)
State 1: DictStateFn({'01': 1})
State 2: CircuitStateFn(
┌───┐┌───┐
q_0: ┤ X ├┤ H ├
├───┤└───┘
q_1: ┤ H ├─────
└───┘
)
Alternative:
from qiskit import QuantumCircuit
from qiskit.quantum_info import StabilizerState, Statevector
qc_zero = QuantumCircuit(1)
qc_one = qc_zero.copy()
qc_one.x(0)
state1 = Statevector(qc_zero) ^ Statevector(qc_one)
print("State 1: ", state1)
qc_plus = qc_zero.copy()
qc_plus.h(0)
qc_minus = qc_one.copy()
qc_minus.h(0)
state2 = StabilizerState(qc_plus) ^ StabilizerState(qc_minus)
print("State 2: ", state2)
State 1: Statevector([0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
dims=(2, 2))
State 2: StabilizerState(StabilizerTable: ['-IX', '+XI'])
Primitive and List Ops
Most of the workflows that previously relied on components from qiskit.opflow.primitive_ops
and
qiskit.opflow.list_ops
can now leverage elements from qiskit.quantum_info
operators instead.
Some of these classes do not require a one-to-one replacement because they were created to interface with other
opflow components.
Primitive Ops
qiskit.opflow.primitive_ops.PrimitiveOp
is the qiskit.opflow.primitive_ops
module's base class.
It also acts as a factory to instantiate a corresponding sub-class, depending on the computational primitive used
to initialize it.
Interpreting qiskit.opflow.primitive_ops.PrimitiveOp
as a factory class:
Class passed to qiskit.opflow.primitive_ops.PrimitiveOp | Subclass returned |
---|---|
qiskit.quantum_info.Pauli | qiskit.opflow.primitive_ops.PauliOp |
qiskit.circuit.Instruction , qiskit.circuit.QuantumCircuit | qiskit.opflow.primitive_ops.CircuitOp |
list , np.ndarray , scipy.sparse.spmatrix , qiskit.quantum_info.Operator | qiskit.opflow.primitive_ops.MatrixOp |
When migrating opflow code, it is important to look for alternatives to replace the specific subclasses that are used within the original code:
Example 1: PauliSumOp
Opflow:
from qiskit.opflow import PauliSumOp
from qiskit.quantum_info import SparsePauliOp, Pauli
qubit_op = PauliSumOp(SparsePauliOp(Pauli("XYZY"), coeffs=[2]), coeff=-3j)
print(repr(qubit_op))
PauliSumOp(SparsePauliOp(['XYZY'],
coeffs=[2.+0.j]), coeff=(-0-3j))
Alternative:
from qiskit.quantum_info import SparsePauliOp, Pauli
qubit_op = SparsePauliOp(Pauli("XYZY"), coeffs=[-6j])
print(repr(qubit_op))
SparsePauliOp(['XYZY'],
coeffs=[0.-6.j])
Example 2: Z2Symmetries
and TaperedPauliSumOp
Opflow:
from qiskit.opflow import PauliSumOp, Z2Symmetries, TaperedPauliSumOp
qubit_op = PauliSumOp.from_list(
[
("II", -1.0537076071291125),
("IZ", 0.393983679438514),
("ZI", -0.39398367943851387),
("ZZ", -0.01123658523318205),
("XX", 0.1812888082114961),
]
)
z2_symmetries = Z2Symmetries.find_Z2_symmetries(qubit_op)
print(z2_symmetries)
tapered_op = z2_symmetries.taper(qubit_op)
print("Tapered Op from Z2 symmetries: ", tapered_op)
# can be represented as:
tapered_op = TaperedPauliSumOp(qubit_op.primitive, z2_symmetries)
print("Tapered PauliSumOp: ", tapered_op)
Z2 symmetries:
Symmetries:
ZZ
Single-Qubit Pauli X:
IX
Cliffords:
0.7071067811865475 * ZZ
+ 0.7071067811865475 * IX
Qubit index:
[0]
Tapering values:
- Possible values: [1], [-1]
Tapered Op from Z2 symmetries: ListOp([
-1.0649441923622942 * I
+ 0.18128880821149604 * X,
-1.0424710218959303 * I
- 0.7879673588770277 * Z
- 0.18128880821149604 * X
])
Tapered PauliSumOp: -1.0537076071291125 * II
+ 0.393983679438514 * IZ
- 0.39398367943851387 * ZI
- 0.01123658523318205 * ZZ
+ 0.1812888082114961 * XX
Alternative:
from qiskit.quantum_info import SparsePauliOp
from qiskit.quantum_info.analysis import Z2Symmetries
qubit_op = SparsePauliOp.from_list(
[
("II", -1.0537076071291125),
("IZ", 0.393983679438514),
("ZI", -0.39398367943851387),
("ZZ", -0.01123658523318205),
("XX", 0.1812888082114961),
]
)
z2_symmetries = Z2Symmetries.find_z2_symmetries(qubit_op)
print(z2_symmetries)
tapered_op = z2_symmetries.taper(qubit_op)
print("Tapered Op from Z2 symmetries: ", tapered_op)
Z2 symmetries:
Symmetries:
ZZ
Single-Qubit Pauli X:
IX
Cliffords:
SparsePauliOp(['ZZ', 'IX'],
coeffs=[0.70710678+0.j, 0.70710678+0.j])
Qubit index:
[0]
Tapering values:
- Possible values: [1], [-1]
Tapered Op from Z2 symmetries: [SparsePauliOp(['I', 'X'],
coeffs=[-1.06494419+0.j, 0.18128881+0.j]), SparsePauliOp(['I', 'Z', 'X'],
coeffs=[-1.04247102+0.j, -0.78796736+0.j, -0.18128881+0.j])]
ListOps
The qiskit.opflow.list_ops
module contained classes for manipulating lists of qiskit.opflow.primitive_ops
or qiskit.opflow.state_fns
. The qiskit.quantum_info
alternatives for this functionality are
qiskit.quantum_info.PauliList
and qiskit.quantum_info.SparsePauliOp
(for sums of qiskit.quantum_info.Pauli
s).
Opflow | Alternative |
---|---|
qiskit.opflow.list_ops.ListOp | No direct replacement. This is the base class for operator lists. In general, these could be replaced with a Python list . For qiskit.quantum_info.Pauli operators, there are a few alternatives, depending on the use case. One alternative is qiskit.quantum_info.PauliList . |
qiskit.opflow.list_ops.ComposedOp | No direct replacement. Current workflows do not require composing states and operators within one object (no lazy evaluation). |
qiskit.opflow.list_ops.SummedOp | No direct replacement. For qiskit.quantum_info.Pauli operators, use qiskit.quantum_info.SparsePauliOp . |
qiskit.opflow.list_ops.TensoredOp | No direct replacement. For qiskit.quantum_info.Pauli operators, use qiskit.quantum_info.SparsePauliOp . |
State functions
The qiskit.opflow.state_fns
module can generally be replaced by subclasses of the qiskit.quantum_info
qiskit.quantum_info.states.quantum_state.QuantumState
.
Similar to qiskit.opflow.primitive_ops.PrimitiveOp
, qiskit.opflow.state_fns.StateFn
acts as a factory to create the corresponding subclass depending on the computational primitive used to initialize it.
Interpreting qiskit.opflow.state_fns.StateFn
as a factory class:
Examine references to qiskit.opflow.state_fns.StateFn
in opflow code to identify the subclass that is being used, then find an alternative.
Example 1: Apply an operator to a state
Opflow:
from qiskit.opflow import StateFn, X, Y
from qiskit import QuantumCircuit
qc = QuantumCircuit(2)
qc.x(0)
qc.z(1)
op = X ^ Y
state = StateFn(qc)
comp = ~op @ state
eval = comp.eval()
print(state)
print(comp)
print(repr(eval))
CircuitStateFn(
┌───┐
q_0: ┤ X ├
├───┤
q_1: ┤ Z ├
└───┘
)
CircuitStateFn(
┌───┐┌────────────┐
q_0: ┤ X ├┤0 ├
├───┤│ Pauli(XY) │
q_1: ┤ Z ├┤1 ├
└───┘└────────────┘
)
VectorStateFn(Statevector([ 0.0e+00+0.j, 0.0e+00+0.j, -6.1e-17-1.j, 0.0e+00+0.j],
dims=(2, 2)), coeff=1.0, is_measurement=False)
Alternative:
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp, Statevector
qc = QuantumCircuit(2)
qc.x(0)
qc.z(1)
op = SparsePauliOp("XY")
state = Statevector(qc)
eval = state.evolve(op)
print(state)
print(eval)
Statevector([0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
dims=(2, 2))
Statevector([0.+0.j, 0.+0.j, 0.-1.j, 0.+0.j],
dims=(2, 2))
See more examples in Expectations and Converters.
Converters
The role of the qiskit.opflow.converters
submodule was to convert the operators into other opflow operator classes:
(qiskit.opflow.converters.TwoQubitReduction
, qiskit.opflow.converters.PauliBasisChange
, and so on).
The qiskit.opflow.converters.CircuitSampler
traversed an operator and outputted
approximations of its state functions using a quantum computer.
This functionality has been replaced by the qiskit.primitives
.
Opflow | Alternative |
---|---|
qiskit.opflow.converters.CircuitSampler | qiskit.primitives.Sampler or qiskit.primitives.Estimator if used with qiskit.opflow.expectations . See examples below. |
qiskit.opflow.converters.AbelianGrouper | This class allowed a sum a of Pauli operators to be grouped. Similar functionality can be achieved through the qiskit.quantum_info.SparsePauliOp.group_commuting method of qiskit.quantum_info.SparsePauliOp . However, this is not a one-to-one replacement, as you can see in the example below. |
qiskit.opflow.converters.DictToCircuitSum | No direct replacement. This class was used to convert from qiskit.opflow.state_fns.DictStateFn or qiskit.opflow.state_fns.VectorStateFn to an equivalent qiskit.opflow.state_fns.CircuitStateFn . |
qiskit.opflow.converters.PauliBasisChange | No direct replacement. This class was used for changing Paulis into other bases. |
qiskit.opflow.converters.TwoQubitReduction | No direct replacement. This class implements a chemistry-specific reduction for the ParityMapper class in Qiskit Nature. The general symmetry logic this mapper depends on has been refactored to other classes in qiskit.quantum_info , so this specific qiskit.opflow implementation is no longer necessary. |
Example 1: CircuitSampler
for sampling parametrized circuits
Opflow:
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.opflow import ListOp, StateFn, CircuitSampler
from qiskit_aer import AerSimulator
x, y = Parameter("x"), Parameter("y")
circuit1 = QuantumCircuit(1)
circuit1.p(0.2, 0)
circuit2 = QuantumCircuit(1)
circuit2.p(x, 0)
circuit3 = QuantumCircuit(1)
circuit3.p(y, 0)
bindings = {x: -0.4, y: 0.4}
listop = ListOp([StateFn(circuit) for circuit in [circuit1, circuit2, circuit3]])
sampler = CircuitSampler(AerSimulator())
sampled = sampler.convert(listop, params=bindings).eval()
for s in sampled:
print(s)
SparseVectorStateFn( (0, 0) 1.0)
SparseVectorStateFn( (0, 0) 1.0)
SparseVectorStateFn( (0, 0) 1.0)
Alternative:
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.primitives import Sampler
x, y = Parameter("x"), Parameter("y")
circuit1 = QuantumCircuit(1)
circuit1.p(0.2, 0)
circuit1.measure_all() # Sampler primitive requires measurement readout
circuit2 = QuantumCircuit(1)
circuit2.p(x, 0)
circuit2.measure_all()
circuit3 = QuantumCircuit(1)
circuit3.p(y, 0)
circuit3.measure_all()
circuits = [circuit1, circuit2, circuit3]
param_values = [[], [-0.4], [0.4]]
sampler = Sampler()
sampled = sampler.run(circuits, param_values).result().quasi_dists
print(sampled)
[{0: 1.0}, {0: 1.0}, {0: 1.0}]
Example 2: CircuitSampler
for computing expectation values
Opflow:
from qiskit import QuantumCircuit
from qiskit.opflow import X, Z, StateFn, CircuitStateFn, CircuitSampler
from qiskit_aer import AerSimulator
qc = QuantumCircuit(1)
qc.h(0)
state = CircuitStateFn(qc)
hamiltonian = X + Z
expr = StateFn(hamiltonian, is_measurement=True).compose(state)
backend = AerSimulator(method="statevector")
sampler = CircuitSampler(backend)
expectation = sampler.convert(expr)
expectation_value = expectation.eval().real
print(expectation_value)
1.0000000000000002
Alternative:
from qiskit import QuantumCircuit
from qiskit.primitives import Estimator
from qiskit.quantum_info import SparsePauliOp
state = QuantumCircuit(1)
state.h(0)
hamiltonian = SparsePauliOp.from_list([('X', 1), ('Z',1)])
estimator = Estimator()
expectation_value = estimator.run(state, hamiltonian).result().values.real
print(expectation_value)
[1.]
Example 3: AbelianGrouper
for grouping operators
Opflow:
from qiskit.opflow import PauliSumOp, AbelianGrouper
op = PauliSumOp.from_list([("XX", 2), ("YY", 1), ("IZ",2j), ("ZZ",1j)])
grouped_sum = AbelianGrouper.group_subops(op)
print(repr(grouped_sum))
SummedOp([PauliSumOp(SparsePauliOp(['XX'],
coeffs=[2.+0.j]), coeff=1.0), PauliSumOp(SparsePauliOp(['YY'],
coeffs=[1.+0.j]), coeff=1.0), PauliSumOp(SparsePauliOp(['IZ', 'ZZ'],
coeffs=[0.+2.j, 0.+1.j]), coeff=1.0)], coeff=1.0, abelian=False)
Alternative:
from qiskit.quantum_info import SparsePauliOp
op = SparsePauliOp.from_list([("XX", 2), ("YY", 1), ("IZ",2j), ("ZZ",1j)])
grouped = op.group_commuting()
grouped_sum = op.group_commuting(qubit_wise=True)
print(repr(grouped))
print(repr(grouped_sum))
[SparsePauliOp(['IZ', 'ZZ'],
coeffs=[0.+2.j, 0.+1.j]), SparsePauliOp(['XX', 'YY'],
coeffs=[2.+0.j, 1.+0.j])]
[SparsePauliOp(['XX'],
coeffs=[2.+0.j]), SparsePauliOp(['YY'],
coeffs=[1.+0.j]), SparsePauliOp(['IZ', 'ZZ'],
coeffs=[0.+2.j, 0.+1.j])]
Evolutions
The qiskit.opflow.evolutions
submodule was created to provide building blocks for Hamiltonian simulation algorithms,
including methods for Trotterization. The original opflow workflow for Hamiltonian simulation did not allow for
delayed synthesis of the gates or efficient transpilation of the circuits, so this functionality was migrated to the
qiskit.synthesis
Evolution Synthesis module.
The qiskit.opflow.evolutions.PauliTrotterEvolution
class computes evolutions for exponentiated
sums of Paulis by converting to the Z basis, rotating with an RZ, changing back, and Trotterizing.
When calling .convert()
, the class follows a recursive strategy that involves creating
qiskit.opflow.evolutions.EvolvedOp
placeholders for the operators,
constructing qiskit.circuit.library.PauliEvolutionGate
s out of the operator primitives, and supplying one of
the desired synthesis methods to perform the Trotterization. The methods can be specified by using a
string
, which is then inputted into a qiskit.opflow.evolutions.TrotterizationFactory
,
or by supplying a method instance of qiskit.opflow.evolutions.Trotter
,
qiskit.opflow.evolutions.Suzuki
or qiskit.opflow.evolutions.QDrift
.
The Trotterization methods that extend qiskit.opflow.evolutions.TrotterizationBase
were migrated to
qiskit.synthesis
and now extend the qiskit.synthesis.ProductFormula
base class. They no longer contain a .convert()
method for
standalone use, but are designed to be plugged into the qiskit.circuit.library.PauliEvolutionGate
and called by using .synthesize()
.
In this context, the job of the qiskit.opflow.evolutions.PauliTrotterEvolution
class can now be handled directly by the algorithms, for example, qiskit.algorithms.time_evolvers.trotterization.TrotterQRTE
.
Similarly, the qiskit.opflow.evolutions.MatrixEvolution
class performs evolution by classical matrix exponentiation,
constructing a circuit with qiskit.extensions.UnitaryGate
s or qiskit.extensions.HamiltonianGate
s that contain the exponentiation of the operator.
This class is no longer necessary, as the qiskit.extensions.HamiltonianGate
s can be directly handled by the algorithms.
Trotterizations
Opflow | Alternative |
---|---|
qiskit.opflow.evolutions.TrotterizationFactory | No direct replacement. This class was used to create instances of one of the classes listed below. |
qiskit.opflow.evolutions.Trotter | qiskit.synthesis.SuzukiTrotter or qiskit.synthesis.LieTrotter |
qiskit.opflow.evolutions.Suzuki | qiskit.synthesis.SuzukiTrotter |
qiskit.opflow.evolutions.QDrift | qiskit.synthesis.QDrift |
Other evolution classes
Opflow | Alternative |
---|---|
qiskit.opflow.evolutions.EvolutionFactory | No direct replacement. This class was used to create instances of one of the classes listed below. |
qiskit.opflow.evolutions.EvolvedOp | No direct replacement. The workflow no longer requires a specific operator for evolutions. |
qiskit.opflow.evolutions.MatrixEvolution | qiskit.extensions.HamiltonianGate |
qiskit.opflow.evolutions.PauliTrotterEvolution | qiskit.circuit.library.PauliEvolutionGate |
Example 1: Trotter evolution
Opflow:
from qiskit.opflow import Trotter, PauliTrotterEvolution, PauliSumOp
hamiltonian = PauliSumOp.from_list([('X', 1), ('Z',1)])
evolution = PauliTrotterEvolution(trotter_mode=Trotter(), reps=2)
evol_result = evolution.convert(hamiltonian.exp_i())
evolved_state = evol_result.to_circuit()
print(evolved_state)
┌─────────────────────┐
q: ┤ exp(-it (X + Z))(1) ├
└─────────────────────┘
Alternative:
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.synthesis import SuzukiTrotter
hamiltonian = SparsePauliOp.from_list([('X', 1), ('Z',1)])
evol_gate = PauliEvolutionGate(hamiltonian, time=1, synthesis=SuzukiTrotter(reps=2))
evolved_state = QuantumCircuit(1)
evolved_state.append(evol_gate, [0])
print(evolved_state)
┌─────────────────────┐
q: ┤ exp(-it (X + Z))(1) ├
└─────────────────────┘
Example 2: Evolution with time-dependent Hamiltonian
Opflow:
from qiskit.opflow import Trotter, PauliTrotterEvolution, PauliSumOp
from qiskit.circuit import Parameter
time = Parameter('t')
hamiltonian = PauliSumOp.from_list([('X', 1), ('Y',1)])
evolution = PauliTrotterEvolution(trotter_mode=Trotter(), reps=1)
evol_result = evolution.convert((time * hamiltonian).exp_i())
evolved_state = evol_result.to_circuit()
print(evolved_state)
┌─────────────────────────┐
q: ┤ exp(-it (X + Y))(1.0*t) ├
└─────────────────────────┘
Alternative:
from qiskit.quantum_info import SparsePauliOp
from qiskit.synthesis import LieTrotter
from qiskit.circuit.library import PauliEvolutionGate
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
time = Parameter('t')
hamiltonian = SparsePauliOp.from_list([('X', 1), ('Y',1)])
evol_gate = PauliEvolutionGate(hamiltonian, time=time, synthesis=LieTrotter())
evolved_state = QuantumCircuit(1)
evolved_state.append(evol_gate, [0])
print(evolved_state)
┌─────────────────────┐
q: ┤ exp(-it (X + Y))(t) ├
└─────────────────────┘
Example 3: Matrix evolution
Opflow:
from qiskit.opflow import MatrixEvolution, MatrixOp
hamiltonian = MatrixOp([[0, 1], [1, 0]])
evolution = MatrixEvolution()
evol_result = evolution.convert(hamiltonian.exp_i())
evolved_state = evol_result.to_circuit()
print(evolved_state.decompose().decompose())
┌────────────────┐
q: ┤ U3(2,-π/2,π/2) ├
└────────────────┘
Alternative:
from qiskit.quantum_info import SparsePauliOp
from qiskit.extensions import HamiltonianGate
from qiskit import QuantumCircuit
evol_gate = HamiltonianGate([[0, 1], [1, 0]], 1)
evolved_state = QuantumCircuit(1)
evolved_state.append(evol_gate, [0])
print(evolved_state.decompose().decompose())
┌────────────────┐
q: ┤ U3(2,-π/2,π/2) ├
└────────────────┘
Expectations
Expectations are converters that enable an observable's expectation value to be computed with respect to some state function.
This function can now be found in the qiskit.primitives.Estimator
primitive. Remember that there
are different Estimator implementations, as noted previously.
Algorithm-Agnostic Expectations
Opflow | Alternative |
---|---|
qiskit.opflow.expectations.ExpectationFactory | No direct replacement. This class was used to create instances of one of the classes listed below. |
qiskit.opflow.expectations.AerPauliExpectation | Use qiskit_aer.primitives.Estimator . See example below. |
qiskit.opflow.expectations.MatrixExpectation | Use qiskit.primitives.Estimator primitive. If no shots are set, it performs an exact Statevector calculation. See example below. |
qiskit.opflow.expectations.PauliExpectation | Use any Estimator primitive. For qiskit.primitives.Estimator , set shots!=None for a shotbased simulation. For qiskit_aer.primitives.Estimator , this is the default. |
Example 1: Aer Pauli expectation
Opflow:
from qiskit.opflow import X, Minus, StateFn, AerPauliExpectation, CircuitSampler
from qiskit.utils import QuantumInstance
from qiskit_aer import AerSimulator
backend = AerSimulator()
q_instance = QuantumInstance(backend)
sampler = CircuitSampler(q_instance, attach_results=True)
expectation = AerPauliExpectation()
state = Minus
operator = 1j * X
converted_meas = expectation.convert(StateFn(operator, is_measurement=True) @ state)
expectation_value = sampler.convert(converted_meas).eval()
print(expectation_value)
-1j
Alternative:
from qiskit.quantum_info import SparsePauliOp
from qiskit import QuantumCircuit
from qiskit_aer.primitives import Estimator
estimator = Estimator()
op = SparsePauliOp.from_list([("X", 1j)])
states_op = QuantumCircuit(1)
states_op.x(0)
states_op.h(0)
expectation_value = estimator.run(states_op, op).result().values
print(expectation_value)
[0.-1.j]
Example 2: Matrix expectation
Opflow:
from qiskit.opflow import X, H, I, MatrixExpectation, ListOp, StateFn
from qiskit.utils import QuantumInstance
from qiskit_aer import AerSimulator
backend = AerSimulator(method='statevector')
q_instance = QuantumInstance(backend)
sampler = CircuitSampler(q_instance, attach_results=True)
expect = MatrixExpectation()
mixed_ops = ListOp([X.to_matrix_op(), H])
converted_meas = expect.convert(~StateFn(mixed_ops))
plus_mean = converted_meas @ Plus
values_plus = sampler.convert(plus_mean).eval()
print(values_plus)
[(1+0j), (0.7071067811865476+0j)]
Alternative:
from qiskit.primitives import Estimator
from qiskit.quantum_info import SparsePauliOp
from qiskit.quantum_info import Clifford
X = SparsePauliOp("X")
qc = QuantumCircuit(1)
qc.h(0)
H = Clifford(qc).to_operator()
plus = QuantumCircuit(1)
plus.h(0)
estimator = Estimator()
values_plus = estimator.run([plus, plus], [X, H]).result().values
print(values_plus)
[1. 0.70710678]
CVaRExpectation
Opflow | Alternative |
---|---|
qiskit.opflow.expectations.CVaRExpectation | Functionality migrated into new VQE algorithm: qiskit.algorithms.minimum_eigensolvers.SamplingVQE |
Example 1: VQE with CVaR
Opflow:
from qiskit.opflow import CVaRExpectation, PauliSumOp
from qiskit.algorithms import VQE
from qiskit.algorithms.optimizers import SLSQP
from qiskit.circuit.library import TwoLocal
from qiskit_aer import AerSimulator
backend = AerSimulator(method="statevector")
ansatz = TwoLocal(2, 'ry', 'cz')
op = PauliSumOp.from_list([('ZZ',1), ('IZ',1), ('II',1)])
alpha = 0.2
cvar_expectation = CVaRExpectation(alpha=alpha)
opt = SLSQP(maxiter=1000)
vqe = VQE(ansatz, expectation=cvar_expectation, optimizer=opt, quantum_instance=backend)
result = vqe.compute_minimum_eigenvalue(op)
print(result.eigenvalue)
(-1+0j)
Alternative:
from qiskit.quantum_info import SparsePauliOp
from qiskit.algorithms.minimum_eigensolvers import SamplingVQE
from qiskit.algorithms.optimizers import SLSQP
from qiskit.circuit.library import TwoLocal
from qiskit.primitives import Sampler
ansatz = TwoLocal(2, 'ry', 'cz')
op = SparsePauliOp.from_list([('ZZ',1), ('IZ',1), ('II',1)])
opt = SLSQP(maxiter=1000)
alpha = 0.2
vqe = SamplingVQE(Sampler(), ansatz, opt, aggregation=alpha)
result = vqe.compute_minimum_eigenvalue(op)
print(result.eigenvalue)
-1.0
Gradients
The opflow qiskit.opflow.gradients
framework has been replaced by the qiskit.algorithms.gradients
module. The new gradients are primitive-based subroutines commonly used by algorithms and applications, which
can also be run standalone. For this reason, they now reside under qiskit.algorithms
.
The former gradient framework contained base classes, converters, and derivatives. The "derivatives" followed a factory design pattern, where different methods could be provided by using string identifiers to each of these classes. The new gradient framework contains two main families of subroutines: Gradients and QGT/QFI. The Gradients can either be Sampler or Estimator based, while the current QGT/QFI implementations are Estimator based.
This leads to a change in the workflow:
Previous workflow
from qiskit.opflow import Gradient
grad = Gradient(method="param_shift")
# task based on expectation value computations + gradients
New workflow
We now explicitly import the desired class, depending on the target primitive (Sampler or Estimator) and target method:
from qiskit.algorithms.gradients import ParamShiftEstimatorGradient
from qiskit.primitives import Estimator
grad = ParamShiftEstimatorGradient(Estimator())
# task based on expectation value computations + gradients
This works similarly for the QFI class:
Previous workflow
from qiskit.opflow import QFI
qfi = QFI(method="lin_comb_full")
# task based on expectation value computations + QFI
New workflow
There is a generic QFI implementation that can be initialized with different QGT (Quantum Gradient Tensor) implementations:
from qiskit.algorithms.gradients import LinCombQGT, QFI
from qiskit.primitives import Estimator
qgt = LinCombQGT(Estimator())
qfi = QFI(qgt)
# task based on expectation value computations + QFI
Here is a quick guide for migrating the most common gradient settings. All new gradient imports follow the format:
from qiskit.algorithms.gradients import MethodPrimitiveGradient, QFI
Gradients:
Opflow | Alternative |
---|---|
Gradient(method="lin_comb") | LinCombEstimatorGradient(estimator=estimator) or LinCombSamplerGradient(sampler=sampler) |
Gradient(method="param_shift") | ParamShiftEstimatorGradient(estimator=estimator) or ParamShiftSamplerGradient(sampler=sampler) |
Gradient(method="fin_diff") | FiniteDiffEstimatorGradient(estimator=estimator) or ParamShiftSamplerGradient(sampler=sampler) |
QFI/QGT:
Opflow | Alternative |
---|---|
QFI(method="lin_comb_full") | qgt=LinCombQGT(Estimator()) |
Other auxiliary classes in the legacy gradient framework have been deprecated. Here is the complete migration list:
Opflow | Alternative |
---|---|
qiskit.opflow.gradients.DerivativeBase | No replacement. This was the base class for the gradient, hessian, and QFI base classes. |
qiskit.opflow.gradients.GradientBase and qiskit.opflow.gradients.Gradient | qiskit.algorithms.gradients.BaseSamplerGradient or qiskit.algorithms.gradients.BaseEstimatorGradient , and specific subclasses per method, as explained above. |
qiskit.opflow.gradients.HessianBase and qiskit.opflow.gradients.Hessian | No replacement. The new gradient framework does not work with hessians as independent objects. |
qiskit.opflow.gradients.QFIBase and qiskit.opflow.gradients.QFI | The new qiskit.algorithms.gradients.QFI class extends QGT, so the corresponding base class is qiskit.algorithms.gradients.BaseQGT |
qiskit.opflow.gradients.CircuitGradient | No replacement. This class was used to convert between circuit and gradient qiskit.opflow.primitive_ops.PrimitiveOp and this functionality is no longer necessary. |
qiskit.opflow.gradients.CircuitQFI | No replacement. This class was used to convert between circuit and QFI qiskit.opflow.primitive_ops.PrimitiveOp and this functionality is no longer necessary. |
qiskit.opflow.gradients.NaturalGradient | No replacement. The same functionality can be achieved with the QFI module. |
Example 1: Finite differences batched gradient
Opflow:
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.opflow import Gradient, X, Z, StateFn, CircuitStateFn
import numpy as np
ham = 0.5 * X - 1 * Z
a = Parameter("a")
b = Parameter("b")
c = Parameter("c")
params = [a,b,c]
qc = QuantumCircuit(1)
qc.h(0)
qc.u(a, b, c, 0)
qc.h(0)
op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0)
# the gradient class acted similarly opflow converters,
# with a .convert() step and an .eval() step
state_grad = Gradient(grad_method="param_shift").convert(operator=op, params=params)
# the old workflow did not allow for batched evaluation of parameter values
values_dict = [{a: np.pi / 4, b: 0, c: 0}, {a: np.pi / 4, b: np.pi / 4, c: np.pi / 4}]
gradients = []
for i, value_dict in enumerate(values_dict):
gradients.append(state_grad.assign_parameters(value_dict).eval())
print(gradients)
[[(0.35355339059327356+0j), (-1.182555756156289e-16+0j), (-1.6675e-16+0j)], [(0.10355339059327384+0j), (0.8535533905932734+0j), (1.103553390593273+0j)]]
Alternative:
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.primitives import Estimator
from qiskit.algorithms.gradients import ParamShiftEstimatorGradient
from qiskit.quantum_info import SparsePauliOp
import numpy as np
ham = SparsePauliOp.from_list([("X", 0.5), ("Z", -1)])
a = Parameter("a")
b = Parameter("b")
c = Parameter("c")
qc = QuantumCircuit(1)
qc.h(0)
qc.u(a, b, c, 0)
qc.h(0)
estimator = Estimator()
gradient = ParamShiftEstimatorGradient(estimator)
# The new workflow follows an interface close to that of the primitives.
param_list = [[np.pi / 4, 0, 0], [np.pi / 4, np.pi / 4, np.pi / 4]]
# For batched evaluations, the number of circuits must match the
# number of parameter value sets.
gradients = gradient.run([qc] * 2, [ham] * 2, param_list).result().gradients
print(gradients)
[array([ 3.53553391e-01, 0.00000000e+00, -1.80411242e-16]), array([0.10355339, 0.85355339, 1.10355339])]
Example 2: QFI
Opflow:
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.opflow import QFI, CircuitStateFn
import numpy as np
# Create the circuit.
a, b = Parameter("a"), Parameter("b")
qc = QuantumCircuit(1)
qc.h(0)
qc.rz(a, 0)
qc.rx(b, 0)
# Convert the circuit to a QFI object.
op = CircuitStateFn(qc)
qfi = QFI(qfi_method="lin_comb_full").convert(operator=op)
# Bind parameters and evaluate.
values_dict = {a: np.pi / 4, b: 0.1}
qfi = qfi.bind_parameters(values_dict).eval()
print(qfi)
[[ 1.00000000e+00+0.j -3.63575685e-16+0.j]
[-3.63575685e-16+0.j 5.00000000e-01+0.j]]
Alternative:
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.primitives import Estimator
from qiskit.algorithms.gradients import LinCombQGT, QFI
import numpy as np
# Create the circuit.
a, b = Parameter("a"), Parameter("b")
qc = QuantumCircuit(1)
qc.h(0)
qc.rz(a, 0)
qc.rx(b, 0)
# Initialize QFI.
estimator = Estimator()
qgt = LinCombQGT(estimator)
qfi = QFI(qgt)
# Evaluate
values_list = [[np.pi / 4, 0.1]]
qfi = qfi.run(qc, values_list).result().qfis
print(qfi)
[array([[ 1.00000000e+00, -1.50274614e-16],
[-1.50274614e-16, 5.00000000e-01]])]