-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] restructuring MCX synthesis code #7505
Conversation
Pull Request Test Coverage Report for Build 1722743718
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for tackling this! I left some preliminary comments below 🙂
_name="mcx", | ||
synthesis=None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the _name
argument required for? 🙂
Could we put the synthesis
argument first? This seems to be the argument a user should use over the _name
one. Also could you add type hints?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Cryoris Many thanks for your comments!
The _name
thing kind of existed before: it's the name given to the returned quantum circuit as per qc = QuantumCircuit(q, name=self.name)
. Does it really matter what name it has? If not, sure, let's remove it.
I need to significantly improve all comments, add type hints, make the code pass black/lint, and in particular resolve the cyclic import dependencies. The synthesis algorithms probably need to be aware of gates, yet somehow the gates also need to be aware of the synthesis algorithms (e.g., the MCXGate.__init__
sets the default synthesis algorithm to MCXSynthesisGrayCode
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About type hints. If I understand correctly, you are suggesting to replace synthesis=None,
by synthesis : Optional[MCXSynthesis] = None
. However, this leads to a chain of cyclic dependencies that I cannot easily resolve: the file x.py
defining the MCX gate now requires the file mcx_synthesis.py
, which requires x.py
. This can be solved by moving the abstract MCX synthesis interface to a separate file. So, does it make sense to have two files? Does it makes sense to have a whole directory for MCX synthesis algorithms?
I am also confused about the _name
argument. Contrary to what I thought earlier, it's the name assigned to the gate, and currently it can be of "mcx", "mcx_gray", "mcx_recursive" or "mcx_vchain". Does it make sense to keep this difference, or would a single "mcx" be fine for all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, you are suggesting to replace synthesis=None, by synthesis : Optional[MCXSynthesis] = None
Yes, exactly. 🙂
Does it makes sense to have a whole directory for MCX synthesis algorithms?
Yes, I would say so, similar to qiskit/synthesis/evolution
.
About the name, we have to be careful since the circuit simulators might use the name to determine the operation. I think we can leave the names as they are. Ideally, we would deprecate all gates except MCXGate
since the others are available by choosing the appropriate synthesis, but we can tackle this in a follow-up.
|
||
# pylint: disable=cyclic-import | ||
from qiskit.circuit.quantumcircuit import QuantumCircuit | ||
num_qubits = num_ctrl_qubits + self.get_num_ancilla_qubits(num_ctrl_qubits) + 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use actual AncillaRegister
for the ancilla qubits? That'll give us the num_ancillas
property for free and provides more information to the compiler (which we cannot use yet but hopefully can in the future 🙂).
The same comment holds for the other synthesis algorithms, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't fully understand this comment. Currently, self.get_num_ancilla_qubits
returns the number of ancilla qubits required for implementing a given synthesis algorithm with a given number of control qubits. What do you mean by "actual AncillaRegisters"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean that we have a qubit type for ancillas, AncillaQubit
which we should use here 🙂 For example
>>> from qiskit.circuit import QuantumRegister, AncillaRegister, QuantumCircuit
>>> qr = QuantumRegister(3)
>>> ar = AncillaRegister(2)
>>> circuit = QuantumCircuit(qr, ar)
>>> circuit.draw()
q1_0:
q1_1:
q1_2:
a1_0:
a1_1:
>>> circuit.num_qubits
5
>>> circuit.num_ancillas
2
This way we expose the information that our circuit contains ancilla qubits. And in the future the compiler should be able to use this information and e.g. re-use ancilla qubits instead of allocating new ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I understand your point much better now, though I am still lost in the details. Currently, a (MCX) gate does not know about the circuit it belongs to, and no MCX synthesis algorithm takes the overall circuit into account. The QuantumCircuit.mcx
function expects a sufficient number of ancilla qubits to be provided, and complains if the required number of ancilla qubits (computed based on the MCX mode string and the number of control qubits) is larger than that. Looking at the grover algorithm as a code example that creates MCX gates: it gets an MCX mode as a parameter, then calls a (static) function to find out how many ancilla qubits would be required (for this MCX mode and this number of control qubits), then would create the required ancilla qubits, and then finally call mcx
. So I am not quite sure what to do here. Ideally, we also probably do not need to specify the MCX synthesis algorithm from the start, and should be able to choose the best-suited algorithm only when we need to synthesize the gate (with the best synthesis algorithm possibly depending on the number of ancilla qubits actually available).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, it's a staticmethod. I got confused because in this context it was called as self.get_num_ancilla_qubits
!
Then my comment simplifies a bit, we need to keep that staticmethod but I think it would still be good to use an AncillaRegister
for the ancilla qubits within the definition. Concretely I mean:
qr = QuantumRegister(num_ctrl_qubits + 1, name="q")
ar = AncillaRegister(self.get_num_ancilla_qubits(num_ctrl_qubits), name="a")
qc = QuantumCircuit(qr, ar, name=self.name)
Co-authored-by: Julien Gacon <gaconju@gmail.com>
One additional comment about naming: the names of the synthesis classes, like from qiskit.synthesis import MCXVChain Maybe we can even go a step further and drop the from qiskit.synthesis.mcx import VChain |
@Cryoris: I am happy to change names of the synthesis classes. However, calling a synthesis algorithm |
Yeah I agree "recursive" is not the best name, in particular since there are many schemes you could classify as recursive (see e.g. sections 7.2 and 7.3 in https://arxiv.org/pdf/quant-ph/9503016.pdf). Looking at the structure, this particular recursion always halves the number of controls, so how about |
e3cbb9e
to
528037b
Compare
qiskit/synthesis/mcx_synthesis.py
Outdated
|
||
@staticmethod | ||
@abstractmethod | ||
def get_num_ancilla_qubits(num_ctrl_qubits): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, could we rename this to get_num_ancillas
so it matches better the method QuantumCircuit.num_ancillas
?
q = QuantumRegister(num_qubits, name="q") | ||
qc = QuantumCircuit(q, name=self.name) | ||
qc._append(HGate(), [q[-1]], []) | ||
qc._append(MCU1Gate(numpy.pi, num_ctrl_qubits=num_ctrl_qubits), q[:], []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use the MCPhaseGate
instead of MCU1Gate
(U1Gate(-numpy.pi / 4), [q_ancillas[i]], []), | ||
(CXGate(), [q_controls[-1], q_ancillas[i]], []), | ||
(U1Gate(numpy.pi / 4), [q_ancillas[i]], []), | ||
(CXGate(), [q_target, q_ancillas[i]], []), | ||
(U1Gate(-numpy.pi / 4), [q_ancillas[i]], []), | ||
(CXGate(), [q_controls[-1], q_ancillas[i]], []), | ||
(U1Gate(numpy.pi / 4), [q_ancillas[i]], []), | ||
(CXGate(), [q_target, q_ancillas[i]], []), | ||
(U2Gate(0, numpy.pi), [q_target], []), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The U1 gates should be PhaseGate
s and the U2(0, pi)
a HGate
"""Implements various synthesis algorithms.""" | ||
|
||
@staticmethod | ||
def noancilla_3qubits(circuit_name): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to have these as separate classes implementing the MCXSynthesis
interface? This way we can keep encapsulation (since adding a new special decomposition would not require this existing class) and we could run isinstance
checks on them to see if they are MCX synthesis algorithms.
qc = QuantumCircuit(q, name=circuit_name) | ||
rules = [ | ||
(HGate(), [q[4]], []), | ||
(CU1Gate(numpy.pi / 2), [q[3], q[4]], []), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you exchange this for the CPhase gate?
if angle is not None: | ||
return C3SXGate(label, ctrl_state, angle=angle) | ||
|
||
from qiskit.synthesis.mcx_synthesis import MCXSynthesisGrayCode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this directly call the 3-qubit synthesis for the C3X? Otherwise it seems a bit backwards: the C3X calls the general Gray code which, as a special case, happens to have the 3-qubit synthesis we want here 🙂 (Same for the C4X)
@Cryoris, many thanks for your feedback! Actually, I will ask: would you like to work on this together? In a sense, we are doing this already. :) One tricky complication (that I do not quite know how to resolve) is: given an MCX gate with some kind of a synthesis algorithm Another problem (the test failure above) happens because I wanted to move the method |
Generally I would say users should use the For the controlled versions of MCXGrayCode, Recursive and VChain I think we should just use the same synthesis for the controlled versions. For the ones with a fixed number of controls that's more tricky... We could (1) extend the interface with a
I think the inverse should just use the same synthesis for simplicity. |
Another synthesis method for MCX gates is mentioned in this issue: #5872 If we also allow ancillas, then I think that the best method appears here: https://arxiv.org/abs/1508.03273 |
Thanks @ShellyGarion. |
We definitely need to rethink this, but this specific PR is outdated and can be closed. |
Summary
This is an experiment to restructure the code for MCX gates. In particular:
Currently the code is there to allow discussing APIs, etc.
Details and comments
There is a new file
qiskit.synthesis.mcx_synthesis.py
which has the actual synthesis algorithmsThere is only one
MCXGate
class, but with different synthesis algorithms (MCXSynthesisGrayCode
,MCXSynthesisRecursive
,MCXSynthesisVChain
)The classes
MCXRecursive
,MCXVChain
,MCXGrayCode
remain for backward compatibility, and under the hood create anMCXGate
with the matching synthesis algorithm.The code for
QuantumCircuit.mcx
is also simplified, and in particular avoids building all different gate types when constructing thedict
available_implementations