Skip to main contentIBM Quantum Documentation Preview

Opflow migration guide

Caution

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.

Note

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.

Note

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:


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.

OpflowAlternative
qiskit.opflow.OperatorBaseqiskit.quantum_info.BaseOperator
Note

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 in qiskit.opflow (for example ~ for .adjoint()) are not defined for qiskit.quantum_info.BaseOperator. You might want to check the specific qiskit.quantum_info subclass instead.

  • qiskit.opflow.OperatorBase also implements methods such as .to_matrix() or .to_spmatrix(), which are only found in some of the qiskit.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.

OpflowAlternative
qiskit.opflow.X, qiskit.opflow.Y, qiskit.opflow.Z, qiskit.opflow.Iqiskit.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)

OpflowAlternative
qiskit.opflow.CX, qiskit.opflow.S, qiskit.opflow.H, qiskit.opflow.T, qiskit.opflow.CZ, qiskit.opflow.SwapAppend 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

OpflowAlternative
qiskit.opflow.Zero, qiskit.opflow.One, qiskit.opflow.Plus, qiskit.opflow.Minusqiskit.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.

When migrating opflow code, it is important to look for alternatives to replace the specific subclasses that are used within the original code:

OpflowAlternative
qiskit.opflow.primitive_ops.PrimitiveOpAs mentioned previously, this class is used to generate an instance of one of the classes below, so there is no direct replacement.
qiskit.opflow.primitive_ops.CircuitOpqiskit.circuit.QuantumCircuit
qiskit.opflow.primitive_ops.MatrixOpqiskit.quantum_info.Operator
qiskit.opflow.primitive_ops.PauliOpqiskit.quantum_info.Pauli. For direct compatibility with classes in qiskit.algorithms, wrap in qiskit.quantum_info.SparsePauliOp.
qiskit.opflow.primitive_ops.PauliSumOpqiskit.quantum_info.SparsePauliOp. See example below.
qiskit.opflow.primitive_ops.TaperedPauliSumOpThis class was used to combine a qiskit.opflow.primitive_ops.PauliSumOp with its identified symmetries in one object. This functionality is not currently used in any workflow, and has been deprecated without replacement. See qiskit.quantum_info.analysis.Z2Symmetries example for updated workflow.
qiskit.opflow.primitive_ops.Z2Symmetriesqiskit.quantum_info.analysis.Z2Symmetries. See example below.

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.Paulis).

OpflowAlternative
qiskit.opflow.list_ops.ListOpNo 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.ComposedOpNo direct replacement. Current workflows do not require composing states and operators within one object (no lazy evaluation).
qiskit.opflow.list_ops.SummedOpNo direct replacement. For qiskit.quantum_info.Pauli operators, use qiskit.quantum_info.SparsePauliOp.
qiskit.opflow.list_ops.TensoredOpNo 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.

Examine references to qiskit.opflow.state_fns.StateFn in opflow code to identify the subclass that is being used, then find an alternative.

OpflowAlternative
qiskit.opflow.state_fns.StateFnIn most cases, qiskit.quantum_info.Statevector. However, remember that qiskit.opflow.state_fns.StateFn is a factory class.
qiskit.opflow.state_fns.CircuitStateFnqiskit.quantum_info.Statevector
qiskit.opflow.state_fns.DictStateFnThis class was used to store efficient representations of sparse measurement results. The qiskit.primitives.Sampler now returns the measurements as an instance of qiskit.result.QuasiDistribution. See the example in Converters.
qiskit.opflow.state_fns.VectorStateFnThis class can be replaced with qiskit.quantum_info.Statevector or qiskit.quantum_info.StabilizerState, for Clifford-based vectors.
qiskit.opflow.state_fns.SparseVectorStateFnNo direct replacement. This class was used for sparse statevector representations.
qiskit.opflow.state_fns.OperatorStateFnNo direct replacement. This class was used to represent measurements against operators.
qiskit.opflow.state_fns.CVaRMeasurementUsed in qiskit.opflow.expectations.CVaRExpectation. This function is now covered by qiskit.algorithms.minimum_eigensolvers.SamplingVQE. See the example in Expectations.

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.

OpflowAlternative
qiskit.opflow.converters.CircuitSamplerqiskit.primitives.Sampler or qiskit.primitives.Estimator if used with qiskit.opflow.expectations. See examples below.
qiskit.opflow.converters.AbelianGrouperThis 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.DictToCircuitSumNo 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.PauliBasisChangeNo direct replacement. This class was used for changing Paulis into other bases.
qiskit.opflow.converters.TwoQubitReductionNo 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.

Note

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

Other evolution classes

OpflowAlternative
qiskit.opflow.evolutions.EvolutionFactoryNo direct replacement. This class was used to create instances of one of the classes listed below.
qiskit.opflow.evolutions.EvolvedOpNo direct replacement. The workflow no longer requires a specific operator for evolutions.
qiskit.opflow.evolutions.MatrixEvolutionqiskit.extensions.HamiltonianGate
qiskit.opflow.evolutions.PauliTrotterEvolutionqiskit.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

OpflowAlternative
qiskit.opflow.expectations.ExpectationFactoryNo direct replacement. This class was used to create instances of one of the classes listed below.
qiskit.opflow.expectations.AerPauliExpectationUse qiskit_aer.primitives.Estimator. See example below.
qiskit.opflow.expectations.MatrixExpectationUse qiskit.primitives.Estimator primitive. If no shots are set, it performs an exact Statevector calculation. See example below.
qiskit.opflow.expectations.PauliExpectationUse 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

OpflowAlternative
qiskit.opflow.expectations.CVaRExpectationFunctionality 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
Note

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:

OpflowAlternative
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:

OpflowAlternative
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:

OpflowAlternative
qiskit.opflow.gradients.DerivativeBaseNo replacement. This was the base class for the gradient, hessian, and QFI base classes.
qiskit.opflow.gradients.GradientBase and qiskit.opflow.gradients.Gradientqiskit.algorithms.gradients.BaseSamplerGradient or qiskit.algorithms.gradients.BaseEstimatorGradient, and specific subclasses per method, as explained above.
qiskit.opflow.gradients.HessianBase and qiskit.opflow.gradients.HessianNo replacement. The new gradient framework does not work with hessians as independent objects.
qiskit.opflow.gradients.QFIBase and qiskit.opflow.gradients.QFIThe new qiskit.algorithms.gradients.QFI class extends QGT, so the corresponding base class is qiskit.algorithms.gradients.BaseQGT
qiskit.opflow.gradients.CircuitGradientNo 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.CircuitQFINo 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.NaturalGradientNo 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]])]