Compute-uncompute implementation to obtain the overlap between two quantum states.

compute_uncompute_overlap[source]

compute_uncompute_overlap(state0:StateFn, state1:Union[StateFn, ListOp, NoneType]=None, param_dict:Optional[Dict[ParameterExpression, List[float]]]=None, expectation:Optional[ExpectationBase]=None, backend:Union[Backend, QuantumInstance, NoneType]=None)

Compute-uncompute method

The compute-uncompute method provides the absolute value of the overlap between two quantum states $|\langle\psi|\phi\rangle|^2$. It relies on the assumption that states can be expressed as the unitary evolution of the zero state, e.g. $|\psi\rangle = U_{\psi}|0\rangle$. This way, we can express the overlap between two states as $\langle\psi|\phi\rangle=\langle 0|U_{\psi}^\dagger U _{\phi}|0\rangle$ and obtain its modulus square by preparing the state $U_{\psi}^\dagger U _{\phi}|0\rangle$ and estimating the probability of measuring zero.

Intuitively, the method prepares a state $U_{\phi}|0\rangle$ and, then, un-prepares the other by applying $U_{\psi}^\dagger$. If both states are the same, the product $U_{\psi}^\dagger U_{\phi}$ is the identity and we measure $|0\rangle$ with probability 1. The more similar the two states are, the more the second operator "undoes" the first one and so the higher the probability of measuring $|0\rangle$.

In terms of execution in a quantum computer, this method does not require any additional qubits, although it effectively doubles the circuit depth needed to prepare the states as the resulting circuit is the composition of both preparations.

The overal behaviour is analogous to that of the other overlap computation functions.

See some function call examples below and refer to the basic usage for a joint explanation of the overlap computation functions.

qc0 = QuantumCircuit(2)
qc0.x(0)
qc0.draw('mpl')
theta0 = Parameter('θ')
qc1 = QuantumCircuit(2)
qc1.x(0)
qc1.rx(theta0, 1)
qc1.draw('mpl')
state0 = CircuitStateFn(qc0)
purity = compute_uncompute_overlap(state0)
purity
1.0
theta_values = [0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi]
param_dict = {theta0: theta_values}
state0 = CircuitStateFn(qc0)
state1 = CircuitStateFn(qc1).bind_parameters(param_dict)
overlaps = compute_uncompute_overlap(state0, state1)
overlaps
array([1. , 0.5, 0. , 0.5, 1. ])
param_state = CircuitStateFn(qc1)
overlaps = compute_uncompute_overlap(param_state, param_dict=param_dict)
overlaps
array([[1. , 0.5, 0. , 0.5, 1. ],
       [0.5, 1. , 0.5, 0. , 0.5],
       [0. , 0.5, 1. , 0.5, 0. ],
       [0.5, 0. , 0.5, 1. , 0.5],
       [1. , 0.5, 0. , 0.5, 1. ]])
overlaps.diagonal()
array([1., 1., 1., 1., 1.])

Compare with a noisy simulator

backend = QasmSimulator.from_backend(FakeVigo())
o = compute_uncompute_overlap(param_state, param_dict=param_dict, backend=backend)
np.round(o, 3)
array([[0.905, 0.488, 0.031, 0.487, 0.918],
       [0.488, 0.911, 0.48 , 0.039, 0.454],
       [0.031, 0.48 , 0.919, 0.483, 0.036],
       [0.487, 0.039, 0.483, 0.91 , 0.454],
       [0.918, 0.454, 0.036, 0.454, 0.923]])