In my previous question I was struggling with the definition of expectation value of an observable for a circuit. Here is what I have derived after some support (I simplify the definition to 2-qubit system (+3rd ancilla)):
My Definition. Given a 2-qubit circuit $C$ and a 2-qubit observable $Ob$, the expectation value of $Ob$ for $C$ is defined as $$EV(C, Ob):=\sqrt{\sum_{ij}|\alpha_{ij0}|^2} - \sqrt{\sum_{ij}|\alpha_{ij1}|^2},$$ where $\alpha_{ijk}$ are such that $$(Ob_X\circ(C\otimes I))|000⟩=\sum_{ijk}\alpha_{ijk}|ijk⟩,$$ where $Ob_X$ denotes X-Axis control, i.e. $Ob_X=(I\otimes H)\circ Ob_C\circ(I\otimes H)$, where $Ob_C$ denotes $Ob$ controlled by the ancilla qubit.
My question is whether this definition is correct. If so, why does cirq
constrain simulate_expectation_values
method only for observables being Pauli Strings (tensor product of Pauli Gates)?
After @Craig Gidney answer I thought it would be easy to write (just using cirq
and numpy
) a code that computes expectation value with regard to arbitrary observable and I derived the following snippet:
import cirq
from copy import deepcopy
from cirq import X, CZ, H, Circuit, Simulator, GridQubit, InsertStrategy
import numpy as np
def example_circuit(q0, q1):
sqrt_x = X ** 0.5
yield sqrt_x(q0), sqrt_x(q1)
yield CZ(q0, q1)
yield sqrt_x(q0), sqrt_x(q1)
def extend_circ(C, Ob):
circ = deepcopy(C)
q2 = GridQubit(2, 0)
ObC = Ob.controlled_by(q2)
circ.append([H(q2), ObC, H(q2)], strategy=InsertStrategy.NEW_THEN_INLINE)
return circ
def main():
q0 = GridQubit(0, 0)
q1 = GridQubit(1, 0)
C = Circuit(example_circuit(q0, q1))
Ob = cirq.X(q0) * cirq.X(q1)
# Ob = cirq.X(q0)*cirq.X(q1) + 0.1 * cirq.Z(q0) * cirq.Z(q1)
ext_circ = extend_circ(C, Ob)
print(ext_circ)
result = Simulator().simulate(ext_circ)
print(result)
p0 = np.sqrt(np.sum(np.abs(result.final_state_vector[::2])**2))
p1 = np.sqrt(np.sum(np.abs(result.final_state_vector[1::2])**2))
expectation_value = p0 - p1
print('Expectation Value: {}'.format(expectation_value))
if __name__ == '__main__':
main()
Which gives the following output obtained from prints:
(0, 0): ───X^0.5───@───X^0.5───────PauliString(+X)───────
│ │
(1, 0): ───X^0.5───@───X^0.5───────X─────────────────────
│
(2, 0): ───────────────────────H───@─────────────────H───
measurements: (no measurements)
output vector: 0.5|000⟩ + 0.5j|010⟩ + 0.5j|100⟩ + 0.5|110⟩
Expectation Value: 0.9999999403953552
However, if I uncomment # Ob = cirq.X(q0)*cirq.X(q1) + 0.1 * cirq.Z(q0) * cirq.Z(q1)
I obtain the error AttributeError: 'PauliSum' object has no attribute 'controlled_by'
which indicates that in order to apply my method an observable must be "controlable".
My naive question. Maybe it would be easier to make in cirq
all observables controlable by definition (i.e. extend controlled_by
method to all observables)?