Skip to content

Commit 103b779

Browse files
Only apply MCMT plugin on MCMTGate (#13596) (#13618)
* Only apply MCMT plugin on MCMTGate * add checks for other gates * fix reno * use == instead of equiv (cherry picked from commit 0eeba0d) Co-authored-by: Julien Gacon <[email protected]>
1 parent 768bd04 commit 103b779

File tree

5 files changed

+120
-9
lines changed

5 files changed

+120
-9
lines changed

qiskit/transpiler/passes/synthesis/hls_plugins.py

+48-9
Original file line numberDiff line numberDiff line change
@@ -420,12 +420,13 @@
420420
C3XGate,
421421
C4XGate,
422422
PauliEvolutionGate,
423+
PermutationGate,
424+
MCMTGate,
423425
ModularAdderGate,
424426
HalfAdderGate,
425427
FullAdderGate,
426428
MultiplierGate,
427429
)
428-
from qiskit.transpiler.exceptions import TranspilerError
429430
from qiskit.transpiler.coupling import CouplingMap
430431

431432
from qiskit.synthesis.clifford import (
@@ -467,6 +468,7 @@
467468
multiplier_qft_r17,
468469
multiplier_cumulative_h18,
469470
)
471+
from qiskit.quantum_info.operators import Clifford
470472
from qiskit.transpiler.passes.routing.algorithms import ApproximateTokenSwapper
471473
from .plugin import HighLevelSynthesisPlugin
472474

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

485487
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
486488
"""Run synthesis for the given Clifford."""
489+
if not isinstance(high_level_object, Clifford):
490+
return None
491+
487492
decomposition = synth_clifford_full(high_level_object)
488493
return decomposition
489494

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

498503
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
499504
"""Run synthesis for the given Clifford."""
505+
if not isinstance(high_level_object, Clifford):
506+
return None
507+
500508
decomposition = synth_clifford_ag(high_level_object)
501509
return decomposition
502510

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

514522
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
515523
"""Run synthesis for the given Clifford."""
524+
if not isinstance(high_level_object, Clifford):
525+
return None
526+
516527
if high_level_object.num_qubits <= 3:
517528
decomposition = synth_clifford_bm(high_level_object)
518529
else:
519530
decomposition = None
531+
520532
return decomposition
521533

522534

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

531543
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
532544
"""Run synthesis for the given Clifford."""
545+
if not isinstance(high_level_object, Clifford):
546+
return None
547+
533548
decomposition = synth_clifford_greedy(high_level_object)
534549
return decomposition
535550

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

545560
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
546561
"""Run synthesis for the given Clifford."""
562+
if not isinstance(high_level_object, Clifford):
563+
return None
564+
547565
decomposition = synth_clifford_layers(high_level_object)
548566
return decomposition
549567

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

560578
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
561579
"""Run synthesis for the given Clifford."""
580+
if not isinstance(high_level_object, Clifford):
581+
return None
582+
562583
decomposition = synth_clifford_depth_lnn(high_level_object)
563584
return decomposition
564585

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

573594
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
574595
"""Run synthesis for the given LinearFunction."""
596+
if not isinstance(high_level_object, LinearFunction):
597+
return None
598+
575599
decomposition = synth_cnot_count_full_pmh(high_level_object.linear)
576600
return decomposition
577601

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

596620
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
597621
"""Run synthesis for the given LinearFunction."""
598-
599622
if not isinstance(high_level_object, LinearFunction):
600-
raise TranspilerError(
601-
"PMHSynthesisLinearFunction only accepts objects of type LinearFunction"
602-
)
623+
return None
603624

604625
use_inverted = options.get("use_inverted", False)
605626
use_transposed = options.get("use_transposed", False)
@@ -646,11 +667,8 @@ class PMHSynthesisLinearFunction(HighLevelSynthesisPlugin):
646667

647668
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
648669
"""Run synthesis for the given LinearFunction."""
649-
650670
if not isinstance(high_level_object, LinearFunction):
651-
raise TranspilerError(
652-
"PMHSynthesisLinearFunction only accepts objects of type LinearFunction"
653-
)
671+
return None
654672

655673
section_size = options.get("section_size", 2)
656674
use_inverted = options.get("use_inverted", False)
@@ -682,6 +700,9 @@ class KMSSynthesisPermutation(HighLevelSynthesisPlugin):
682700

683701
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
684702
"""Run synthesis for the given Permutation."""
703+
if not isinstance(high_level_object, PermutationGate):
704+
return None
705+
685706
decomposition = synth_permutation_depth_lnn_kms(high_level_object.pattern)
686707
return decomposition
687708

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

696717
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
697718
"""Run synthesis for the given Permutation."""
719+
if not isinstance(high_level_object, PermutationGate):
720+
return None
721+
698722
decomposition = synth_permutation_basic(high_level_object.pattern)
699723
return decomposition
700724

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

709733
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
710734
"""Run synthesis for the given Permutation."""
735+
if not isinstance(high_level_object, PermutationGate):
736+
return None
737+
711738
decomposition = synth_permutation_acg(high_level_object.pattern)
712739
return decomposition
713740

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

888+
if not isinstance(high_level_object, PermutationGate):
889+
return None
890+
861891
trials = options.get("trials", 5)
862892
seed = options.get("seed", 0)
863893
parallel_threshold = options.get("parallel_threshold", 50)
@@ -1156,6 +1186,9 @@ class MCMTSynthesisDefault(HighLevelSynthesisPlugin):
11561186

