Skip to content

Eliminate multiple control layers on CX/CZ.controlled([0]) #7241

@daxfohl

Description

@daxfohl

Describe your design idea/issue

All other cases of .controlled() in Cirq, as well as the ControlledGate constructor, do their best to flatten the layers of control where possible. The one exception is CX/CZ.controlled(...), where ... is a non-default set of control values.

    for cv in [1, 0]:
        for g in [cirq.X, cirq.CX, cirq.CCX, cirq.Z, cirq.CZ, cirq.CCZ]:
            print()
            for i in range(5):
                print(repr(g.controlled(i, [cv] * i)))

Output:

cirq.X
cirq.CNOT
cirq.TOFFOLI
cirq.ControlledGate(sub_gate=cirq.X, num_controls=3)
cirq.ControlledGate(sub_gate=cirq.X, num_controls=4)

cirq.CNOT
cirq.TOFFOLI
cirq.ControlledGate(sub_gate=cirq.X, num_controls=3)
cirq.ControlledGate(sub_gate=cirq.X, num_controls=4)
cirq.ControlledGate(sub_gate=cirq.X, num_controls=5)

cirq.TOFFOLI
cirq.ControlledGate(sub_gate=cirq.X, num_controls=3)
cirq.ControlledGate(sub_gate=cirq.X, num_controls=4)
cirq.ControlledGate(sub_gate=cirq.X, num_controls=5)
cirq.ControlledGate(sub_gate=cirq.X, num_controls=6)

cirq.Z
cirq.CZ
cirq.CCZ
cirq.ControlledGate(sub_gate=cirq.Z, num_controls=3)
cirq.ControlledGate(sub_gate=cirq.Z, num_controls=4)

cirq.CZ
cirq.CCZ
cirq.ControlledGate(sub_gate=cirq.Z, num_controls=3)
cirq.ControlledGate(sub_gate=cirq.Z, num_controls=4)
cirq.ControlledGate(sub_gate=cirq.Z, num_controls=5)

cirq.CCZ
cirq.ControlledGate(sub_gate=cirq.Z, num_controls=3)
cirq.ControlledGate(sub_gate=cirq.Z, num_controls=4)
cirq.ControlledGate(sub_gate=cirq.Z, num_controls=5)
cirq.ControlledGate(sub_gate=cirq.Z, num_controls=6)

cirq.X
cirq.ControlledGate(sub_gate=cirq.X, control_values=cirq.ProductOfSums(((0,),)),control_qid_shape=(2,))
cirq.ControlledGate(sub_gate=cirq.X, control_values=cirq.ProductOfSums(((0,), (0,))),control_qid_shape=(2, 2))
cirq.ControlledGate(sub_gate=cirq.X, control_values=cirq.ProductOfSums(((0,), (0,), (0,))),control_qid_shape=(2, 2, 2))
cirq.ControlledGate(sub_gate=cirq.X, control_values=cirq.ProductOfSums(((0,), (0,), (0,), (0,))),control_qid_shape=(2, 2, 2, 2))

cirq.CNOT
cirq.ControlledGate(sub_gate=cirq.CNOT, control_values=cirq.ProductOfSums(((0,),)),control_qid_shape=(2,))
cirq.ControlledGate(sub_gate=cirq.CNOT, control_values=cirq.ProductOfSums(((0,), (0,))),control_qid_shape=(2, 2))
cirq.ControlledGate(sub_gate=cirq.CNOT, control_values=cirq.ProductOfSums(((0,), (0,), (0,))),control_qid_shape=(2, 2, 2))
cirq.ControlledGate(sub_gate=cirq.CNOT, control_values=cirq.ProductOfSums(((0,), (0,), (0,), (0,))),control_qid_shape=(2, 2, 2, 2))

