Skip to content

Commit

Permalink
Only apply MCMT plugin on MCMTGate (#13596)
Browse files Browse the repository at this point in the history
* Only apply MCMT plugin on MCMTGate

* add checks for other gates

* fix reno

* use == instead of equiv
  • Loading branch information
Cryoris authored Jan 7, 2025
1 parent b9cba9f commit 0eeba0d
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 9 deletions.
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,
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"

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

0 comments on commit 0eeba0d

Please sign in to comment.