Opflow Migration Guide#

TL;DR#

The new primitives, in combination with the quantum_info module, have superseded functionality of opflow. Thus, the latter is being deprecated.

In this migration guide, you will find instructions and code examples for how to migrate your code based on the opflow module to the primitives and quantum_info modules.

注釈

The use of opflow was tightly coupled to the QuantumInstance class, which is also being deprecated. For more information on migrating the QuantumInstance, please read 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 IBM’s Qiskit Runtime primitives (qiskit_ibm_runtime.Sampler/qiskit_ibm_runtime.Estimator). Specific backends can be wrapped with (qiskit.primitives.BackendSampler, qiskit.primitives.BackendEstimator) to also present primitive-compatible interfaces.

Certain classes, such as the 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 opflow module was originally introduced as a layer between circuits and algorithms, a series of building blocks for quantum algorithms research and development.

The recent release of the qiskit.primitives introduced a new paradigm for interacting with backends. Now, instead of preparing a circuit to execute with a backend.run() type of method, the algorithms can leverage the Sampler and Estimator primitives, send parametrized circuits and observables, and directly receive quasi-probability distributions or expectation values (respectively). This workflow simplifies considerably the pre-processing and post-processing steps that previously relied on this module; encouraging us to move away from opflow and find new paths for developing algorithms based on the primitives interface and the quantum_info module.

This guide traverses the opflow submodules and provides either a direct alternative (i.e., using quantum_info), or an explanation of how to replace their functionality in algorithms.

The functional equivalency can be roughly summarized as follows:

Contents#

This document covers the migration from these opflow submodules:

Operators

Converters

Gradients

Operator Base Class#

Back to Contents

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 to each other, and the transition should be handled with care. Namely:

1. qiskit.opflow.OperatorBase implements a broader algebra mixin. Some operator overloads that were commonly used opflow (for example ~ for .adjoint()) are not defined for qiskit.quantum_info.BaseOperator. You might want to check the specific quantum_info subclass instead.

2. 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 OperatorBase and BaseOperator API references for more information.

Operator Globals#

Back to Contents

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 quantum_info class: Pauli, Clifford or Statevector.

1-Qubit Paulis#

Back to Contents

The 1-qubit paulis were commonly used for quick testing of algorithms, 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 PauliSumOp, and can be replaced by directly creating a corresponding SparsePauliOp, as shown in the examples below.

Opflow

Alternative

X, Y, Z, I

Pauli

Tip

For direct compatibility with classes in algorithms, wrap in SparsePauliOp.

Example 1: Defining 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: Defining 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)#

Back to Contents

Opflow

Alternative

CX, S, H, T, CZ, Swap

Append corresponding gate to QuantumCircuit. If necessary, qiskit.quantum_info.Operators can be directly constructed from quantum circuits. Another alternative is to wrap the circuit in Clifford and call Clifford.to_operator().

注釈

Constructing quantum_info operators from circuits is not efficient, as it is a dense operation and scales exponentially with the size of the circuit, use with care.

Example 1: Defining 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 ├
     └───┘

If we want to turn this circuit into an operator, we can do:

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#

Back to Contents

Opflow

Alternative

Zero, One, Plus, Minus

Statevector or simply QuantumCircuit, depending on the use case.

注釈

For efficient simulation of stabilizer states, quantum_info includes a StabilizerState class. See API reference of StabilizerState for more info.

Example 1: Working with 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#

Back to Contents

Most of the workflows that previously relied on components from primitive_ops and list_ops can now leverage elements from quantum_info's operators instead. Some of these classes do not require a 1-1 replacement because they were created to interface with other opflow components.

Primitive Ops#

Back to Contents

PrimitiveOp is the 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.

Tip

Interpreting PrimitiveOp as a factory class:

Class passed to PrimitiveOp

Subclass returned

Pauli

PauliOp

Instruction, QuantumCircuit

CircuitOp

list, np.ndarray, scipy.sparse.spmatrix, Operator

MatrixOp

Thus, when migrating opflow code, it is important to look for alternatives to replace the specific subclasses that are used 「under the hood」 in the original code:

Opflow

Alternative

PrimitiveOp

As mentioned above, this class is used to generate an instance of one of the classes below, so there is no direct replacement.

CircuitOp

QuantumCircuit

MatrixOp

Operator

PauliOp

Pauli. For direct compatibility with classes in qiskit.algorithms, wrap in SparsePauliOp.

PauliSumOp

SparsePauliOp. See example below.

TaperedPauliSumOp

This class was used to combine a 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.Z2Symmetries

qiskit.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#

Back to Contents

The list_ops module contained classes for manipulating lists of primitive_ops or state_fns. The quantum_info alternatives for this functionality are the PauliList and SparsePauliOp (for sums of Paulis).

Opflow

Alternative

ListOp

No direct replacement. This is the base class for operator lists. In general, these could be replaced with Python lists. For Pauli operators, there are a few alternatives, depending on the use-case. One alternative is PauliList.

ComposedOp

No direct replacement. Current workflows do not require composition of states and operators within one object (no lazy evaluation).

SummedOp

No direct replacement. For Pauli operators, use SparsePauliOp.

TensoredOp

No direct replacement. For Pauli operators, use SparsePauliOp.

State Functions#

Back to Contents

The state_fns module can be generally replaced by subclasses of quantum_info's qiskit.quantum_info.QuantumState.

Similarly to PrimitiveOp, StateFn acts as a factory to create the corresponding subclass depending on the computational primitive used to initialize it.

Tip

Interpreting StateFn as a factory class:

Class passed to StateFn

Sub-class returned

str, dict, Result

DictStateFn

list, np.ndarray, Statevector

VectorStateFn

QuantumCircuit, Instruction

CircuitStateFn

OperatorBase

OperatorStateFn

This means that references to StateFn in opflow code should be examined to identify the subclass that is being used, to then look for an alternative.

Opflow

Alternative

StateFn

In most cases, Statevector. However, please remember that StateFn is a factory class.

CircuitStateFn

Statevector

DictStateFn

This class was used to store efficient representations of sparse measurement results. The Sampler now returns the measurements as an instance of QuasiDistribution (see example in Converters).

VectorStateFn

This class can be replaced with Statevector or StabilizerState (for Clifford-based vectors).

SparseVectorStateFn

No direct replacement. This class was used for sparse statevector representations.

OperatorStateFn

No direct replacement. This class was used to represent measurements against operators.

CVaRMeasurement

Used in CVaRExpectation. Functionality now covered by SamplingVQE. See example in Expectations.

Example 1: Applying 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 applied examples in Expectations and Converters.

Converters#

Back to Contents

The role of the qiskit.opflow.converters submodule was to convert the operators into other opflow operator classes (TwoQubitReduction, PauliBasisChange…). In the case of the CircuitSampler, it traversed an operator and outputted approximations of its state functions using a quantum backend. Notably, this functionality has been replaced by the primitives.

Opflow

Alternative

CircuitSampler

Sampler or Estimator if used with expectations. See examples below.

AbelianGrouper

This class allowed a sum a of Pauli operators to be grouped, a similar functionality can be achieved through the group_commuting() method of qiskit.quantum_info.SparsePauliOp, although this is not a 1-1 replacement, as you can see in the example below.

DictToCircuitSum

No direct replacement. This class was used to convert from DictStateFns or VectorStateFns to equivalent CircuitStateFns.

PauliBasisChange

No direct replacement. This class was used for changing Paulis into other bases.

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 quantum_info, so this specific 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#

Back to Contents

