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

Only apply MCMT plugin on MCMTGate #13596

Merged
merged 5 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 48 additions & 9 deletions qiskit/transpiler/passes/synthesis/hls_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,12 +420,13 @@
C3XGate,
C4XGate,
PauliEvolutionGate,
PermutationGate,
MCMTGate,
ModularAdderGate,
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
HalfAdderGate,
FullAdderGate,
MultiplierGate,
)
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.coupling import CouplingMap

from qiskit.synthesis.clifford import (
Expand Down Expand Up @@ -467,6 +468,7 @@
multiplier_qft_r17,
multiplier_cumulative_h18,
)
from qiskit.quantum_info.operators import Clifford
from qiskit.transpiler.passes.routing.algorithms import ApproximateTokenSwapper
from .plugin import HighLevelSynthesisPlugin

Expand All @@ -484,6 +486,9 @@ class DefaultSynthesisClifford(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given Clifford."""
if not isinstance(high_level_object, Clifford):
return None

decomposition = synth_clifford_full(high_level_object)
return decomposition

Expand All @@ -497,6 +502,9 @@ class AGSynthesisClifford(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given Clifford."""
if not isinstance(high_level_object, Clifford):
return None

decomposition = synth_clifford_ag(high_level_object)
return decomposition

Expand All @@ -513,10 +521,14 @@ class BMSynthesisClifford(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given Clifford."""
if not isinstance(high_level_object, Clifford):
return None

if high_level_object.num_qubits <= 3:
decomposition = synth_clifford_bm(high_level_object)
else:
decomposition = None

return decomposition


Expand All @@ -530,6 +542,9 @@ class GreedySynthesisClifford(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given Clifford."""
if not isinstance(high_level_object, Clifford):
return None

decomposition = synth_clifford_greedy(high_level_object)
return decomposition

Expand All @@ -544,6 +559,9 @@ class LayerSynthesisClifford(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given Clifford."""
if not isinstance(high_level_object, Clifford):
return None

decomposition = synth_clifford_layers(high_level_object)
return decomposition

Expand All @@ -559,6 +577,9 @@ class LayerLnnSynthesisClifford(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given Clifford."""
if not isinstance(high_level_object, Clifford):
return None

decomposition = synth_clifford_depth_lnn(high_level_object)
return decomposition

Expand All @@ -572,6 +593,9 @@ class DefaultSynthesisLinearFunction(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given LinearFunction."""
if not isinstance(high_level_object, LinearFunction):
return None

decomposition = synth_cnot_count_full_pmh(high_level_object.linear)
return decomposition

Expand All @@ -595,11 +619,8 @@ class KMSSynthesisLinearFunction(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given LinearFunction."""

if not isinstance(high_level_object, LinearFunction):
raise TranspilerError(
"PMHSynthesisLinearFunction only accepts objects of type LinearFunction"
)
return None

use_inverted = options.get("use_inverted", False)
use_transposed = options.get("use_transposed", False)
Expand Down Expand Up @@ -646,11 +667,8 @@ class PMHSynthesisLinearFunction(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given LinearFunction."""

if not isinstance(high_level_object, LinearFunction):
raise TranspilerError(
"PMHSynthesisLinearFunction only accepts objects of type LinearFunction"
)
return None

section_size = options.get("section_size", 2)
use_inverted = options.get("use_inverted", False)
Expand Down Expand Up @@ -682,6 +700,9 @@ class KMSSynthesisPermutation(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given Permutation."""
if not isinstance(high_level_object, PermutationGate):
return None

decomposition = synth_permutation_depth_lnn_kms(high_level_object.pattern)
return decomposition

Expand All @@ -695,6 +716,9 @@ class BasicSynthesisPermutation(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given Permutation."""
if not isinstance(high_level_object, PermutationGate):
return None

decomposition = synth_permutation_basic(high_level_object.pattern)
return decomposition

Expand All @@ -708,6 +732,9 @@ class ACGSynthesisPermutation(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given Permutation."""
if not isinstance(high_level_object, PermutationGate):
return None

decomposition = synth_permutation_acg(high_level_object.pattern)
return decomposition

Expand Down Expand Up @@ -858,6 +885,9 @@ class TokenSwapperSynthesisPermutation(HighLevelSynthesisPlugin):
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given Permutation."""

if not isinstance(high_level_object, PermutationGate):
return None

trials = options.get("trials", 5)
seed = options.get("seed", 0)
parallel_threshold = options.get("parallel_threshold", 50)
Expand Down Expand Up @@ -1156,6 +1186,9 @@ class MCMTSynthesisDefault(HighLevelSynthesisPlugin):

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
# first try to use the V-chain synthesis if enough auxiliary qubits are available
if not isinstance(high_level_object, MCMTGate):
return None

if (
decomposition := MCMTSynthesisVChain().run(
high_level_object, coupling_map, target, qubits, **options
Expand All @@ -1170,6 +1203,9 @@ class MCMTSynthesisNoAux(HighLevelSynthesisPlugin):
"""A V-chain based synthesis for ``MCMTGate``."""

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
if not isinstance(high_level_object, MCMTGate):
return None

base_gate = high_level_object.base_gate
ctrl_state = options.get("ctrl_state", None)

Expand All @@ -1195,6 +1231,9 @@ class MCMTSynthesisVChain(HighLevelSynthesisPlugin):
"""A V-chain based synthesis for ``MCMTGate``."""

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
if not isinstance(high_level_object, MCMTGate):
return None

if options.get("num_clean_ancillas", 0) < high_level_object.num_ctrl_qubits - 1:
return None # insufficient number of auxiliary qubits

Expand Down
15 changes: 15 additions & 0 deletions releasenotes/notes/fix-mcmt-to-gate-ec84e1c625312444.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
fixes:
- |
Fixed a bug where any instruction called ``"mcmt"`` would be passed into the high-level
synthesis routine for a :class:`.MCMTGate`, which causes a failure or invalid result.
In particular, this could happen accidentally when handling the :class:`.MCMT` _circuit_,
named ``"mcmt"``, and implicitly converting it into an instruction e.g. when appending
it to a circuit.
Fixed `#13563 <https://github.com/Qiskit/qiskit/issues/13563>`__.
upgrade_synthesis:
- |
The plugins for :class:`.LinearFunction` no longer raise an error if another object
than :class:`.LinearFunction` is passed into the ``run`` method. Instead, ``None`` is
returned, which is consistent with the other plugins. If you relied on this error being raised,
you can manually perform an instance-check.
13 changes: 13 additions & 0 deletions test/python/circuit/library/test_mcmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,19 @@ def test_gate_with_parameters_vchain(self):
self.assertEqual(circuit.num_parameters, 1)
self.assertIs(circuit.parameters[0], theta)

def test_mcmt_circuit_as_gate(self):
"""Test the MCMT plugin is only triggered for the gate, not the same-named circuit.

Regression test of #13563.
"""
circuit = QuantumCircuit(2)
gate = RYGate(0.1)
mcmt = MCMT(gate=gate, num_ctrl_qubits=1, num_target_qubits=1)
circuit.append(mcmt, circuit.qubits) # append the MCMT circuit as gate called "MCMT"
Comment on lines +295 to +296
Copy link
Contributor

Choose a reason for hiding this comment

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

A bit off-topic. Here we are appending the quantum circuit mcmt to the quantum circuit circuit, yet the docstring for append states instruction: Operation | CircuitInstruction. The code does work, since a QuantumCircuit defines a to_instruction method, which append tries to use when the instruction is not of type Operation. Would it be slightly cleaner to change to circuit.append(mcmt.to_instruction(), ...), or should we change the typehint for the append method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updating the docs for append sounds good, since we use append(QuantumCircuit) all over the place 🤔


transpiled = transpile(circuit, basis_gates=["u", "cx"])
self.assertEqual(Operator(transpiled), Operator(gate.control(1)))


if __name__ == "__main__":
unittest.main()
11 changes: 11 additions & 0 deletions test/python/transpiler/test_clifford_passes.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,17 @@ def test_collect_all_clifford_gates(self):
qct = PassManager(CollectCliffords(matrix_based=True)).run(qc)
self.assertEqual(qct.count_ops()["clifford"], 1)

def test_plugin_unfortunate_name(self):
"""Test the synthesis is not triggered for a custom gate with the same name."""
intruder = QuantumCircuit(2, name="clifford")
circuit = QuantumCircuit(2)
circuit.append(intruder.to_gate(), [0, 1])

hls = HighLevelSynthesis()
synthesized = hls(circuit)

self.assertIn("clifford", synthesized.count_ops())


if __name__ == "__main__":
unittest.main()
33 changes: 33 additions & 0 deletions test/python/transpiler/test_high_level_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,17 @@ def test_plugin_selection_all_with_metrix(self):
self.assertEqual(qct.size(), 24)
self.assertEqual(qct.depth(), 13)

def test_unfortunate_name(self):
"""Test the synthesis is not triggered for a custom gate with the same name."""
intruder = QuantumCircuit(2, name="linear_function")
circuit = QuantumCircuit(2)
circuit.append(intruder.to_gate(), [0, 1])

hls = HighLevelSynthesis()
synthesized = hls(circuit)

self.assertIn("linear_function", synthesized.count_ops())


class TestKMSSynthesisLinearFunctionPlugin(QiskitTestCase):
"""Tests for the KMSSynthesisLinearFunction plugin for synthesizing linear functions."""
Expand Down Expand Up @@ -877,6 +888,17 @@ def test_invert_and_transpose(self):
self.assertEqual(qct.size(), 87)
self.assertEqual(qct.depth(), 32)

def test_unfortunate_name(self):
"""Test the synthesis is not triggered for a custom gate with the same name."""
intruder = QuantumCircuit(2, name="linear_function")
circuit = QuantumCircuit(2)
circuit.append(intruder.to_gate(), [0, 1])

hls = HighLevelSynthesis()
synthesized = hls(circuit)

self.assertIn("linear_function", synthesized.count_ops())


class TestTokenSwapperPermutationPlugin(QiskitTestCase):
"""Tests for the token swapper plugin for synthesizing permutation gates."""
Expand Down Expand Up @@ -1059,6 +1081,17 @@ def test_concrete_synthesis_all_permutations(self):
qubits = tuple(qc_transpiled.find_bit(q).index for q in inst.qubits)
self.assertIn(qubits, edges)

def test_unfortunate_name(self):
"""Test the synthesis is not triggered for a custom gate with the same name."""
intruder = QuantumCircuit(2, name="permutation")
circuit = QuantumCircuit(2)
circuit.append(intruder.to_gate(), [0, 1])

hls = HighLevelSynthesis()
synthesized = hls(circuit)

self.assertIn("permutation", synthesized.count_ops())


class TestHighLevelSynthesisModifiers(QiskitTestCase):
"""Tests for high-level-synthesis pass."""
Expand Down
Loading