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 is_zero_initialized to transpile and extending HighLevelSynthesis with ancilla qubits #12729

Closed
wants to merge 26 commits into from

Conversation

alexanderivrii
Copy link
Contributor

@alexanderivrii alexanderivrii commented Jul 7, 2024

Updated Summary

This PR adds a new argument is_zero_initialized to transpile(...), as well as to PassManagerConfig and generate_pass_manager. If set to True (the default) the qubits are assumed to be initially in state <0|. For some additional context, see #10591, #5127 and other linked issues. After an offline discussion, we have concluded that having an explicit argument in transpile achieves the best of both worlds.

In addition, this PR passes this flag to HighLevelSynthesis transpiler pass. If set to True, the pass can initially label all qubits as "clean auxiliary qubits". The pass then keeps track of clean and dirty auxiliary qubits throughout the run, and passes this information to plugins via kwargs _num_clean_ancillas and _num_dirty_ancillas. Additionally, the pass has been extended to handle the case that the circuit synthesizing a high-level-object is defined over these additional qubits.

I have removed the MCX synthesis methods and MCX synthesis plugins from this PR. We will handle a more organized restructuring of the multi-controlled synthesis algorithms and plugins in a follow-up.

Original Summary (outdated)

For now this is a proof-of-concept PR based on internal Qiskit discussions. Everything is up to debate.

The idea is to extend the HighLevelSynthesis transpiler pass with the ability to use "auxiliary qubits" when synthesizing high-level operations using plugins. In this initial implementation, the allowed synthesis methods should support "dirty" auxiliary qubits, that is the synthesis methods are required to bring each auxiliary qubit into its original state. This allows to use any qubit that is not a part of the object's definition as an auxiliary qubit. It should be also possible to support "clean" auxiliary qubits (or a combination of clean and dirty auxiliary qubits).

This is illustrated for two MCX synthesis functions: the default synthesis function (based on the reduction to MCPhase gate) that does not require any auxiliary qubits, and the "recursive synthesis" function (extracted from the definition of the MCXRecursive gate) that requires a single auxiliary qubit for MCX gates with more than 4 control qubits. The key difference between the MCXRecursiveGate and the new functionality is that for MCXRecursiveGate the number and the auxiliary qubits used are baked into the gate's definition, while with the new flow the gate is an MCXGate only defined on its control and target qubits, and it's the actual synthesis that decides how many auxiliary qubits are available.

This PR does not deprecate or remove MCXRecursiveGate (and other custom MCX variants). This can be done in some follow-up PR if we agree that this is the right strategy. Also note that since the MCXRecursiveGate has a name "mcx_recursive" (or something like that) the two new "mcx" plugins do not apply, and the old synthesis behavior based on the gate's definition is used.

More comments appear as notes within the code.

Details and comments

Here is a concrete example:

qc = QuantumCircuit(10)
# This is a bona fide MCX gate, not its recursive version
qc.mcx(control_qubits=[0, 1, 2, 3, 4, 5, 6], target_qubit=[7])

# default synthesis plugin (from this PR) that uses the current default synthesis algorithm
qct1 = transpile(qc, basis_gates=['cx', 'u'])
print(qct1.count_ops())

# mcx synthesis plugin (from this PR) that uses the recursive synthesis algorithm with 1 extra ancilla
hls_config = HLSConfig(mcx=["recursive"])
qct2 = transpile(qc, basis_gates=['cx', 'u'], hls_config=hls_config)
print(qct2.count_ops())

The output is:

OrderedDict([('u', 248), ('cx', 220)])
OrderedDict([('u', 159), ('cx', 144)])

Feedback is welcome!

@coveralls
Copy link

coveralls commented Jul 8, 2024

Pull Request Test Coverage Report for Build 10027519940

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

  • 74 of 75 (98.67%) changed or added relevant lines in 5 files are covered.
  • 20 unchanged lines in 5 files lost coverage.
  • Overall coverage increased (+0.03%) to 89.947%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/synthesis/high_level_synthesis.py 70 71 98.59%