The qiskit.opflow.evolutions submodule was created to provide building blocks for Hamiltonian simulation algorithms, including various 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 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 EvolvedOp placeholders for the operators, constructing PauliEvolutionGates out of the operator primitives, and supplying one of the desired synthesis methods to perform the Trotterization. The methods can be specified via string, which is then inputted into a TrotterizationFactory, or by supplying a method instance of qiskit.opflow.evolutions.Trotter, qiskit.opflow.evolutions.Suzuki or qiskit.opflow.evolutions.QDrift.

The different 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 now designed to be plugged into the PauliEvolutionGate and called via .synthesize(). In this context, the job of the qiskit.opflow.evolutions.PauliTrotterEvolution class can now be handled directly by the algorithms (for example, TrotterQRTE).

In a similar manner, the qiskit.opflow.evolutions.MatrixEvolution class performs evolution by classical matrix exponentiation, constructing a circuit with UnitaryGates or HamiltonianGates containing the exponentiation of the operator. This class is no longer necessary, as the HamiltonianGates can be directly handled by the algorithms.

Trotterizations#

Back to Contents

Opflow

Alternative

TrotterizationFactory

No direct replacement. This class was used to create instances of one of the classes listed below.

Trotter

qiskit.synthesis.SuzukiTrotter or qiskit.synthesis.LieTrotter

Suzuki

qiskit.synthesis.SuzukiTrotter

QDrift

qiskit.synthesis.QDrift

Other Evolution Classes#

Back to Contents

Opflow

Alternative

EvolutionFactory

No direct replacement. This class was used to create instances of one of the classes listed below.

EvolvedOp

No direct replacement. The workflow no longer requires a specific operator for evolutions.

MatrixEvolution

HamiltonianGate

PauliTrotterEvolution

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#

Back to Contents

Expectations are converters which enable the computation of the expectation value of an observable with respect to some state function. This functionality can now be found in the Estimator primitive. Please remember that there are different Estimator implementations, as noted here

Algorithm-Agnostic Expectations#

Back to Contents

Opflow

Alternative

ExpectationFactory

No direct replacement. This class was used to create instances of one of the classes listed below.

AerPauliExpectation

Use qiskit_aer.primitives.Estimator with approximation=True, and then shots=None as run_options. See example below.

MatrixExpectation

Use qiskit.primitives.Estimator primitive (if no shots are set, it performs an exact Statevector calculation). See example below.

PauliExpectation

Use any Estimator primitive (for qiskit.primitives.Estimator, set shots!=None for a shot-based 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(approximation=True, run_options={"shots": None})

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#

Back to Contents

Opflow

Alternative

CVaRExpectation

Functionality migrated into new VQE algorithm: 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#

Back to Contents

The opflow gradients framework has been replaced by the new qiskit.algorithms.gradients module. The new gradients are primitive-based subroutines commonly used by algorithms and applications, which can also be executed in a standalone manner. 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 via 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, where instead of doing:

from qiskit.opflow import Gradient

grad = Gradient(method="param_shift")

# task based on expectation value computations + gradients

We now import explicitly the desired class, depending on the target primitive (Sampler/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, where instead of doing:

from qiskit.opflow import QFI

qfi = QFI(method="lin_comb_full")

# task based on expectation value computations + QFI

You now have 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. Please note that 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()) QFI(qgt=qgt)

Other auxiliary classes in the legacy gradient framework have now been deprecated. Here is the complete migration list:

Opflow

Alternative

DerivativeBase

No replacement. This was the base class for the gradient, hessian and QFI base classes.

GradientBase and Gradient

BaseSamplerGradient or BaseEstimatorGradient, and specific subclasses per method, as explained above.

HessianBase and Hessian

No replacement. The new gradient framework does not work with hessians as independent objects.

QFIBase and QFI

The new QFI class extends QGT, so the corresponding base class is BaseQGT

CircuitGradient

No replacement. This class was used to convert between circuit and gradient PrimitiveOp, and this functionality is no longer necessary.

CircuitQFI

No replacement. This class was used to convert between circuit and QFI PrimitiveOp, and this functionality is no longer necessary.

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 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]])]