12

I'm wondering how in Qiskit one can calculate the expectation value of an operator given as a WeightedPauli (or, at least, of a single Pauli operator...) in a certain state (given as a QuantumCircuit object ⁠— meaning that the actual state is the result of the action of this circuit on the computational basis state). I would like the inputs of such a procedure to be floats, not Parameters (it is an essential requirement — I'm using an external library to form the circuit for each set of parameters, and then converting it gate-by-gate to Qiskit format).

This would be useful if, say, we wanted to manually implement VQE, and for that needed a function calculating the expectation value of the Hamiltonian on a quantum computer. More importantly, we would need this for implementing generalizations of VQE, such as subspace search.

I guess, PauliBasisChange may be involved...

mavzolej
  • 1,921
  • 7
  • 17
  • https://qiskit.org/documentation/stubs/qiskit.circuit.QuantumCircuit.snapshot_expectation_value.html ? – Simon Crane May 22 '20 at 16:17
  • @Simon, to my understanding, this is an unphysical method. I'm looking for smth that would work on an anctual device, based on sampling. – mavzolej May 22 '20 at 16:22
  • The SnapshotExpectationValue is developed to be fast. It computes the expectation value via matrix multiplication, not using shots. – Cryoris May 27 '20 at 16:48

1 Answers1

17

Note: This post is a bit older and Qiskit Aqua is now deprecated. Replace all occurences of qiskit.aqua.operators with qiskit.opflow to be compatible with Qiskit Terra 0.17.0 and above.

The operators in Qiskit Aqua allow the evaluation of expectation values both exactly (via matrix multiplication) or on shot-based sampling (closer to real quantum computers). The basic principle is the same both times, it only differs in how the expectation value is evaluated in the end.

First, you need to define the operator $O$ you're interested in and the state $|\psi\rangle$ with respect to which you want to compute the expecation value. So we're looking for $$ E = \langle\psi|O|\psi\rangle. $$ In the code below we have $O$ = op and $|\psi\rangle$ = psi. See also there for your use-case of a WeightedPauliOperator.

# you can define your operator as circuit
circuit = QuantumCircuit(2)
circuit.z(0)
circuit.z(1)
op = CircuitOp(circuit)  # and convert to an operator

or if you have a WeightedPauliOperator, do

op = weighted_pauli_op.to_opflow()

but here we'll use the H2-molecule Hamiltonian

from qiskit.aqua.operators import X, Y, Z, I op = (-1.0523732 * I^I) + (0.39793742 * I^Z) + (-0.3979374 * Z^I)
+ (-0.0112801 * Z^Z) + (0.18093119 * X^X)

define the state you w.r.t. which you want the expectation value

psi = QuantumCircuit(2) psi.x(0) psi.x(1)

convert to a state

psi = CircuitStateFn(psi)

There are now different ways to evaluate the expectation value. The straightforward, "mathematical", approach would be to take the adjoint of $|\psi\rangle$ (which is $\langle\psi|$) and multiply with $O$ and then $|\psi\rangle$ to get the expectation. You can actually do exactly this in Qiskit:

# easy expectation value, use for small systems only!
print('Math:', psi.adjoint().compose(op).compose(psi).eval().real)

to get

Exact: -1.0636533199999998

This is only suitable for small systems though.

To use the simulators, and the also get the shot-based result, you can use the PauliExpectation (shots), AerPauliExpectation (exact) or MatrixExpectation (exact). Here's how to do it:

from qiskit import Aer
from qiskit.aqua import QuantumInstance
from qiskit.aqua.operators import PauliExpectation, CircuitSampler, StateFn

define your backend or quantum instance (where you can add settings)

backend = Aer.get_backend('qasm_simulator') q_instance = QuantumInstance(backend, shots=1024)

define the state to sample

measurable_expression = StateFn(op, is_measurement=True).compose(psi)

convert to expectation value

expectation = PauliExpectation().convert(measurable_expression)

get state sampler (you can also pass the backend directly)

sampler = CircuitSampler(q_instance).convert(expectation)

evaluate

print('Sampled:', sampler.eval().real)

which yields

Sampled: -1.0530518430859401

This result varies if you execute multiple times.

For comparison, here the other methods to evaluate the expecation value

expectation = AerPauliExpectation().convert(measurable_expression)
sampler = CircuitSampler(backend).convert(expectation)  
print('Snapshot:', sampler.eval().real)

expectation = MatrixExpectation().convert(measurable_expression) sampler = CircuitSampler(backend).convert(expectation)
print('Matrix:', sampler.eval().real)

which produces

Snapshot: -1.06365328
Matrix: -1.06365328

I hope that clarifies how to compute the expectation value!