Files with Coverage Reduction New Missed Lines %
qiskit/circuit/random/utils.py 1 94.87%
qiskit/primitives/backend_estimator_v2.py 3 98.29%
crates/qasm2/src/lex.rs 3 92.88%
crates/qasm2/src/parse.rs 6 97.61%
crates/accelerate/src/synthesis/clifford/utils.rs 7 78.01%
Totals Coverage Status
Change from base Build 9976077291: 0.03%
Covered Lines: 66015
Relevant Lines: 73393

💛 - Coveralls

@alexanderivrii
Copy link
Contributor Author

Heads up: the number of 2-qubit gates for the synthesized circuit on the benchpress multi_control_circuit benchmark (that contains multiple MCX gates of different sizes):

  • up to 10 qubits: tket - 937, qiskit - 1269, qiskit* - 985
  • up to 15 qubit: tket - 3617, qiskit - 5849, qiskit* - 3825

Here qiskit* corresponds to running transpile with this PR and with the following config:

hls_config = HLSConfig(mcx=["recursive", "default"], plugin_selection="all", plugin_evaluation_fn=size2q)
transpile(qiskit_qc, basis_gates=["cx", "u"], hls_config=hls_config)

Now, let's add an extra free qubit to the circuit (i.e. the circuit is now defined over n+1 qubits with multiple MCX gates up to n qubits). The number of gates for tket and for qiskit do not change, but qiskit* uses the extra qubit.

  • up to 10 qubits: tket - 937, qiskit - 1269, qiskit* - 765
  • up to 15 qubit: tket - 3617, qiskit - 5849, qiskit* - 3197

@alexanderivrii alexanderivrii changed the title [WIP] extending HighLevelSynthesis with ancilla qubits Adding is_zero_initialized to transpile and extending HighLevelSynthesis with ancilla qubits Jul 18, 2024
@alexanderivrii alexanderivrii marked this pull request as ready for review July 18, 2024 18:30
@alexanderivrii alexanderivrii requested review from ShellyGarion and a team as code owners July 18, 2024 18:30
@qiskit-bot
Copy link
Collaborator

One or more of the following people are relevant to this code:

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

@alexanderivrii alexanderivrii added this to the 1.2.0 milestone Jul 18, 2024
@mtreinish mtreinish added Changelog: New Feature Include in the "Added" section of the changelog mod: transpiler Issues and PRs related to Transpiler labels Jul 18, 2024
@alexanderivrii alexanderivrii removed Changelog: New Feature Include in the "Added" section of the changelog mod: transpiler Issues and PRs related to Transpiler labels Jul 18, 2024
@alexanderivrii alexanderivrii added the mod: transpiler Issues and PRs related to Transpiler label Jul 18, 2024
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.

tl;dr: I would suggest we only add is_zero_initialized (and add RemoveResetInZeroState in this case) and only add the HLS logic once we have plugins that can use it.


Overall this looks like a solid start for handling ancillas. But I have one question upfront: if our main goal is to introduce the new argument is_zero_initialized for 1.2, wouldn't it make more sense to defer the HLS changes to when we actually add the new plugins that can use ancillas? That way we can also add tests and ensure the HLS logic works as expected, and we don't add "dead code" that is in the release but never used.

Another important point for is being consistent with the assumption that the initial state is $|0\rangle$ throughout the compiling process. That would mean running the RemoveResetInZeroState pass if is_zero_initialized is True -- otherwise it seems a bit unexpected that we state the qubits are already reset but don't remove initial resets.

I can open a PR with these changes to your branch, and if you agree, I'd be happy to move forward with the reduced changes for 1.2.

- add tests
- add handling of no-ops and reset
@ElePT
Copy link
Contributor

ElePT commented Jul 23, 2024

Given that this PR is in ongoing discussion, I think it will likely not make it for 1.2, and be for 1.3.

@alexanderivrii
Copy link
Contributor Author

Superseded by #12911.

@alexanderivrii
Copy link
Contributor Author

This can be closed as the better PR #12911 has been merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mod: transpiler Issues and PRs related to Transpiler
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants