Repository: Qiskit/qiskit (Python)

Issue:
`transpile` initial layout and `SetLayout` pass don't work with `range` in 0.25
### Environment

- **Qiskit Terra version**: 0.25.0
- **Python version**: 3.11
- **Operating system**: MacOS

### What is happening?


The recent 0.25 release broke passing in `initial_layout` as a `range` in the transpile function. This used to work in previous versions.


### How can we reproduce the issue?

Running this code raises an exception

```python
from qiskit import QuantumCircuit
from qiskit import transpile

qc = QuantumCircuit(2)
transpile(qc, initial_layout=range(2))
```

```
---------------------------------------------------------------------------
TranspilerError                           Traceback (most recent call last)
Cell In[1], line 5
      2 from qiskit import transpile
      4 qc = QuantumCircuit(2)
----> 5 transpile(qc, initial_layout=range(2))

File ~/mambaforge/envs/pec11/lib/python3.11/site-packages/qiskit/compiler/transpiler.py:418, in transpile(circuits, backend, basis_gates, inst_map, coupling_map, backend_properties, initial_layout, layout_method, routing_method, translation_method, scheduling_method, instruction_durations, dt, approximation_degree, timing_constraints, 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)
    390         instruction_durations = _parse_instruction_durations(
    391             backend, instruction_durations, dt, circuits[0]
    392         )
    394 pm = generate_preset_pass_manager(
    395     optimization_level,
    396     backend=backend,
   (...)
    416     _skip_target=_skip_target,
    417 )
--> 418 out_circuits = pm.run(circuits, callback=callback)
    419 for name, circ in zip(output_name, out_circuits):
    420     circ.name = name

File ~/mambaforge/envs/pec11/lib/python3.11/site-packages/qiskit/transpiler/passmanager.py:537, in StagedPassManager.run(self, circuits, output_name, callback)
    530 def run(
    531     self,
    532     circuits: _CircuitsT,
    533     output_name: str | None = None,
    534     callback: Callable | None = None,
    535 ) -> _CircuitsT:
    536     self._update_passmanager()
--> 537     return super().run(circuits, output_name, callback)

File ~/mambaforge/envs/pec11/lib/python3.11/site-packages/qiskit/transpiler/passmanager.py:233, in PassManager.run(self, circuits, output_name, callback)
    231     return self._run_single_circuit(circuits, output_name, callback)
    232 if len(circuits) == 1:
--> 233     return [self._run_single_circuit(circuits[0], output_name, callback)]
    234 return self._run_several_circuits(circuits, output_name, callback)

File ~/mambaforge/envs/pec11/lib/python3.11/site-packages/qiskit/transpiler/passmanager.py:292, in PassManager._run_single_circuit(self, circuit, output_name, callback)
    280 """Run all the passes on a ``circuit``.
    281 
    282 Args:
   (...)
    289     The transformed circuit.
    290 """
    291 running_passmanager = self._create_running_passmanager()
--> 292 result = running_passmanager.run(circuit, output_name=output_name, callback=callback)
    293 self.property_set = running_passmanager.property_set
    294 return result

File ~/mambaforge/envs/pec11/lib/python3.11/site-packages/qiskit/transpiler/runningpassmanager.py:125, in RunningPassManager.run(***failed resolving arguments***)
    123 for passset in self.working_list:
    124     for pass_ in passset:
--> 125         dag = self._do_pass(pass_, dag, passset.options)
    127 circuit = dag_to_circuit(dag, copy_operations=False)
    128 if output_name:

File ~/mambaforge/envs/pec11/lib/python3.11/site-packages/qiskit/transpiler/runningpassmanager.py:173, in RunningPassManager._do_pass(self, pass_, dag, options)
    171 # Run the pass itself, if not already run
    172 if pass_ not in self.valid_passes:
--> 173     dag = self._run_this_pass(pass_, dag)
    175     # update the valid_passes property
    176     self._update_valid_passes(pass_)

File ~/mambaforge/envs/pec11/lib/python3.11/site-packages/qiskit/transpiler/runningpassmanager.py:227, in RunningPassManager._run_this_pass(self, pass_, dag)
    224 elif pass_.is_analysis_pass:
    225     # Measure time if we have a callback or logging set
    226     start_time = time()
--> 227     pass_.run(FencedDAGCircuit(dag))
    228     end_time = time()
    229     run_time = end_time - start_time

File ~/mambaforge/envs/pec11/lib/python3.11/site-packages/qiskit/transpiler/passes/layout/set_layout.py:60, in SetLayout.run(self, dag)
     58     layout = None
     59 else:
---> 60     raise TranspilerError(
     61         f"SetLayout was intialized with the layout type: {type(self.layout)}"
     62     )
     63 self.property_set["layout"] = layout
     64 return dag

TranspilerError: "SetLayout was intialized with the layout type: <class 'range'>"
```

### What should happen?

In 0.24 this code would run as expected.

### Any suggestions?

Recent changes to the `SetLayout` pass to support integer layouts look to have missed the case of range inputs.

[This line](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/transpiler/passes/layout/set_layout.py#L48-L54)

```python
if isinstance(self.layout, list):
```
Should be
```python
from collections.abc import Sequence
...

if isinstance(self.layout, Sequence):
```

If there is a reason I'm not aware of that you dont want to allow general Sequence-like objects it should be `(list, tuple, range)`


Discussion: Strictly this should have been an error to do this ever since #3821 - the `initial_layout` argument to `transpile` has never been typed as accepting a `range` but in practice it worked.  Fwiw, I'd keep `SetLayout` with a restrictive input format, and just modify `_parse_initial_layout` in `transpile` to normalise the input, rather than spreading more normalisation code that would need to be kept in sync further afield.
Also, `isinstance(x, collections.abc.Sequence)` is almost never correct - subclassing / registering as the ABC `Sequence` implies the existence of several methods that ABC mixes in to implementors that are almost never required, and many custom things like `numpy.ndarray` don't implement (`index` and `count` are big ones).  If anything, it would be `Iterable` with immediate normalisation, but really using runtime type-checking that something implements an interface in Python is rarely fully correct because it's fighting how the language was designed and works.  Better just to see if it quacks like a duck; `list(initial_layout)` is the only that's _actually_ required to work, and that will raise a very understandable error if something is badly typed.
I can take a look at this bug, if someone can assign it to me :)



Fix (patch):
diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py
index 360cd0af57f7..22dfd35be3c2 100644
--- a/qiskit/compiler/transpiler.py
+++ b/qiskit/compiler/transpiler.py
@@ -512,10 +512,10 @@ def _parse_initial_layout(initial_layout):
         return initial_layout
     if isinstance(initial_layout, dict):
         return Layout(initial_layout)
+    initial_layout = list(initial_layout)
     if all(phys is None or isinstance(phys, Qubit) for phys in initial_layout):
         return Layout.from_qubit_list(initial_layout)
-    else:
-        return initial_layout
+    return initial_layout
 
 
 def _parse_instruction_durations(backend, inst_durations, dt, circuit):
diff --git a/releasenotes/notes/fix-input-normalization-of-transpile-initial-layout.yaml b/releasenotes/notes/fix-input-normalization-of-transpile-initial-layout.yaml
new file mode 100644
index 000000000000..a996861ce992
--- /dev/null
+++ b/releasenotes/notes/fix-input-normalization-of-transpile-initial-layout.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+  - |
+    Fixed a regression in :func:`.transpile`, where an ``initial_layout`` given
+    as a :class:`range` object would no longer be treated as a valid input.
+    Fixed `#10544 <https://github.com/Qiskit/qiskit/issues/10554>`__.