Cryoris
  • 2,893
  • 6
  • 14
  • Thanks! A couple of questions regarding the sampler solution: 1) Will it work if I replace the qasm backend with a real device? 2) Where do I specify the number of samples? – mavzolej May 28 '20 at 03:23
  • Yes it should, it can use the QuantumInstance like the other Aqua algorithms use too. 2) By passing a QuantumInstance, I updated the answer above.
  • Could you accept the answer, if your question was answered? :) It helps so others can see that this is resolved and a solution was found.

    – Cryoris May 28 '20 at 07:50
  • What kinds of optimizations does Qikist do while using this method? For example, does it split the WeightedPauliSum into commuting sets, in order to reduce the number of measurements? – mavzolej May 28 '20 at 12:54
  • Following my previous comment - are there any circuit optimizations performed using the transpiler in this code, or should I first do that manually? – mavzolej May 28 '20 at 13:33
  • Also, the line q_instance = QuantumInstance(backend, shots = shots) generates the following warning, if the real hardware is used: WARNING - The skip Qobj validation does not work for IBMQ provider. Disable it.. Do you know why, and how to avoid it? – mavzolej May 28 '20 at 13:39
  • Do QuantumInstance(backend, shots=1024, skip_qobj_validation=False) but its just a warning and does not influence your code. – Cryoris May 28 '20 at 19:40
  • Got it! Please see also my two comments before. – mavzolej May 28 '20 at 20:34
  • The Paulis are grouped in the PauliExpectation, it has an argument group_paulis which can be set to False (True by default). The transpiler does some light circuit optimization by default, you can set optimization_level=3 in the quantum instance for the heavy optimization. See the expectation docs and quantum instance docs. – Cryoris May 28 '20 at 20:39
  • Awesome, thanks!! – mavzolej May 28 '20 at 21:28
  • @Cryosis, please see my bug report here. It looks like this method does not work with circuits obtained via evolve() or initialize. – mavzolej Jun 11 '20 at 01:10
  • one more question ^_^. If I were measuring Pauli operators in a brute-force way, by manually doing sampling term-by-term, it would be trivial to implement this way of measurement error mitigation. Is there a way I could implement this functionality within your approach? – mavzolej Jun 15 '20 at 02:41
  • Here's my guess: q_instance = QuantumInstance(backend, shots=1024, measurement_error_mitigation_cls = CompleteMeasFitter(), measurement_error_mitigation_shots = 1024). Will the error filter matrix be calculated at the moment of initializing the q_instance? (I want to use the q_instance for evaluating multiple Pauli operators, and not to calculate the filter matrix again each time.) – mavzolej Jun 15 '20 at 03:30
  • Almost right, in principle it should be qi = QuantumInstance(backend=backend, shots=8000, measurement_error_mitigation_cls=CompleteMeasFitter, measurement_error_mitigation_shots=8000) followed by qi.execute(circuit). – Cryoris Jun 16 '20 at 12:54
  • the circuit I'm using was obtained by the procedure suggested here, so I have the following code: circuit_fixed = QuantumCircuit( circuit.num_qubits ); circuit_fixed.append( circuit.to_instruction(), list( range( circuit.num_qubits ) ) ). However, as I'm calling q_instance.execute(circuit_fixed), I get the following error: ..\measurement_error_mitigation.py", line 129, in build_measurement_error_mitigation_qobj raise AquaError("The measured qubit list can not be []."). Do you have an idea what this can be? – mavzolej Jun 16 '20 at 21:41
  • @Cryosis, how can I calculate the exact expectation value using the ibmq_qasm_simulator, instead of using my own CPU? I asked this question here. – mavzolej Feb 15 '21 at 19:11
  • @Cryoris, I am using the PauliExpectation method you outlined above on the IBMQ backend. I am finding that it works very well. However, it seems that script execution time increases drastically for the number of qubits. For nqubits=4 it takes ~1.5s, for nqubits=6 it takes ~50s, and for nqubits=8 it hangs for a long time (at least 30 min). I have found that it hangs on the PauliExpectation().convert(measurable_expression) line. Is there a way I can speed this up? – thespaceman Jun 21 '21 at 22:53
  • 1
    Depends on your measurable_expression. If possible, the operator should consist only of Paulis and sums of Paulis. As soon as you have matrices, it will take a very long time. – Cryoris Jun 22 '21 at 15:56
  • @Cryoris, Apart from the Pauli matrices, my circuit is composed of only $R_y, C_x, H$ gates. Given that, is there anything I can do to reduce the processing time? – thespaceman Jun 22 '21 at 18:09
  • 1
    Could you maybe open an issue on GitHub? It’ll be easier to discuss there, maybe you could add a minimal example :) – Cryoris Jun 22 '21 at 20:04
  • @Cryoris, I have opened an issue: https://github.com/thegiantspaceman/VQLS/issues/1 If you run the "VQLS_github.py" script it should work fine for 'nqubit=4' but for 'nqubit=8' you will see it getting hung as I described. – thespaceman Jun 22 '21 at 21:18
  • @Cryoris, Thanks for your help so far, have you had a chance to look at the github issue yet? – thespaceman Jun 24 '21 at 20:41