11571187
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
11581188
# first try to use the V-chain synthesis if enough auxiliary qubits are available
1189+
if not isinstance(high_level_object, MCMTGate):
1190+
return None
1191+
11591192
if (
11601193
decomposition := MCMTSynthesisVChain().run(
11611194
high_level_object, coupling_map, target, qubits, **options
@@ -1170,6 +1203,9 @@ class MCMTSynthesisNoAux(HighLevelSynthesisPlugin):
11701203
"""A V-chain based synthesis for ``MCMTGate``."""
11711204

11721205
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
1206+
if not isinstance(high_level_object, MCMTGate):
1207+
return None
1208+
11731209
base_gate = high_level_object.base_gate
11741210
ctrl_state = options.get("ctrl_state", None)
11751211

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

11971233
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
1234+
if not isinstance(high_level_object, MCMTGate):
1235+
return None
1236+
11981237
if options.get("num_clean_ancillas", 0) < high_level_object.num_ctrl_qubits - 1:
11991238
return None # insufficient number of auxiliary qubits
12001239

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed a bug where any instruction called ``"mcmt"`` would be passed into the high-level
5+
synthesis routine for a :class:`.MCMTGate`, which causes a failure or invalid result.
6+
In particular, this could happen accidentally when handling the :class:`.MCMT` _circuit_,
7+
named ``"mcmt"``, and implicitly converting it into an instruction e.g. when appending
8+
it to a circuit.
9+
Fixed `#13563 <https://github.com/Qiskit/qiskit/issues/13563>`__.
10+
upgrade_synthesis:
11+
- |
12+
The plugins for :class:`.LinearFunction` no longer raise an error if another object
13+
than :class:`.LinearFunction` is passed into the ``run`` method. Instead, ``None`` is
14+
returned, which is consistent with the other plugins. If you relied on this error being raised,
15+
you can manually perform an instance-check.

test/python/circuit/library/test_mcmt.py

+13
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,19 @@ def test_gate_with_parameters_vchain(self):
285285
self.assertEqual(circuit.num_parameters, 1)
286286
self.assertIs(circuit.parameters[0], theta)
287287

288+
def test_mcmt_circuit_as_gate(self):
289+
"""Test the MCMT plugin is only triggered for the gate, not the same-named circuit.
290+
291+
Regression test of #13563.
292+
"""
293+
circuit = QuantumCircuit(2)
294+
gate = RYGate(0.1)
295+
mcmt = MCMT(gate=gate, num_ctrl_qubits=1, num_target_qubits=1)
296+
circuit.append(mcmt, circuit.qubits) # append the MCMT circuit as gate called "MCMT"
297+
298+
transpiled = transpile(circuit, basis_gates=["u", "cx"])
299+
self.assertEqual(Operator(transpiled), Operator(gate.control(1)))
300+
288301

289302
if __name__ == "__main__":
290303
unittest.main()

test/python/transpiler/test_clifford_passes.py

+11
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,17 @@ def test_collect_all_clifford_gates(self):
831831
qct = PassManager(CollectCliffords(matrix_based=True)).run(qc)
832832
self.assertEqual(qct.count_ops()["clifford"], 1)
833833

834+
def test_plugin_unfortunate_name(self):
835+
"""Test the synthesis is not triggered for a custom gate with the same name."""
836+
intruder = QuantumCircuit(2, name="clifford")
837+
circuit = QuantumCircuit(2)
838+
circuit.append(intruder.to_gate(), [0, 1])
839+
840+
hls = HighLevelSynthesis()
841+
synthesized = hls(circuit)
842+
843+
self.assertIn("clifford", synthesized.count_ops())
844+
834845

835846
if __name__ == "__main__":
836847
unittest.main()

test/python/transpiler/test_high_level_synthesis.py

+33
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,17 @@ def test_plugin_selection_all_with_metrix(self):
827827
self.assertEqual(qct.size(), 24)
828828
self.assertEqual(qct.depth(), 13)
829829

830+
def test_unfortunate_name(self):
831+
"""Test the synthesis is not triggered for a custom gate with the same name."""
832+
intruder = QuantumCircuit(2, name="linear_function")
833+
circuit = QuantumCircuit(2)
834+
circuit.append(intruder.to_gate(), [0, 1])
835+
836+
hls = HighLevelSynthesis()
837+
synthesized = hls(circuit)
838+
839+
self.assertIn("linear_function", synthesized.count_ops())
840+
830841

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

891+
def test_unfortunate_name(self):
892+
"""Test the synthesis is not triggered for a custom gate with the same name."""
893+
intruder = QuantumCircuit(2, name="linear_function")
894+
circuit = QuantumCircuit(2)
895+
circuit.append(intruder.to_gate(), [0, 1])
896+
897+
hls = HighLevelSynthesis()
898+
synthesized = hls(circuit)
899+
900+
self.assertIn("linear_function", synthesized.count_ops())
901+
880902

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

1084+
def test_unfortunate_name(self):
1085+
"""Test the synthesis is not triggered for a custom gate with the same name."""
1086+
intruder = QuantumCircuit(2, name="permutation")
1087+
circuit = QuantumCircuit(2)
1088+
circuit.append(intruder.to_gate(), [0, 1])
1089+
1090+
hls = HighLevelSynthesis()
1091+
synthesized = hls(circuit)
1092+
1093+
self.assertIn("permutation", synthesized.count_ops())
1094+
10621095

10631096
class TestHighLevelSynthesisModifiers(QiskitTestCase):
10641097
"""Tests for high-level-synthesis pass."""

0 commit comments

Comments
 (0)