Cirq icon indicating copy to clipboard operation
Cirq copied to clipboard

concat_ragged allows measurements to overlap

Open daxfohl opened this issue 2 years ago • 6 comments

Description of the issue

Circuit.concat_ragged allows measurements and classical controls to commute past each other or overlap, resulting in unexpected behavior or invalid circuits.

How to reproduce the issue

q1, q2 = cirq.LineQubit.range(2)
c1 = cirq.Circuit(cirq.measure(q1, key='a'))
c2 = cirq.Circuit(cirq.measure(q2, key='a'))
c = c1.concat_ragged(c2)
print(c)

Result is two independent measurements to a at the same time.

0: ───M('a')───

1: ───M('a')───

Note: classical controls on a measurement key can commute past each other and/or overlap in a single moment; just measurements cannot commute past other measurements or classical controls on the same measurement key.

Cirq version v1.0

daxfohl avatar Jul 11 '22 04:07 daxfohl

From Cirq sync - to what extent is classical controls broken by this? cc @MichaelBroughton @tanujkhattar @95-martin-orion

verult avatar Aug 10 '22 18:08 verult

Pretty broken - see also: #5775

95-martin-orion avatar Aug 10 '22 18:08 95-martin-orion

What is the expected behavior for putting two measurements with the same key on two different qubits? Do we support repeated measurements on different qubits? My understanding was that a measurement with the same key can be repeated only when the qubits on which it acts doesn't change. So, for example, the following also breaks:

In [1]: import cirq

In [2]: q = cirq.LineQubit.range(2)

In [3]: circuit = cirq.Circuit(cirq.measure(q[0], key="a"), cirq.measure(q[1], key="a"))
   ...: 

In [4]: circuit
Out[4]: 
0: ───M('a')────────────

1: ────────────M('a')───

In [5]: cirq.Simulator().sample(circuit, repetitions=1000)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-609ed0eb90ae> in <module>
----> 1 cirq.Simulator().sample(circuit, repetitions=1000)

~/quantum/Cirq/cirq-core/cirq/work/sampler.py in sample(self, program, repetitions, params)
    174                 param_values_once = [resolver.value_of(key) for key in keys]
    175                 param_table = pd.DataFrame(data=[param_values_once] * repetitions, columns=keys)
--> 176                 results.append(pd.concat([param_table, result.data], axis=1))
    177 
    178         return pd.concat(results)

~/quantum/Cirq/cirq-core/cirq/study/result.py in data(self)
    370     def data(self) -> pd.DataFrame:
    371         if self._data is None:
--> 372             self._data = self.dataframe_from_measurements(self.measurements)
    373         return self._data
    374 

~/quantum/Cirq/cirq-core/cirq/study/result.py in measurements(self)
    341                 reps, instances, qubits = data.shape
    342                 if instances != 1:
--> 343                     raise ValueError('Cannot extract 2D measurements for repeated keys')
    344                 self._measurements[key] = data.reshape((reps, qubits))
    345         return self._measurements

ValueError: Cannot extract 2D measurements for repeated keys

tanujkhattar avatar Aug 10 '22 19:08 tanujkhattar

@maffoo for comments on same-moment measurements sharing a key.

In my opinion: measurements sharing a key but on different qubits should not commute past each other or be allowed in the same moment. The example @tanujkhattar gave above looks like a bug to me - sampler.py is accessing the 2D data (no repetitions allowed) instead of the 3D records (repetitions allowed).

95-martin-orion avatar Aug 10 '22 19:08 95-martin-orion

A agree that measurements with the same key on different qubits should not be commuted past each other, because that changes the order of results in the measurement record. I think allowing the same key on different qubits within a moment is fine though, because operations in a moment are ordered, and the hardware interface layer does support this. We do need to be careful if we combine measurements with the same key into a moment that we preserve the relative order of the ops.

maffoo avatar Aug 10 '22 20:08 maffoo

The sampler error is a known limitation of the current implementation because we never defined how repeated keys should be mapped into the 2D data frame produced by the sample method, so we intentionally left this code as it was where it would try to access .measurements, which fails if any measurement keys are repeated.

maffoo avatar Aug 10 '22 20:08 maffoo

I think allowing the same key on different qubits within a moment is fine though, because operations in a moment are ordered, and the hardware interface layer does support this.

I don't think ordering of operations within a moment is well defined. Currently, we just preserve insertion ordering in Moment._operations and Moment._sorted_operations is used for equality comparison and diagrams.

tanujkhattar avatar Aug 17 '22 17:08 tanujkhattar

Insertion ordering is what I mean.

maffoo avatar Aug 17 '22 17:08 maffoo

So then the original issue raised is not a bug (because measurements with same key in the same moment are well defined) and we should instead fix the sampler error that I raised (to support measurements with same key on more than one qubit)?

tanujkhattar avatar Aug 17 '22 17:08 tanujkhattar

In my view, measurements overlapping is not a problem as long as we maintain the output order. So, for example, if we have a circuit:

cirq.Circuit(
    cirq.Moment(cirq.measure(a, key='m')),
    cirq.Moment(cirq.measure(b, key='m')),
)

then transforming this into the following would be fine:

# ok; measurement order in the same moment is the same as before
cirq.Circuit(
    cirq.Moment(cirq.measure(a, key='m'), cirq.measure(b, key='m')),
)

but we should not transform it into either of the following, since these change the order of outputs:

# bad; measurement order in the same moment has changed
cirq.Circuit(
    cirq.Moment(cirq.measure(b, key='m'), cirq.measure(a, key='m')),
)

# bad; measurement order in different moments has changed
cirq.Circuit(
    cirq.Moment(cirq.measure(b, key='m')),
    cirq.Moment(cirq.measure(a, key='m')),
)

maffoo avatar Aug 17 '22 17:08 maffoo

I think this discussion is outside the scope of this issue. This issue is to fix a specific bug. It wouldn't make sense to fix it in a way that makes concat_ragged behave differently from append, and I don't think anyone is suggesting that we do.

Changing the behavior of how measurements are appended and how circuits are constructed is a breaking change that has many implications. If that is a consideration then it should be its own issue. But discussion of that issue should not block this bug fix.

daxfohl avatar Sep 17 '22 18:09 daxfohl

Closing due to lack of activity.

daxfohl avatar Oct 14 '22 02:10 daxfohl