cirq.TOFFOLI
cirq.ControlledGate(sub_gate=cirq.X, control_values=cirq.ProductOfSums(((0,), (1,), (1,))),control_qid_shape=(2, 2, 2))
cirq.ControlledGate(sub_gate=cirq.X, control_values=cirq.ProductOfSums(((0,), (0,), (1,), (1,))),control_qid_shape=(2, 2, 2, 2))
cirq.ControlledGate(sub_gate=cirq.X, control_values=cirq.ProductOfSums(((0,), (0,), (0,), (1,), (1,))),control_qid_shape=(2, 2, 2, 2, 2))
cirq.ControlledGate(sub_gate=cirq.X, control_values=cirq.ProductOfSums(((0,), (0,), (0,), (0,), (1,), (1,))),control_qid_shape=(2, 2, 2, 2, 2, 2))

cirq.Z
cirq.ControlledGate(sub_gate=cirq.Z, control_values=cirq.ProductOfSums(((0,),)),control_qid_shape=(2,))
cirq.ControlledGate(sub_gate=cirq.Z, control_values=cirq.ProductOfSums(((0,), (0,))),control_qid_shape=(2, 2))
cirq.ControlledGate(sub_gate=cirq.Z, control_values=cirq.ProductOfSums(((0,), (0,), (0,))),control_qid_shape=(2, 2, 2))
cirq.ControlledGate(sub_gate=cirq.Z, control_values=cirq.ProductOfSums(((0,), (0,), (0,), (0,))),control_qid_shape=(2, 2, 2, 2))

cirq.CZ
cirq.ControlledGate(sub_gate=cirq.CZ, control_values=cirq.ProductOfSums(((0,),)),control_qid_shape=(2,))
cirq.ControlledGate(sub_gate=cirq.CZ, control_values=cirq.ProductOfSums(((0,), (0,))),control_qid_shape=(2, 2))
cirq.ControlledGate(sub_gate=cirq.CZ, control_values=cirq.ProductOfSums(((0,), (0,), (0,))),control_qid_shape=(2, 2, 2))
cirq.ControlledGate(sub_gate=cirq.CZ, control_values=cirq.ProductOfSums(((0,), (0,), (0,), (0,))),control_qid_shape=(2, 2, 2, 2))

cirq.CCZ
cirq.ControlledGate(sub_gate=cirq.Z, control_values=cirq.ProductOfSums(((0,), (1,), (1,))),control_qid_shape=(2, 2, 2))
cirq.ControlledGate(sub_gate=cirq.Z, control_values=cirq.ProductOfSums(((0,), (0,), (1,), (1,))),control_qid_shape=(2, 2, 2, 2))
cirq.ControlledGate(sub_gate=cirq.Z, control_values=cirq.ProductOfSums(((0,), (0,), (0,), (1,), (1,))),control_qid_shape=(2, 2, 2, 2, 2))
cirq.ControlledGate(sub_gate=cirq.Z, control_values=cirq.ProductOfSums(((0,), (0,), (0,), (0,), (1,), (1,))),control_qid_shape=(2, 2, 2, 2, 2, 2))

The CX and CZ in the nontrivial control value cases end up with ControlledGates that wrap CX/CZ, instead of having the ControlGate absorb the outer control layer like everywhere else. Beyond the inconsistency, multiple layers of control are futzy to work with, awkward to understand, and harder to decompose. (Hence the isinstance(subgate, CZ) in ControlledGate._decompose_).

Making this consistent seems worthwhile, and given the behavior of most cases, it seems like this was the original goal, but it just got lost in the special nontrivial control values cases.

I'm pretty sure this is just modifying the CX/CZPowGate.controlled methods and adding unit tests, so marking as good first issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    good first issueThis issue can be resolved by someone who is not familiar with the codebase. A good starting issue.good for learningFor beginners in QC, this will help picking up some knowledge. Bit harder than "good first issues"kind/design-issueA conversation around designtriage/acceptedA consensus emerged that this bug report, feature request, or other action should be worked ontriage/needs-more-evidence[Feature requests] Seems plausible, but maintainers are not convinced about the use cases yet

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions