Skip to content
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

Adding QFT gate to natively reason about Quantum Fourier Transforms #11463

Open
wants to merge 23 commits into
base: main
Choose a base branch
from

Conversation

alexanderivrii
Copy link
Contributor

@alexanderivrii alexanderivrii commented Dec 28, 2023

Summary

A new "high-level" gate QFTGate allows to natively add QFTs on a quantum circuit and, in particular, to defer their synthesis to the transpiler. The gates will be synthesized by the HighLevelSynthesis transpiler pass, using one of several available plugins.

A new function synth_qft_full(num_qubits, do_swaps, approximation_degree, insert_barriers, inverse, name) allows to synthesize a QFT circuit with the given number of qubits. For now the implementation is taken directly from the _build method of the existing QFT (blueprint) circuit class. The name of the method reflects that the synthesized circuit requires full connectivity. Note that another QFT synthesis method has been recently added in #11236, with the synthesized circuit following only linear (aka nearest-neighbor) connectivity.

The two methods above are wrapped into HighLevelSynthesis plugins QFTSynthesisFull and QFTSynthesisLine respectively. The default synthesis is set to QFTSynthesisFull, thus fully coinciding with the circuits produced using the existing QFT circuit class.

In a follow-up PR I am going to replace the construction of QFT circuits by QFT gates whenever possible, such as for example when constructing QFT-based adders and multipliers.

Details and Comments

The constructor in the original QFT circuit class includes arguments num_qubits, approximation_degree, do_swaps, inverse, insert_barriers and name. Here approximation_degree allows to ignore small controlled-phase rotations, this leading to an approximate but more efficient circuit, and do_swaps allows to ignore SWAP gates at the end of the circuit. An important decision is which of the arguments above should be a part of the definition of a QFTGate, and which should be parameters to its synthesis method. In this PR only num_qubits is part of the QFTGate, while the rest are parameters to the synthesis method. For instance, I strongly believe that whether to include barriers in the synthesized circuit and which name to assign to the synthesized circuit have nothing to do with the definition of the QFTGate. I am also inclined to think that approximation_degree and do_swaps should be synthesis parameters, i.e. instead of talking about an approximate swap-reduced QFT-gate, we are talking about a QFT-gate that can be synthesized ignoring small control-phase rotations and final swaps, especially that deciding which gates in the synthesized circuit can be ignored heavily depends on the synthesis method itself. What, however, is missing is incorporating the knowledge that in some cases in the bigger circuit we have inverse QFT-gate -- some gate U -- QFT-gate is equivalent to inverse swap-reduced QFT-gate -- some gate U -- swap-reduced QFT-gate, that is it is safe to drop swaps for both QFT gates. This is important as it leads to a smaller overall circuit, however for this to be correct both QFT gates should be synthesized in the same way. And we currently don't have high-level-synthesis API to ensure this. Alternatively we could add do_swaps to the gate definition, however I don't really like this solution, and it also does not solve the problem: in theory HighLevelSynthesis can choose different QFT-synthesis algorithms for both gates, again leading to incorrect results when ignoring swap gates. Thoughts and suggestions are welcome.

@qiskit-bot
Copy link
Collaborator

One or more of the the following people are requested to review this:

  • @Cryoris
  • @Qiskit/terra-core
  • @ajavadia
  • @mtreinish
  • @nkanazawa1989

@coveralls
Copy link

coveralls commented Dec 28, 2023

Pull Request Test Coverage Report for Build 9855919792

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 62 of 64 (96.88%) changed or added relevant lines in 8 files are covered.
  • 407 unchanged lines in 19 files lost coverage.
  • Overall coverage increased (+0.04%) to 89.888%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/synthesis/high_level_synthesis.py 21 23 91.3%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/expr.rs 1 94.02%
qiskit/synthesis/evolution/lie_trotter.py 1 96.77%
qiskit/circuit/library/standard_gates/u.py 1 95.92%
qiskit/synthesis/evolution/product_formula.py 1 98.64%
qiskit/synthesis/evolution/qdrift.py 2 94.29%
crates/accelerate/src/synthesis/linear/mod.rs 2 93.55%
qiskit/circuit/library/standard_gates/u3.py 2 96.94%
qiskit/circuit/library/standard_gates/u1.py 3 94.9%
qiskit/synthesis/evolution/suzuki_trotter.py 4 90.24%
qiskit/transpiler/passes/optimization/consolidate_blocks.py 5 95.12%
Totals Coverage Status
Change from base Build 9785044203: 0.04%
Covered Lines: 65733
Relevant Lines: 73128

