Skip to content

to_qasm() on classically controlled multi-line gates (e.g. CCZ) only guards the first line #8329

Description

@WindOctober

Describe the issue

When a ClassicallyControlledOperation wraps a sub-operation whose QASM output spans multiple lines—for example, CCZ, which exports as h; ccx; h—to_qasm() prepends a single if (...) prefix to the entire block.

In both OpenQASM 2.0 and 3.0, a brace-less if guards only the single following statement. As a result, every subsequent line executes unconditionally.

This means the exported QASM is semantically different from the original Cirq circuit, with no warning.

Root cause

ClassicallyControlledOperation._qasm_ concatenates the if prefix directly with the raw QASM string returned by the wrapped operation:

# cirq-core/cirq/ops/classically_controlled_operation.py

subop_qasm = protocols.qasm(self._sub_operation, args=args)
...
return f'if ({condition_qasm}) {subop_qasm}'

For CCZ, CCZPowGate._qasm_ returns a multi-line string when exponent == 1:

h ...;
ccx ...;
h ...;

This implementation is in three_qubit_gates.py.

Among the standard-library gates, CCZ is a common multi-line case, while CCX and CSWAP export as single statements.

Since QASM export is an interoperability boundary, downstream tools consuming this output can silently receive a circuit with different semantics.

Explain how to reproduce the bug or problem

import cirq

q = cirq.LineQubit.range(4)

circuit = cirq.Circuit()
circuit.append(cirq.X(q[0]))
circuit.append(cirq.measure(q[3], key='c'))
circuit.append(
    cirq.CCZ(q[0], q[1], q[2]).with_classical_controls('c')
)
circuit.append(cirq.measure(q, key='m'))

print(circuit.to_qasm())

The exported QASM contains:

if (m_c==1) h q[2];
ccx q[0],q[1],q[2];   // executes unconditionally — wrong
h q[2];                // executes unconditionally — wrong

The semantic discrepancy is observable.

Suggested fix

In ClassicallyControlledOperation._qasm_, guard the entire sub-operation rather than only its first statement.

  • For OpenQASM 3.0, emit a braced block:
if (m_c==1) {
    h q[2];
    ccx q[0],q[1],q[2];
    h q[2];
}
  • For OpenQASM 2.0, which has no equivalent block form for this case, prepend the same if (...) condition to each statement in subop_qasm.

Tell us the version of Cirq where this happens

Reproduced on Cirq v1.7.0 (latest release).

The affected code path is also unchanged on main at commit 8122be0, confirmed by source inspection.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/qasmgood for learningFor beginners in QC, this will help picking up some knowledge. Bit harder than "good first issues"kind/bug-reportSomething doesn't seem to work.no QC knowledge neededWant to contribute to Cirq, but don't know quantum computing? This issue is for you.triage/acceptedA consensus emerged that this bug report, feature request, or other action should be worked on

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions