Skip to content

Conversation

alexanderivrii
Copy link
Member

@alexanderivrii alexanderivrii commented Feb 24, 2025

Summary

This PR significantly improves the high-level-synthesis plugin for synthesizing AnnotatedOperation objects and in particular the synthesis of hierarchical circuits involving controlled subcircuits. Addresses #13566. Should be rebased on top of #13813. Does not need #13870, but serves as a basis to that PR explaining why we want to change the default handling of controlled gates to annotated gates.

In addition, the PR improves synthesis of multi-controlled CZ-gates and synthesis of controlled QFT-adders.

Details and comments

Multi-controlled X-gates

Here are experiments for the following 4 simple quantum circuits. All of these circuits are equivalent to a 6-controlled X-gate, however they are constructed in somewhat different ways: circuit1 is a 6-controlled X-gate; circuit2 is a 5-controlled CX-gate; circuit3 is a 2-controlled quantum circuit that contains a 3-controlled CX-gate; circuit4 is the same as circuit3, however the controls are added as annotations rather than being eagerly synthesized.

# circuit1: 6-controlled X
qc = QuantumCircuit(15)
qc.append(XGate().control(6), [0, 1, 2, 3, 4, 5, 6])

# circuit2: 5-controlled CX
qc = QuantumCircuit(15)
qc.append(CXGate().control(5), [0, 1, 2, 3, 4, 5, 6])

# circuit3: hierarchical circuit morally equivalent to 5-controlled CX
inner = QuantumCircuit(5)
inner.append(CXGate().control(3, annotated=False), [0, 1, 2, 3, 4])
controlled_inner_gate = inner.to_gate().control(2, annotated= False)
qc = QuantumCircuit(15)
qc.append(controlled_inner_gate, [0, 1, 2, 3, 4, 5, 6])

# circuit4: hierarchical circuit morally equivalent to 5-controlled CX
inner = QuantumCircuit(5)
inner.append(CXGate().control(3, annotated=True), [0, 1, 2, 3, 4])
controlled_inner_gate = inner.to_gate().control(2, annotated=True)
qc = QuantumCircuit(15)
qc.append(controlled_inner_gate, [0, 1, 2, 3, 4, 5, 6])

We are going to transpile these circuits with HighLevelSynthesis(basis_gates=["cx", "u"]).

MAIN:

CIRCUIT1: {'u': 57, 'cx': 30}
CIRCUIT2: {'u': 57, 'cx': 30}
CIRCUIT3: {'u': 1575, 'cx': 974}
CIRCUIT4: {'u': 849, 'cx': 574}

THIS PR:

CIRCUIT1: {'u': 57, 'cx': 30}
CIRCUIT2: ({'u': 57, 'cx': 30}
CIRCUIT3: {'u': 1543, 'cx': 990}
CIRCUIT4: {'u': 57, 'cx': 30}

As we can see, both circuit1 and circuit2 are efficiently synthesized before and after this PR (note that the synthesis algorithm uses available ancillary qubits). However, the hierarchical circuit3 is synthesized very poorly: due to eagerly synthesizing control logic, we first construct the circuit for a 3-controlled CX-gate, and then control every gate in this circuit using 2 additional controls. To improve the above, we need to do two things: create controlled gates as annotated gates (using annotated=True) and improving the HighLevelSynthesis pass to handle annotated operations, which is done in this PR by improving the AnnotatedSynthesisDefault plugin. With both of these changes, circuit4 is synthesized just as efficiently as circuit1 and circuit2.

Multi-controlled Z-gates

We repeat experiment with multi-controlled Z-gates instead of multi-controlled X-gates. Let circuits 5, 6, 7, 8 be the versions of circuits 1, 2, 3, 4 with Z/CZ gates instead of X/CX. One important difference is that we don't have native MCZ gates in Qiskit as compared to native MCX-gates, and in particular we don't have dedicated plugins for MCZ-gates. In theory, a multi-controlled Z-gate is equivalent to a multi-controlled X-gate with just two additional H-gates, and so we should get the same quality of results as above.

MAIN:

CIRCUIT5: {'u': 59, 'cx': 30}
CIRCUIT6: {'u': 159, 'cx': 78}
CIRCUIT7: {'u': 2549, 'cx': 1586}
CIRCUIT8: {'u': 5719, 'cx': 3626}

THIS PR:

CIRCUIT5: {'u': 59, 'cx': 30}
CIRCUIT6: {'u': 59, 'cx': 30}
CIRCUIT7: {'u': 1573, 'cx': 1002}
CIRCUIT8: {'u': 59, 'cx': 30}

However, in practice circuit6 is worse than circuit2: this is because a CZ-gate is translated to a CX-gate with 2 Hadamards, and adding 5-controls creates two 5-controlled Hadamard gates instead of their non-controlled versions. This PR improves this behavior by providing an explicit decomposition for multiply-controlled CZ-gates in _add_control.py, similarly to how other controlled gates are currently handled. As before, creating a hierarchical circuit using annotated=True and using the improved AnnotatedSynthesisDefault plugin allows to synthesize circuit8 using 30 CX-gates as compared to 1586.

Controlled QFT-adders

A long discussed optimization (actually available as a part of unused by default OptimizeAnnotated transpiler pass) is to simplify control-[U -- V -- U^{-1}] to U -- [control-V] -- U^{-1}. This immediately applies to controlled QFT-adders, where the logic is precisely of this form: control-[QFT -- V -- QFT^{-1}]. With AnnotatedSynthesisDefault adapting two simple optimizations from OptimizeAnnotated pass and after allowing in the construction of the QFT-based adder to create the inverse QFT^{-1} as annotated (hence a new argument to adder_qft_d00), a controlled QFT-adder can be synthesized much more efficiently than before:

#circuit 9: our old friend controlled QFT-based adder
gate = HalfAdderGate(3).control(2, annotated=True)
qc = QuantumCircuit(gate.num_qubits)
qc.append(gate, qc.qubits)

MAIN:

CIRCUIT9: {'u': 1607, 'cx': 1310}

THIS PR:

CIRCUIT9: {'u': 539, 'cx': 450}

@1ucian0 1ucian0 modified the milestones: 2.0.0, 2.1.0 Mar 4, 2025
@alexanderivrii
Copy link
Member Author

alexanderivrii commented Mar 31, 2025

Thanks @ShellyGarion for the review. I have addressed your review comments, please take another look! Update: I have also improved/updated this PR's summary and release notes.

Copy link
Member

@ShellyGarion ShellyGarion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still need to look more carefully into the code and tests.
How do we decide which gates to include in EFFICIENTLY_CONTROLLED_GATES and in the HLS plugin?

"ry",
"rz",
"cx",
"cz",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that some gates should be added here:

  • s and sdg are missing (since we have cs and csdg).
  • swap (since we have cswap)
  • u1 and u3 (since we have cu1 and cu3)
  • should we also add here ccx, c3x ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good question, also related to your question on how to decide which gates to include in this list.

Let's try to think through this together, taking the SGate as a perfect example. What happens when we call SGate().control(num_ctrl_qubits)? We can check that SGate implements its own control method, which returns CSGate when num_ctrl_qubits==1 and falls back on the general Gate.control mechanism otherwise. The latter calls this code in the _add_control module. Since SGate is currently not in the EFFICIENTLY_CONTROLLED_GATES, we are going to unroll it into this set using UnrollCustomDefinitions and BasisTranslator passes, which will produce (a circuit containing) a single PhaseGate. Now, PhaseGate is in EFFICIENTLY_CONTROLLED_GATES and the method apply_basic_controlled_gate will be used, producing an MCPhase gate. I would claim that this is the best we can do from the efficiency perspective (modulo to how then an MCPhase gets synthesized), so technically we do not need to add an SGate to the list above. On the other hand, adding it to the list would avoid the need to run the unroller pass, and since SGate is a very common gate it might make sense to add it to the list from the perspective of efficiency.

Summarizing, we probably want to consider the gates above + other standard gates and see if we should add them to the list. We should also decide which part should be handled by a gate's native control mechanism vs. apply_basic_controlled_gate, hopefully avoiding any kind of duplication. I suggest to do this as a follow up.

@@ -24,21 +24,47 @@
from ._utils import _ctrl_state_to_int


# The list of gates whose controlled versions have efficient synthesis algorithms.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean controlled or multi-controlled?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "controlled" I mean "controlled with an arbitrary number of qubits". And this is what we generally mean when we say "controlled" in Qiskit, as reflected in the names Gate.control, _add_control, etc.

# (including annotated operations) as the HighLevelSynthesis transpiler pass will
# recursively re-synthesize this circuit, However, we should always guarantee that some
# progress is made.
basis = set(EFFICIENTLY_CONTROLLED_GATES + ["annotated", "mcx", "qft"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these the only relevant gates? how about mcphase? or mcrz/mcry/mcrx?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also a great question. For MCPhaseGate, we don't have a plugin mechanism (correct?), so the definition circuit will be used. Can we do something better using ancillas? Is an MCPhaseGate used when constructing more complex circuits? If yes, then it could make sense to first unroll a more complex circuit to a circuit with MCPhaseGate, then apply the best known algorithm for MCPhaseGate. There is also a question on whether we need a native MCPhaseGate at all, as opposed to a controlled phase gate. Bottom-line: I agree that we should think about the above, but I would not like to add more gates here without concrete use cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about adders? maybe in the future? say someone wants to add a controlled adder (w/o specifying the method)

Copy link
Member

@ShellyGarion ShellyGarion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! there is a minor misprint, but this PR is ready to go.

# (including annotated operations) as the HighLevelSynthesis transpiler pass will
# recursively re-synthesize this circuit, However, we should always guarantee that some
# progress is made.
basis = set(EFFICIENTLY_CONTROLLED_GATES + ["annotated", "mcx", "qft"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about adders? maybe in the future? say someone wants to add a controlled adder (w/o specifying the method)


# all gates with the plugins we check, including an expected operation
plugins = [("cumulative_h18", "ch"), ("qft_r17", "mcphase")]
# For each pliugin, we check the presence of an expected operation after
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pliugin --> plugin

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for spotting this :) fixed in 29cd6d7

@alexanderivrii
Copy link
Member Author

what about adders? maybe in the future? say someone wants to add a controlled adder (w/o specifying the method)

Hmm, that's an interesting point: while we try to make sure that the default plugins use the best synthesis algorithm for each high-level-gate in the circuit, these "locally best" choices might not be the overall best, taking the rest of the circuit and/or the following transpilation into account. I don't see an immediate solution to this, and I am afraid that there is only so much we can do automatically...

@ShellyGarion ShellyGarion enabled auto-merge April 3, 2025 11:46
@ShellyGarion ShellyGarion added this pull request to the merge queue Apr 3, 2025
Merged via the queue into Qiskit:main with commit 7f3bd10 Apr 3, 2025
24 checks passed
@eliarbel eliarbel added the Changelog: New Feature Include in the "Added" section of the changelog label Jun 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve synthesis of controlled circuits with controlled gates within
6 participants