💛 - Coveralls

@jakelishman jakelishman added the mod: circuit Related to the core of the `QuantumCircuit` class or the circuit library label Jan 3, 2024
@jakelishman jakelishman added the Changelog: New Feature Include in the "Added" section of the changelog label Jan 3, 2024
@alexanderivrii
Copy link
Contributor Author

alexanderivrii commented Jan 5, 2024

I had a discussion with @ShellyGarion and a discussion with @Cryoris on whether do_swaps should be a part of the definition of the QFT gate:

In the original QFT synthesis algorithm for all-to-all connectivity, the synthesized QFT circuit has a layer of swap gates at the end. These swap gates correspond to a very specific reversal permutation. Other circuits that use QFT as a building block, in many cases use this special knowledge and avoid explicitly implementing the reversal implementation with swaps when the reversal can be achieved in a different way. For instance, for circuits that have both QFT and its inverse, the two reversal permutations cancel out and hence do not need to be implemented at all. (Note that the name do_swaps is misleading: we are not talking about dropping any permutation at the end of the circuit, but about a very specific reversal permutation.) Note that other QFT synthesis algorithms (such as the one implemented in #11236) do not require the layer of SWAP gates at all.

Since the goal is to make sure that the QFT-based application circuits have count/depth as small as possible, we should investigate if in all cases the transpiler is able to automatically optimize away the reversal permutations if they were to appear in the circuits. If this is the case, then we can have the canonical QFT gate (and we do not need to change the implementation in this PR). If this is not the case, then we may want to add to a QFT gate the argument reversed_bits (or some better name): when True the gate corresponds to the "reversed QFT" (which also has precise and clear semantics). The application circuits would then be able to pick the form of the gate best suited for the application, and the synthesis plugin would need to handle the "reverse_bits" argument correctly.

@jakelishman
Copy link
Member

imo, a Gate object should represent exactly something that could be a hardware instruction, which means they should have very limited semantics, or it'll not be useful as an element. If we want to keep the reasoning at a higher abstract level in the Qiskit sense, then it should be an implementor of Operation but not Instruction - this signifies that the semantics are local to Qiskit and its compiler, and then we can extend the semantics as needed.

In other words, if it's going to be a Gate, then QFTGate should represent exactly the operation of the QFT matrix on virtual qubits, without any swaps. There shouldn't be any possibility of a do_swaps argument on the Gate object, because a Gate is a representation of a hardware operation and not of a synthesis, just like a HGate represents exactly the operation of the Hadamard matrix on qubits. It's up to a synthesis routine to decide how to insert swaps when including a QFT operation as part of a larger circuit. The concept of backend connectivity, instruction set, or surrounding operations should not have any effect on an Instruction, so a do_swaps argument shouldn't exist.


With the advent of Operation, and the move towards optimisation passes based in mathematical abstractions before we hit the gate-level requirements, I'd consider making an abstract QFT representative object that implements Operation and not Instruction, much like Clifford does. It could always be converted to a Gate later, but I'd think we might want some indication of some hardware that has a QFT primitive instruction to motivate that - as I understand, all the goals from the synthesis side are to use this as a high level object, which needn't (and imo shouldn't, but I don't feel too strongly) be a Gate.

@alexanderivrii
Copy link
Contributor Author

alexanderivrii commented Jan 10, 2024

Jake, thanks for the feedback.

In other words, if it's going to be a Gate, then QFTGate should represent exactly the operation of the QFT matrix on virtual qubits, without any swaps.

I completely agree with this, and this is how it's implemented right now. Though I still have not checked if we get the same or worse results when we substitute QFT circuits (with do_swaps=False) by QFT gates in various application circuits that use QFTs as building blocks.

Note: to avoid possible confusion, for the QFT circuit to faithfully implement the QFT matrix using the default implementation in QFT._build, the circuit must contain the reversal permutation (implemented as a layer of swaps). So what the sneaky application circuit developers did is to start using "bit-reversed QFT circuits" for building larger circuits (as these bit-reversed circuits have a more efficient implementation without that swap layer, in code this corresponds to do_swaps=False). If the Qiskit transpiler is not able to automatically remove these layers of swap gates, then we may want to provide both QFT gate and bit-reversed QFT gate , and in practice we can have one to be defined as QftGate(bit_reversed=False) and the other as QftGate(bit_reversed=True). Both have well-defined matrix semantics.


In theory I agree with you that things like QftGate should be Operations and not Instructions, but in practice we have been inheriting from Gate/Instruction whenever possible, for instance both PermutationGate and LinearFunction are gates. This automatically takes care of a large number of annoying problems that we don't fully support at the level of Operations: including drawing support, QPY, etc. Specifically about QftGates, I would like to drop-in-replace QFT circuits by QFT gates in larger circuits provided by our circuit library, and I would like this change to be backwards-compatible. Maybe I am overthinking it, but I am afraid that something on the user side would break if QftGates became non-Instructions. (Well, I also want to replace inverse-QFT-gates by annotated operations, which kind of contradicts to what I just wrote).

@mtreinish mtreinish modified the milestones: 1.0.0, 1.1.0 Jan 23, 2024
@alexanderivrii
Copy link
Contributor Author

alexanderivrii commented Feb 6, 2024

An update on how QFT-subcircuits are used within Qiskit's circuit library (thanks to @ShellyGarion and @Cryoris for all the help).

We have 4 circuits that use QFT as a building block: DraperQFTAdder, RGQFTMultiplier, PhaseEstimation, and QuadraticForm.

In each of these cases, the main circuit uses a sneaky optimization of using not the proper QFT, but the QFT with the argument do_swaps=False, which corresponds to QFT with the reversal permutation of its qubits. As a side-note, the main circuit has been also modified, so that the final operator is indeed correct. And to be pedantic, the illustrations like the one appearing for DraperQFTAdder in draper_qft_adder.py are a bit misleading since in the illustration it's not really the QFT/IQFT pair, but the QFT-with-reversal/IQFT-with-reversal pair:

a_0: ─────────■──────■────────■───────────────────────────────────────
              │      │        │                                       
a_1: ─────────┼──────┼────────┼────────■──────■───────────────────────
              │      │        │        │      │                       
a_2: ─────────┼──────┼────────┼────────┼──────┼────────■──────────────
     ┌──────┐ │P(π)  │        │        │      │        │     ┌───────┐
b_0: ┤0     ├─■──────┼────────┼────────┼──────┼────────┼─────┤0      ├
     │      │        │P(π/2)  │        │P(π)  │        │     │       │
b_1: ┤1 QFT ├────────■────────┼────────■──────┼────────┼─────┤1 IQFT ├
     │      │                 │P(π/4)         │P(π/2)  │P(π) │       │
b_2: ┤2     ├─────────────────■───────────────■────────■─────┤2      ├
     └──────┘                                                └───────┘

The optimization (removing the final reversal permutation and modifying the rest of the circuit accordingly) makes sense for he default QFT synthesis (targeting all-to-all connectivity), as it introduces the layer of swaps (aka the reversal permutation) at the end of the circuit, so the "QFT-with-reversal" is the simple circuit without this layer of SWAPs. Note however that when transpiling for general architectures, routing may insert many additional SWAPs.

But the optimization no longer makes sense for other QFT synthesis algorithms, such as the one targeting the linear-nearest neighbor connectivity, since it does not introduce a reversal permutation at the end of the circuit. Hence, for the DraperQFTAdder above it would make more sense to use the "direct" QFT/IQFT gates and not the "reversed" ones. (It would probably make even more sense to think on how to transpile the full circuit including the many CP gates in the middle, but that's not the main point I am trying to make).

In addition, it is easy to check that if in the DrapperQFTAdder above was implemented with the direct versions of QFT and IQFT (that is with the reversal permutations + other required changes), then the ElidePermutations pass would be able to remove them (essentially producing the circuit above). The same is true for the other 3 application circuits mentioned (in all of the cases the reversal permutations come in pairs; e.g. for PhaseEstimation one reversal comes from do_swaps=False and the second - from reverse_bits).

The bottom line of the discussion is that we don't really need to have the "reversed" version of QFT for the transpiler to produce optimal circuits, as long as we can properly use the ElidePermutations pass. Without using ElidePermutations there is no simple way to get rid of the extra swaps.

Hence it probably makes sense to have the new QftGate not include additional arguments (like do_swaps), corresponding to the mathematically correct QFT operator.

Though, some thought is required on how to properly combine the HighLevelSynthesis and ElidePermutations passes and recursion to enable the swap cancellation above, for instance for controlled QFT-adders, where the QFTs appear as subcircuits within the definitions of more complex gates.

@Cryoris
Copy link
Contributor

Cryoris commented Feb 28, 2024

I agree with the final goal of having this be an Operation and move towards a clear separation of synthesis-based objects (Clifford, Operator, ...) vs. fundamental gates (like the standard gates). As @alexanderivrii mentioned above, however, this does not only affect the QFT but also e.g. the LinearFunction or the PauliEvolutionGate. To enable an efficient QFT synthesis I would therefore think it is fine to move along as QFTGate before properly thinking synthesis objects through and changing it for all.

Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

Thanks Sasha, this is looking good. For the purposes of ElideSwaps and things like that, maybe a route along the lines of letting synthesis routines synthesise up to a permutation, then outputting a permutation gate as well? That way ElideSwaps can just pick that up and remove it, or the behaviour can generally be controlled.

qiskit/circuit/library/generalized_gates/qft.py Outdated Show resolved Hide resolved
Comment on lines 44 to 54
num_qubits = self.num_qubits
mat = np.empty((2**num_qubits, 2**num_qubits), dtype=dtype)
for i in range(2**num_qubits):
i_index = int(bin(i)[2:].zfill(num_qubits), 2)
for j in range(i, 2**num_qubits):
entry = np.exp(2 * np.pi * 1j * i * j / 2**num_qubits) / 2 ** (num_qubits / 2)
j_index = int(bin(j)[2:].zfill(num_qubits), 2)
mat[i_index, j_index] = entry
if i != j:
mat[j_index, i_index] = entry
return mat
Copy link
Member

Choose a reason for hiding this comment

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

Something like

def qft(n):
    pows = np.arange(2**n)
    return np.exp(2j * np.pi / n * np.outer(pows, pows)) * (0.5 ** (n/2))

is probably a fair bit faster than this. I think there might be some nicer np.power.outer tricks that might be faster, but it probably doesn't matter too much at the scale of matrices that we can generate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is cool! Done in 6106c4a. On top of this, I have utterly no idea why I was converting an integer to its binary representation and back.

qiskit/transpiler/passes/synthesis/high_level_synthesis.py Outdated Show resolved Hide resolved
Comment on lines 56 to 72
def _basic_decomposition(self):
"""Provide a specific decomposition of the QFT gate into a quantum circuit.

Returns:
QuantumCircuit: A circuit implementing the evolution.
"""
from qiskit.synthesis.qft import synth_qft_full

decomposition = synth_qft_full(num_qubits=self.num_qubits)
return decomposition

def _define(self):
"""Populate self.definition with a specific decomposition of the gate.
This is used for constructing Operators from QftGates, creating qasm
representations and more.
"""
self.definition = self._basic_decomposition()
Copy link
Member

Choose a reason for hiding this comment

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

Very minor nitpicking, but is there a need to have a separate _basic_decomposition function? Can we inline it into _define (which largely implies that it's a basic definition)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, done in a76fbe7.

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.

Nice contribution @alexanderivrii. I have a few comments and suggestions.

  • Do we plan to deprecate QFT in favor of QFTGate in Qiskit 2.0 ?

  • You mentioned the use-case of adders and circuits containing QFT and QFT-inverse, do you have some test for it? or will it appear in a future PR?

qiskit/synthesis/qft/qft_decompose_full.py Outdated Show resolved Hide resolved
qiskit/transpiler/passes/synthesis/high_level_synthesis.py Outdated Show resolved Hide resolved
qiskit/transpiler/passes/synthesis/high_level_synthesis.py Outdated Show resolved Hide resolved
It is impossible ti implement the QFT approximately by ignoring
controlled-phase rotations with the angle is beneath a threshold. This is discussed
in more detail in https://arxiv.org/abs/quant-ph/9601018 or
https://arxiv.org/abs/quant-ph/0403071.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add them as references?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I am slightly inclined to keep it a bit less formal: IMHO, a good reference section would first cite a paper defining a QFT operation, then cite papers that introduce different synthesis methods for QFT, and only then papers that discuss approximation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Personally, I would also prefer the references written out, because of (1) consistency with other code and (2) being able to read authors & title w/o clicking the link (but maybe that's just me being lazy 😛 )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Due to popular demand, the references are now written out.

Copy link
Member

Choose a reason for hiding this comment

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

thanks, I think that you should still remove line 912

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, forgot to delete this line, done now.

qiskit/transpiler/passes/synthesis/high_level_synthesis.py Outdated Show resolved Hide resolved
@mtreinish mtreinish modified the milestones: 1.1.0, 1.2.0 May 2, 2024
@alexanderivrii
Copy link
Contributor Author

After a long delay, I have hopefully addressed all of the review comments; this is ready for review once again.

Now we have the ElidePermutations pass in Qiskit, my concerns that we might be adding more swap gates are fully addressed -- the pass would remove these swap gates.

Deprecating QFT circuit will probably be done as a part of a larger effort of restructuring the circuits library.

from qiskit.circuit.quantumcircuit import Gate


class QFTGate(Gate):
Copy link
Member

Choose a reason for hiding this comment

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

This class appears in the Section "Generalized Gates", unlike QFT which appears in the section "Basis Change Circuits".
Is it deliberate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! It's a gate but not a "basic gate", so it's a "generalized gate". Are you proposing to restructure the library of generalized gates, grouping the generalized gates by "their purpose"?

Copy link
Member

Choose a reason for hiding this comment

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

I'm actually not sure why QFT appears separately than the other generalized gates

Copy link
Contributor

Choose a reason for hiding this comment

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

Generalized gates were thought to be "extensions" of the standard gates, such as multi-Pauli-gates or uniformly controlled gates. But by now there's a lot of other objects there that we don't yet have a better category for, like Isometry or Permutation. It also doesn't really matter as we allow (and encourage) import from qiskit.circuit.library, but I'd keep QFTGate it in basis_change for the time being.

We probably won't put all gates into generalized_gates since that wouldn't be any sorting really, hopefully we'll be able to clean it up a bit in the restructure.

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 maybe all the (multi/uniformly) controlled gates deserve a special category (but that's not relevant to this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. I have moved the QFTGate to basis_change, the same file that contains the QFT circuit.

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.

This PR looks very good to me. I only have a few questions about the documentation.

Copy link
Contributor

@Cryoris Cryoris left a comment

Choose a reason for hiding this comment

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

This looks great! Just some minor comments below.

from qiskit.circuit.quantumcircuit import Gate


class QFTGate(Gate):
Copy link
Contributor

Choose a reason for hiding this comment

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

Generalized gates were thought to be "extensions" of the standard gates, such as multi-Pauli-gates or uniformly controlled gates. But by now there's a lot of other objects there that we don't yet have a better category for, like Isometry or Permutation. It also doesn't really matter as we allow (and encourage) import from qiskit.circuit.library, but I'd keep QFTGate it in basis_change for the time being.

We probably won't put all gates into generalized_gates since that wouldn't be any sorting really, hopefully we'll be able to clean it up a bit in the restructure.

Comment on lines 35 to 36
"""Construct a new QFT gate.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think in most places we don't put this initial sentence anymore if it doesn't provide any info, since it looks nicer in the docs if we just start with the Args 🙂 but that's certainly true in the circuit lib 🙂

Suggested change
"""Construct a new QFT gate.
"""

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

approximation_degree: int = 0,
insert_barriers: bool = False,
inverse: bool = False,
name: Optional[str] = None,
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you use the new style of

Suggested change
name: Optional[str] = None,
name: str | None = None,

instead? 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

The plugin supports the following additional options:
* reverse_qubits (bool): Whether to synthesize the "QFT" operation (default) or the
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure about the name of this argument, to me it sounds like reverse_qubits=True would give me a QFT with qubits in reversed order -- but it's the opposite. Could we flip the meaning, such that reverse_qubits=True returns the qubits in reversed order, meaning that do_swaps=not reverse_qubits?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, indeed, I had this wrong, fixed.

It is impossible ti implement the QFT approximately by ignoring
controlled-phase rotations with the angle is beneath a threshold. This is discussed
in more detail in https://arxiv.org/abs/quant-ph/9601018 or
https://arxiv.org/abs/quant-ph/0403071.
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally, I would also prefer the references written out, because of (1) consistency with other code and (2) being able to read authors & title w/o clicking the link (but maybe that's just me being lazy 😛 )

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 mod: circuit Related to the core of the `QuantumCircuit` class or the circuit library synthesis
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants