Opflow Migration Guide
요약
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.
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:
본문
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.
주의
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.
옵플로우 |
Alternative |
X , Y , Z , I
|
Pauli
|
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])
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
옵플로우 |
Alternative |
CX , S , H , T ,
CZ , Swap
|
Append corresponding gate to QuantumCircuit . If necessary,
qiskit.quantum_info.Operator s 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.
|
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
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.
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
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])
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])]
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.
팁
Interpreting StateFn
as a factory class:
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
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
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}]
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)
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)
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 PauliEvolutionGate
s 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 UnitaryGate
s or HamiltonianGate
s containing the exponentiation of the operator.
This class is no longer necessary, as the HamiltonianGate
s can be directly handled by the algorithms.
Other Evolution Classes
Back to Contents
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) ├
└─────────────────────┘
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) ├
└─────────────────────┘
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
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)
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)
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)
CVaRExpectation
Back to Contents
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)
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)
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
옵플로우 |
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)
|
옵플로우 |
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
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])]
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]])]