Skip to content

Single-qubit permutation causes Rust transpiler to panic #15426

@fvoichick

Description

@fvoichick

Environment

  • Qiskit version: 2.2.3
  • Python version: 3.12.3
  • Operating system: Ubuntu 24.04.3 LTS

What is happening?

I'm getting an internal error from the Rust part of the Qiskit transpiler when trying to compile a single-qubit permutation (which should be a no-op) to a device.

How can we reproduce the issue?

Run this code:

from qiskit_ibm_runtime.fake_provider import FakeTorino
from qiskit.circuit.library import PermutationGate
from qiskit import QuantumCircuit, transpile

c = QuantumCircuit(1)
c.append(PermutationGate([0]), [0])
transpile(c, backend=FakeTorino())

What should happen?

Instead of compiling to an empty circuit, I get this error message:

---------------------------------------------------------------------------
PanicException                            Traceback (most recent call last)
Cell In[8], line 7
      5 c = QuantumCircuit(1)
      6 c.append(PermutationGate([0]), [0])
----> 7 transpile(c, backend=FakeTorino())

File path/to/venv/lib/python3.12/site-packages/qiskit/compiler/transpiler.py:291, in transpile(circuits, backend, basis_gates, coupling_map, initial_layout, layout_method, routing_method, translation_method, scheduling_method, dt, approximation_degree, seed_transpiler, optimization_level, callback, output_name, unitary_synthesis_method, unitary_synthesis_plugin_config, target, hls_config, init_method, optimization_method, ignore_backend_supplied_default_methods, num_processes, qubits_initially_zero)
    266 # Edge cases require using the old model (loose constraints) instead of building a target,
    267 # but we don't populate the passmanager config with loose constraints unless it's one of
    268 # the known edge cases to control the execution path.
    269 pm = generate_preset_pass_manager(
    270     optimization_level,
    271     target=target,
   (...)
    288     qubits_initially_zero=qubits_initially_zero,
    289 )
--> 291 out_circuits = pm.run(circuits, callback=callback, num_processes=num_processes)
    293 for name, circ in zip(output_name, out_circuits):
    294     circ.name = name

File path/to/venv/lib/python3.12/site-packages/qiskit/transpiler/passmanager.py:415, in StagedPassManager.run(self, circuits, output_name, callback, num_processes, property_set)
    405 def run(
    406     self,
    407     circuits: _CircuitsT,
   (...)
    412     property_set: dict[str, object] | None = None,
    413 ) -> _CircuitsT:
    414     self._update_passmanager()
--> 415     return super().run(circuits, output_name, callback, num_processes=num_processes)

File path/to/venv/lib/python3.12/site-packages/qiskit/transpiler/passmanager.py:438, in _replace_error.<locals>.wrapper(*meth_args, **meth_kwargs)
    435 @wraps(meth)
    436 def wrapper(*meth_args, **meth_kwargs):
    437     try:
--> 438         return meth(*meth_args, **meth_kwargs)
    439     except TranspilerError:
    440         # If it's already a `TranspilerError` subclass, don't erase the extra information.
    441         raise

File path/to/venv/lib/python3.12/site-packages/qiskit/transpiler/passmanager.py:197, in PassManager.run(self, circuits, output_name, callback, num_processes, property_set)
    194 if callback is not None:
    195     callback = _legacy_style_callback(callback)
--> 197 return super().run(
    198     in_programs=circuits,
    199     callback=callback,
    200     output_name=output_name,
    201     num_processes=num_processes,
    202     property_set=property_set,
    203 )

File path/to/venv/lib/python3.12/site-packages/qiskit/passmanager/passmanager.py:238, in BasePassManager.run(self, in_programs, callback, num_processes, property_set, **kwargs)
    234 # If we're not going to run in parallel, we want to avoid spending time `dill` serializing
    235 # ourselves, since that can be quite expensive.
    236 if len(in_programs) == 1 or not should_run_in_parallel(num_processes):
    237     out = [
--> 238         _run_workflow(
    239             program=program,
    240             pass_manager=self,
    241             callback=callback,
    242             initial_property_set=property_set,
    243             **kwargs,
    244         )
    245         for program in in_programs
    246     ]
    247     if len(in_programs) == 1 and not is_list:
    248         return out[0]

File path/to/venv/lib/python3.12/site-packages/qiskit/passmanager/passmanager.py:311, in _run_workflow(program, pass_manager, initial_property_set, **kwargs)
    306 pass_manager.property_set = property_set
    307 passmanager_ir = pass_manager._passmanager_frontend(
    308     input_program=program,
    309     **kwargs,
    310 )
--> 311 passmanager_ir, final_state = flow_controller.execute(
    312     passmanager_ir=passmanager_ir,
    313     state=PassManagerState(
    314         workflow_status=initial_status, property_set=pass_manager.property_set
    315     ),
    316     callback=kwargs.get("callback", None),
    317 )
    318 # The `property_set` has historically been returned as a mutable attribute on `PassManager`
    319 # This makes us non-reentrant (though `PassManager` would be dependent on its internal tasks to
    320 # be re-entrant if that was required), but is consistent with previous interfaces.  We're still
    321 # safe to be called in a serial loop, again assuming internal tasks are re-runnable.  The
    322 # conversion to the backend language is also allowed to use the property set, so it must be set
    323 # before calling it.
    324 pass_manager.property_set = final_state.property_set

File path/to/venv/lib/python3.12/site-packages/qiskit/passmanager/base_tasks.py:218, in BaseController.execute(self, passmanager_ir, state, callback)
    216     return passmanager_ir, state
    217 while True:
--> 218     passmanager_ir, state = next_task.execute(
    219         passmanager_ir=passmanager_ir,
    220         state=state,
    221         callback=callback,
    222     )
    223     try:
    224         # Sending the object through the generator implies the custom controllers
    225         # can always rely on the latest data to choose the next task to run.
    226         next_task = task_generator.send(state)

File path/to/venv/lib/python3.12/site-packages/qiskit/transpiler/basepasses.py:167, in TransformationPass.execute(self, passmanager_ir, state, callback)
    161 def execute(
    162     self,
    163     passmanager_ir: PassManagerIR,
    164     state: PassManagerState,
    165     callback: Callable = None,
    166 ) -> tuple[PassManagerIR, PassManagerState]:
--> 167     new_dag, state = super().execute(
    168         passmanager_ir=passmanager_ir,
    169         state=state,
    170         callback=callback,
    171     )
    173     if state.workflow_status.previous_run == RunState.SUCCESS:
    174         if not isinstance(new_dag, DAGCircuit):

File path/to/venv/lib/python3.12/site-packages/qiskit/passmanager/base_tasks.py:98, in GenericPass.execute(self, passmanager_ir, state, callback)
     96 try:
     97     if self not in state.workflow_status.completed_passes:
---> 98         ret = self.run(passmanager_ir)
     99         run_state = RunState.SUCCESS
    100     else:

File path/to/venv/lib/python3.12/site-packages/qiskit/transpiler/passes/optimization/elide_permutations.py:69, in ElidePermutations.run(self, dag)
     63     logger.warning(
     64         "ElidePermutations is not valid after a layout has been set. This indicates "
     65         "an invalid pass manager construction."
     66     )
     67     return dag
---> 69 result = elide_permutations_rs.run(dag)
     71 # If the pass did not do anything, the result is None
     72 if result is None:

PanicException: internal error: entered unreachable code

Any suggestions?

It seems like the problem is probably in elide_permutations_rs, but I haven't looked further.

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions