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.OperatorBaseimplements 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_infosubclass instead. - 
qiskit.opflow.OperatorBasealso implements methods such as.to_matrix()or.to_spmatrix(), which are only found in some of theqiskit.quantum_info.BaseOperatorsubclasses. 
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 * XXAlternative:
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.Paulis).
| 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.0000000000000002Alternative:
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.PauliEvolutionGates 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.UnitaryGates or qiskit.extensions.HamiltonianGates that contain the exponentiation of the operator.
This class is no longer necessary, as the qiskit.extensions.HamiltonianGates 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)-1jAlternative:
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.0Gradients
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 + gradientsNew 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 + gradientsThis 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 + QFINew 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 + QFIHere is a quick guide for migrating the most common gradient settings. All new gradient imports follow the format:
from qiskit.algorithms.gradients import MethodPrimitiveGradient, QFIGradients:
| 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]])]