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

Ensure UnitaryGate's are preserved by transpile if in basis gates #7104

Merged
merged 7 commits into from
Oct 6, 2021

Conversation

mtreinish
Copy link
Member

Summary

This commit tweaks the preset passmanager definitions to not include the
UnitarySynthesis pass as part of basis translation if the 'unitary' gate
is in the basis gates. Previously in #6124 and #6349 the preset
passmanagers were updated to leverage the user specified unitary
synthesis method instead of implicitly using the default as part of the
unroll 3q and unroll custom definition passes. This however had the
unintended side effect of always synthesizing unitary gates even if it
was in the basis (which is the case on Aer). This commit fixes this by
only running unitary synthesis as part of the basis translation step if
it's not in the basis.

A potential follow-on here is to make the unroll 3q transpiler pass
basis aware (since right now it will implicitly run unitary synthesis
internally) and add a similar logic around the use of the
UnitarySynthesis pass for unrolling 3q or larger gates. The unroll 3q
transpiler stage suffers from this same issue. However, this wasn't
done here because the way 3q unrolling is used is to reduce the gates to
be less than 3 qubits so the layout and routing phase can deal work with
the gates in the circuit would likely cause issues if a unitary gate
larger than 2 qubits was in the circuit being transpiled.

Details and comments

@mtreinish mtreinish requested a review from a team as a code owner October 5, 2021 19:56
@mtreinish mtreinish added the Changelog: None Do not include in changelog label Oct 5, 2021
This commit tweaks the preset passmanager definitions to not include the
UnitarySynthesis pass as part of basis translation if the 'unitary' gate
is in the basis gates. Previously in Qiskit#6124 and Qiskit#6349 the preset
passmanagers were updated to leverage the user specified unitary
synthesis method instead of implicitly using the default as part of the
unroll 3q and unroll custom definition passes. This however had the
unintended side effect of always synthesizing unitary gates even if it
was in the basis (which is the case on Aer). This commit fixes this by
only running unitary synthesis as part of the basis translation step if
it's not in the basis.

