diff --git a/qiskit/transpiler/passes/synthesis/hls_plugins.py b/qiskit/transpiler/passes/synthesis/hls_plugins.py index eef996e636eb..11f9958f076c 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -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 ( @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) @@ -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) @@ -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 @@ -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 @@ -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 @@ -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) @@ -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 @@ -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) @@ -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 diff --git a/releasenotes/notes/fix-mcmt-to-gate-ec84e1c625312444.yaml b/releasenotes/notes/fix-mcmt-to-gate-ec84e1c625312444.yaml new file mode 100644 index 000000000000..04fb9cc3f1b5 --- /dev/null +++ b/releasenotes/notes/fix-mcmt-to-gate-ec84e1c625312444.yaml @@ -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 `__. +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. \ No newline at end of file diff --git a/test/python/circuit/library/test_mcmt.py b/test/python/circuit/library/test_mcmt.py index 73befb19db46..435ee0629593 100644 --- a/test/python/circuit/library/test_mcmt.py +++ b/test/python/circuit/library/test_mcmt.py @@ -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() diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py index 2a39c45bc48a..50bf2ecb493e 100644 --- a/test/python/transpiler/test_clifford_passes.py +++ b/test/python/transpiler/test_clifford_passes.py @@ -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() diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index 11aa8501afd0..b7bc31132a48 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -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.""" @@ -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.""" @@ -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."""