SampledExpectation layer seems to recompute the same circuits for different measurement ops
Example
c = cirq.Circuit()
qubits = cirq.GridQubit.rect(2, 1)
c.append(cirq.rz(0.)(qubits[0]))
c.append(cirq.H(qubits[1]))
sampled_expectation_layer = tfq.layers.SampledExpectation()
output = sampled_expectation_layer(
[c],
operators=[cirq.Z(qubits[0]), cirq.Z(qubits[1]), cirq.Z(qubits[0]) * cirq.Z(qubits[1])],
repetitions=1)
print(output) # we have [1, -1, 1] sometimes
assert output[0,0]*output[0,1] == output[0,2] # and this may fail
It is unnatural to me that, when repetitions=1 (1 sample), we have inconsistent measurement results between Z1, Z2 and Z1*Z2.
And from code here https://github.com/tensorflow/quantum/blob/db2eac4e598f614a8c0e157c56313a23ef1943fb/tensorflow_quantum/core/ops/batch_util.py#L554-L561, this may be due to that each circuit, batch, measurement ops combination is simulated separately on backends. If it is so, it is not only unnatural but also inefficient since one can measure all ops for one circuit simulation.
My understanding may be wrong somewhere, any ideas on this?
Thanks for raising this issue. I'm going to refer to the examples from the documentation here: https://www.tensorflow.org/quantum/api_docs/python/tfq/layers/SampledExpectation
bit = cirq.GridQubit(0, 0)
symbols = sympy.symbols('x y z')
ops = [cirq.Z(bit), cirq.X(bit)]
num_samples = [100, 200]
circuit = _gen_single_bit_rotation_problem(bit, symbols)
values = [[1,1,1], [2,2,2], [3,3,3]]
sampled_expectation_layer = tfq.layers.SampledExpectation()
output = sampled_expectation_layer(
circuit,
symbol_names=symbols,
symbol_values=values,
operators=ops,
repetitions=num_samples)
# output[i][j] = The sampled expectation of ops[j] with
# values_tensor[i] placed into the symbols of the circuit
# with the order specified by feed_in_params.
# so output[1][2] = The sampled expectation of a circuit with parameter
# values [2,2,2] w.r.t Pauli X, estimated using 200 samples per term.
output # Non-deterministic result. It can vary every time.
The output is defined as: output[i][j] = <psi(\theta_i) | OP_j | psi(\theta_i) >. In your case this means the circuit will be evaluated three different times, once with <psi | Z_0 | psi >, <psi | Z_1 | psi > , <psi | Z_0 Z_1 | psi >, where psi is made from RZ and H. Each of these outputs is estimated using only 1 sample per term.
These quantities are all simultaneously observable and can in theory all be estimated at the same time. However this is not true in all cases with more complex operators. Consider the case where each psi_i is totally different (in structure and not just parameter value) from one another and each op_j is totally different from one another. Then we have to estimate each one entirely independently of one another because they are not simultaneously observable. In order to accommodate that case and not provide ambiguous behavior (in terms of how we do sampling) between the simultaneous observable vs not simultaneous observable case, we opted to go with this behavior in order to be consistent everywhere.
The library snippet you linked actually isn't getting run in your program and is designed for use with py_functions when one needs the Cirq backend (and not the qsim backend). This is usually only the case when running through the engine or another exotic sampler interface in Cirq.
Does this clear things up ?
@MichaelBroughton , thanks for your detailed explanation, it helps a lot!
By considering non-simultaneous observables, (such as X1, Z1, X1*Z1) it is indeed better to keep the current behavior.
It would be better the documentation is more clear on this point, in case someone (like me) wrongly relies on repetitions=1 and found the result a little confusing.
Good idea! I can take that on, unless you would like to :) ?
Please go ahead :)
This issue has not had any activity in a month. Is it stale ?
@MichaelBroughton Did anything more happen on this topic in later years? Should this issue be kept open, or closed?