A potential follow-on here is to make the unroll 3q transpiler pass
basis aware (since right now it will implicitly run unitary synthesis
internally) and add a similar logic around the use of the
UnitarySynthesis pass for unrolling 3q or larger gates. The unroll 3q
transpiler stage suffers from this same issue. However, this wasn't
done here because the way 3q unrolling is used is to reduce the gates to
be less than 3 qubits so the layout and routing phase can deal work with
the gates in the circuit would likely cause issues if a unitary gate
larger than 2 qubits was in the circuit being transpiled.
@mtreinish mtreinish force-pushed the respect-unitary-in-basis branch from 8208367 to 49fcc41 Compare October 5, 2021 20:13
The level 3 preset passmanager by default runs 2q block collection which
is then synthesized into a unitary. As long as we're running that in the
pass manager it's not really possible right now to preserve input
unitaries through the transpilation. A potential follow on PR could
potential skip the synthesis pass if 'unitary' is in the basis so the
optimization pass would just collect 2q blocks into a unitary and pass
that to the backend directly. However, that's not in scope for this PR
(and it's not clear if we want that or not).
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.

I couldn't put a comment on it because it's not in the diffs, but the if translation_method == "synthesis" blocks will also always decompose unitary gates (for example, here's the level1 one): https://github.com/Qiskit/qiskit-terra/blob/f59a1fd4bca6c92683d9487c94db10e6c730ebfd/qiskit/transpiler/preset_passmanagers/level1.py#L193-L215

The first pass won't do it because it's limited to 3+ qubits, but the last pass will. This is probably desired behaviour (else why pass translation_method="synthesis" - it's not the default?), but just bringing up the inconsistency.

backend_props=backend_properties,
method=unitary_synthesis_method,
),
if basis_gates is not None and "unitary" not in basis_gates:
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 this changes the default behaviour if basis_gates is None - this is the relevant part of UnitarySynthesis.run from immediately before #6124: https://github.com/Qiskit/qiskit-terra/blob/c202e460a7369fde41209c2f41ad1484a3810a31/qiskit/transpiler/passes/synthesis/unitary_synthesis.py#L158-L160

In there, if basis_gates is None then the pass will run and synthesis unitary gates. I would think the old form is possibly what people would expect, but it's a bit of a funny one. If we want to maintain the exact behaviour, then it needs to be if (not basis_gates) or "unitary" not in basis_gates (which also runs it if basis_gates=[]), or perhaps more sensibly, if basis_gates is None or "unitary" not in basis_gates.

Similar comments on every pass level.

Copy link
Member Author

Choose a reason for hiding this comment

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

So, I've been testing this with 0.18.3 to compare the results with this PR:

qc = QuantumCircuit(2)
qc.unitary(random_unitary(4, seed=42), [0, 1])
qc.measure_all()

With result = transpile(qc, basis_gates=None):

0.18.3:

        ┌──────────┐ ░ ┌─┐   
   q_0: ┤0         ├─░─┤M├───
        │  Unitary │ ░ └╥┘┌─┐
   q_1: ┤1         ├─░──╫─┤M├
        └──────────┘ ░  ║ └╥┘
meas: 2/════════════════╩══╩═
                        0  1 

This PR:

        ┌──────────┐ ░ ┌─┐   
   q_0: ┤0         ├─░─┤M├───
        │  Unitary │ ░ └╥┘┌─┐
   q_1: ┤1         ├─░──╫─┤M├
        └──────────┘ ░  ║ └╥┘
meas: 2/════════════════╩══╩═
                        0  1 

With result = transpile(qc, basis_gates=[]):

0.18.3:

qiskit.transpiler.exceptions.TranspilerError: "Unable to map source basis {('barrier', 2), ('u3', 1), ('measure', 1), ('cx', 2)} to target basis {'delay', 'reset', 'snapshot', 'barrier', 'measure'} over library <qiskit.circuit.equivalence.EquivalenceLibrary object at 0x7fbe564d2460>."

This PR:

qiskit.transpiler.exceptions.TranspilerError: "Unable to map source basis {('u3', 1), ('barrier', 2), ('measure', 1), ('cx', 2)} to target basis {'barrier', 'reset', 'measure', 'snapshot', 'delay'} over library <qiskit.circuit.equivalence.EquivalenceLibrary object at 0x7f2dcff347f0>."

So I think the translator method is ok as is. Unless I'm missing something or I need a different example circuit.

Copy link
Member

Choose a reason for hiding this comment

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

For the record - the lines I highlighted above aren't the only relevant lines in 0.18.3. For one, the UnitarySynthesis plugin wasn't even run at optimization_level=1 in 0.18.3, because that didn't come til #6349, and for two, even if you force it, there's an implicit skip because _choose_kak_gate(None) would return None, and cause no synthesis to be done.

@mtreinish
Copy link
Member Author

For the synthesis method with this PR it has different behavior compared to 0.18.3:

qc = QuantumCircuit(2)
qc.unitary(random_unitary(4, seed=42), [0, 1])
qc.measure_all()

With result = transpile(qc, basis_gates=['cx', 'u', 'unitary'], translation_method='synthesis'):

0.18.3:

        ┌──────────┐ ░ ┌─┐   
   q_0: ┤0         ├─░─┤M├───
        │  Unitary │ ░ └╥┘┌─┐
   q_1: ┤1         ├─░──╫─┤M├
        └──────────┘ ░  ║ └╥┘
meas: 2/════════════════╩══╩═
                        0  1 

This PR:

global phase: 0.26938
        ┌───────────────────────────┐         ┌─────────────────────┐         »
   q_0: ┤ U(2.3398,-0.43844,1.2137) ├───■─────┤ U(0.52485,-π/2,π/2) ├──────■──»
        ├───────────────────────────┴┐┌─┴─┐┌──┴─────────────────────┴───┐┌─┴─┐»
   q_1: ┤ U(0.58612,-1.4512,0.09315) ├┤ X ├┤ U(0.61426,-1.5731,-3.1397) ├┤ X ├»
        └────────────────────────────┘└───┘└────────────────────────────┘└───┘»
meas: 2/══════════════════════════════════════════════════════════════════════»
                                                                              »
«          ┌──────────────────┐       ┌────────────────────────────┐ ░ ┌─┐   
«   q_0: ──┤ U(0.4452,-π,π/2) ├───■───┤ U(0.97669,-2.2224,-2.6846) ├─░─┤M├───
«        ┌─┴──────────────────┴┐┌─┴─┐┌┴────────────────────────────┤ ░ └╥┘┌─┐
«   q_1: ┤ U(1.5695,1.5695,-π) ├┤ X ├┤ U(0.97271,-2.3231,-0.59794) ├─░──╫─┤M├
«        └─────────────────────┘└───┘└─────────────────────────────┘ ░  ║ └╥┘
«meas: 2/═══════════════════════════════════════════════════════════════╩══╩═
«                                                                       0  1 

So there is something more to fix in the synthesis path. At least it also works for the basis_gates=None case:

With result = transpile(qc, basis_gates=None, translation_method='synthesis'):

0.18.3:

        ┌──────────┐ ░ ┌─┐   
   q_0: ┤0         ├─░─┤M├───
        │  Unitary │ ░ └╥┘┌─┐
   q_1: ┤1         ├─░──╫─┤M├
        └──────────┘ ░  ║ └╥┘
meas: 2/════════════════╩══╩═
                        0  1 

This PR:

        ┌──────────┐ ░ ┌─┐   
   q_0: ┤0         ├─░─┤M├───
        │  Unitary │ ░ └╥┘┌─┐
   q_1: ┤1         ├─░──╫─┤M├
        └──────────┘ ░  ║ └╥┘
meas: 2/════════════════╩══╩═
                        0  1 

With result = transpile(qc, basis_gates=[], translation_method='synthesis'):

0.18.3:

        ┌──────────┐ ░ ┌─┐   
   q_0: ┤0         ├─░─┤M├───
        │  Unitary │ ░ └╥┘┌─┐
   q_1: ┤1         ├─░──╫─┤M├
        └──────────┘ ░  ║ └╥┘
meas: 2/════════════════╩══╩═
                        0  1 

This PR:

        ┌──────────┐ ░ ┌─┐   
   q_0: ┤0         ├─░─┤M├───
        │  Unitary │ ░ └╥┘┌─┐
   q_1: ┤1         ├─░──╫─┤M├
        └──────────┘ ░  ║ └╥┘
meas: 2/════════════════╩══╩═
                        0  1 

I'll add additional tests and come up with a fix for the synthesis path too.

As was pointed out by @jakelishman in code review. Prior to Qiskit#6124 the
unitary synthesis pass was checking the basis for the presence of
unitary (and swap gates if pulse_optimize=True) in the basis set and
skipping the gates in the pass directly instead of in the pass manager.
This logic was lost when we added the plugin interface in Qiskit#6124
(probably as part of a rebase as it was a long lived branch). Since we
used to have the logic in the pass itself this commit changes the
approach to not adjust the preset passmanager usage and just make the
pass detect if unitary is in the basis and not synthesize. This
simplifies the logic and makes it less error prone.

At the same time the test coverage is expanded to ensure we preserve
this behavior moving forward (as aer's testing requires it).
jakelishman
jakelishman previously approved these changes Oct 6, 2021
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.

I'm ticking this because I'm happy with this PR whether or not we merge the comment I made above, and I'm leaving work for the evening, so I don't want to hold up getting Aer's CI back online if we decide to go ahead without those changes.

If they are made, I'll re-approve again once I've seen them, or someone else can do it for me.

@mtreinish mtreinish merged commit 4c9a620 into Qiskit:main Oct 6, 2021
@mtreinish mtreinish deleted the respect-unitary-in-basis branch October 6, 2021 20:23
@kdk kdk added this to the 0.19 milestone Nov 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: None Do not include in changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants