Observables for expectation values

Observables play a crucial role in computing the expectation value in conjunction with a wave function. Currently, observables constructed purely from the Pauli group \(\{\hat{X},\hat{Y},\hat{Z},\hat{I}\}\) are supported. In the context of sQUlearn, observables are mandatory inputs for Quantum Neural Networks (QNNs) and can be employed in the Projected Quantum Kernel program. All operators follow the Base Class ObservableBase. sQulearn features several predefined observables, but it is also possible to simply construct custom observables.

Implemented observables.

The following predefined observables are available in the module squlearn.observables:

SinglePauli

Observable constructed from a single Pauli operator of a single Qubit.

SummedPaulis

Observable for summation of single Pauli operators.

SingleProbability

Observable for measuring the probability of being in state 0 or 1 of a specified qubit.

SummedProbabilities

Observable for summing single Qubit probabilities of binary states.

IsingHamiltonian

Implementation of Ising type Hamiltonians.

CustomObservable

Class for defining a custom observable.

The observables are simply constructed by initializing the associated class.

Example: Summed Pauli observable

from squlearn.observables import SummedPaulis

op = SummedPaulis(num_qubits=2)
print(op)
SparsePauliOp(['II', 'IZ', 'ZI'],
              coeffs=[<qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f12142610>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f12142670>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f121426d0>])

Custom observables

sQUlearn features several options to construct custom observables. With the class CustomObservable, it is possible to construct observables from a string containing the letters of the Pauli matrices ("X", "Y", "Z", "I"). The resulting observable can be multiplied by a parameter by setting parameterized=True. Furthermore, observables can be added by + or multiplied by * with each other. This allows the creation of arbitrary observables.

Example: Custom observable

from squlearn.observables import CustomObservable

ob1 = CustomObservable(num_qubits=2, operator_string="IX",parameterized=True)
ob2 = CustomObservable(num_qubits=2, operator_string="ZY",parameterized=True)
added_ob = ob1 + ob2
print("Added observable:\n",added_ob,"\n\n")
squared_ob = added_ob*added_ob
print("Squared observable:\n",squared_ob)
Added observable:
 SparsePauliOp(['IX', 'ZY'],
              coeffs=[<qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f12143390>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f121433f0>]) 


Squared observable:
 SparsePauliOp(['II', 'ZZ'],
              coeffs=[<qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f12143690>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f121436f0>])

Example: More complex custom observable

It is also possible to construct more complex observables by supplying a list of strings containing the Pauli matrices. The observable is then constructed by adding the single observables together.

from squlearn.observables import CustomObservable

# It is also possible to add trainable parameters:
ob = CustomObservable(num_qubits=4, operator_string=["ZIZZ", "XIXI"], parameterized=True)
print("Custom observable with multiple operators:\n",ob)
Custom observable with multiple operators:
 SparsePauliOp(['ZIZZ', 'XIXI'],
              coeffs=[<qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f12142f10>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f12143210>])

Note that in Qiskit, the qubits are counted from the right to the left as in the computational basis!

Mapping observables to real qubits

When running on a backend, the number of physical qubits may change from the number of qubits the definition of the observable. If it is necessary, it is possible to adjust the observable to the physical qubits. This is achieved by providing a map from the qubits of the observable to the physical qubits utilized for example in the feature map via the function set_qubit_map(). The map can be for example obtained in the transpiled encoding circuit.

Example: Use the mapping from the transpiled encoding circuit

from squlearn.encoding_circuit import ChebyshevRx,TranspiledEncodingCircuit
from squlearn.observables import SummedPaulis
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
fm = TranspiledEncodingCircuit(
 ChebyshevRx(3,1),
 backend=FakeManilaV2(),
 num_features=1,
 initial_layout=[0,1,4]
)
ob = SummedPaulis(num_qubits=3, op_str="Z")
print("Observable before mapping:\n",ob,"\n\n")
ob.set_map(fm.qubit_map, fm.num_physical_qubits)
print("Observable after mapping:\n",ob)
Observable before mapping:
 SparsePauliOp(['III', 'IIZ', 'IZI', 'ZII'],
              coeffs=[<qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11daad30>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11daa730>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11daa5b0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11daa610>]) 


Observable after mapping:
 SparsePauliOp(['IIIII', 'IIIIZ', 'IIIZI', 'ZIIII'],
              coeffs=[<qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab510>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab570>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab5d0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab630>])

Derivatives of the observable

In sQUlearn it is also possible to calculate derivatives of observables as for example needed during the training of the QNN. This is possible with the class ObservableDerivatives. The derivatives are calculated with respect to the parameters of the observable.

Example: first-order derivatives of the Ising Hamiltonian

from squlearn.observables import IsingHamiltonian
from squlearn.observables.observable_derivatives import ObservableDerivatives
ob = IsingHamiltonian(num_qubits=3)
print("Observable:\n", ob,"\n\n")
print("Gradient of the observable:\n", ObservableDerivatives(ob).get_derivative("dop"))
Observable:
 SparsePauliOp(['III', 'IIZ', 'IZI', 'ZII', 'IZZ', 'ZIZ', 'ZZI'],
              coeffs=[<qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11daaeb0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab2d0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11daafd0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab390>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab1b0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11daaf10>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11daaaf0>]) 


Gradient of the observable:
 [1.0*SparsePauliOp(['III'],
              coeffs=[1.+0.j]), 1.0*SparsePauliOp(['IIZ'],
              coeffs=[1.+0.j]), 1.0*SparsePauliOp(['IZI'],
              coeffs=[1.+0.j]), 1.0*SparsePauliOp(['ZII'],
              coeffs=[1.+0.j]), 1.0*SparsePauliOp(['IZZ'],
              coeffs=[1.+0.j]), 1.0*SparsePauliOp(['ZIZ'],
              coeffs=[1.+0.j]), 1.0*SparsePauliOp(['ZZI'],
              coeffs=[1.+0.j])]

Example: higher-order derivative of the cubed SummedPaulis observable

Furthermore, arbitrary derivatives can be computed by supplying a tuple, although the higher-order derivatives are zero for linear parameters. To achieve this, the function ObservableDerivatives.get_derivative() can be used with a tuple of parameters ObservableDerivatives.parameter_vector().

from squlearn.observables import SummedPaulis
from squlearn.observables.observable_derivatives import ObservableDerivatives

# Build cubed SummedPaulis observable
op = SummedPaulis(num_qubits=2)
op3 = op*op*op
print("Cubed operator:\n",op3)

# Get the Hessian from a tuple
deriv = ObservableDerivatives(op3)
print("Second-order derivative w.r.t. p[0]:\n",
         deriv.get_derivative((deriv.parameter_vector[0],deriv.parameter_vector[0])))
Cubed operator:
 SparsePauliOp(['II', 'IZ', 'ZI', 'ZZ'],
              coeffs=[<qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab5d0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab450>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dc80f0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dc8090>])
Second-order derivative w.r.t. p[0]:
 [1.0*[1.0*SparsePauliOp(['II', 'IZ', 'ZI'],
              coeffs=[<qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab1b0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab210>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dab750>])]]

Example: Squared summed Pauli Observable

It is also possible to calculate the squared observable, which is needed for the calculation of the variance of the observable.

from squlearn.observables import SummedPaulis
from squlearn.observables.observable_derivatives import ObservableDerivatives
op = SummedPaulis(num_qubits=3)
print(ObservableDerivatives(op).get_operator_squared())
SparsePauliOp(['III', 'IIZ', 'IZI', 'ZII', 'IZZ', 'ZIZ', 'ZZI'],
              coeffs=[<qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dc8cf0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dc8d50>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dc8db0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dc8e10>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dc8e70>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dc8ed0>,
 <qiskit._accelerate.circuit.ParameterExpression object at 0x7f7f11dc8f30>])