Qiskit / qiskit-ibm-runtime

IBM Client for Qiskit Runtime
https://docs.quantum.ibm.com/api/qiskit-ibm-runtime
Apache License 2.0
139 stars 149 forks source link

transpile does not work with IBM Runtime Backend #726

Closed kevinsung closed 1 year ago

kevinsung commented 1 year ago

Environment

What is happening?

transpilation for an IBM Runtime backend fails when it involves unitary synthesis

How can we reproduce the issue?

import numpy as np
from qiskit import QuantumCircuit, transpile
from qiskit.extensions import UnitaryGate
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
backend = service.get_backend("ibm_sherbrooke")

circuit = QuantumCircuit(2)
circuit.append(UnitaryGate(np.eye(4)), [0, 1])
transpile(circuit, backend=backend)
---------------------------------------------------------------------------
QiskitError                               Traceback (most recent call last)
<ipython-input-5-f49936502b93> in <module>
      9 circuit = QuantumCircuit(2)
     10 circuit.append(UnitaryGate(np.eye(4)), [0, 1])
---> 11 transpile(circuit, backend=backend)

~/projects/qiskit-terra/qiskit/compiler/transpiler.py 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)
    379             transpile_config, pass_manager = _combine_args(shared_args, unique_args)
    380             output_circuits.append(
--> 381                 _serial_transpile_circuit(
    382                     circuit,
    383                     pass_manager,

~/projects/qiskit-terra/qiskit/compiler/transpiler.py in _serial_transpile_circuit(circuit, pass_manager, callback, output_name, num_qubits, faulty_qubits_map, backend_prop)
    472     backend_prop=None,
    473 ):
--> 474     result = pass_manager.run(circuit, callback=callback, output_name=output_name)
    475     if faulty_qubits_map:
    476         return _remap_circuit_faulty_backend(

~/projects/qiskit-terra/qiskit/transpiler/passmanager.py in run(self, circuits, output_name, callback)
    526     ) -> Union[QuantumCircuit, List[QuantumCircuit]]:
    527         self._update_passmanager()
--> 528         return super().run(circuits, output_name, callback)
    529 
    530     def draw(self, filename=None, style=None, raw=False):

~/projects/qiskit-terra/qiskit/transpiler/passmanager.py in run(self, circuits, output_name, callback)
    226             return circuits
    227         if isinstance(circuits, QuantumCircuit):
--> 228             return self._run_single_circuit(circuits, output_name, callback)
    229         if len(circuits) == 1:
    230             return self._run_single_circuit(circuits[0], output_name, callback)

~/projects/qiskit-terra/qiskit/transpiler/passmanager.py in _run_single_circuit(self, circuit, output_name, callback)
    281         """
    282         running_passmanager = self._create_running_passmanager()
--> 283         result = running_passmanager.run(circuit, output_name=output_name, callback=callback)
    284         self.property_set = running_passmanager.property_set
    285         return result

~/projects/qiskit-terra/qiskit/transpiler/runningpassmanager.py in 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)
    126 
    127         circuit = dag_to_circuit(dag)

~/projects/qiskit-terra/qiskit/transpiler/runningpassmanager.py in _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)
    174 
    175                 # update the valid_passes property

~/projects/qiskit-terra/qiskit/transpiler/runningpassmanager.py in _run_this_pass(self, pass_, dag)
    200             # Measure time if we have a callback or logging set
    201             start_time = time()
--> 202             new_dag = pass_.run(dag)
    203             end_time = time()
    204             run_time = end_time - start_time

~/projects/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py in run(self, dag)
    431             # pylint: disable=attribute-defined-outside-init
    432             plugin_method._approximation_degree = self._approximation_degree
--> 433         return self._run_main_loop(
    434             dag, plugin_method, plugin_kwargs, default_method, default_kwargs
    435         )

~/projects/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py in _run_main_loop(self, dag, plugin_method, plugin_kwargs, default_method, default_kwargs)
    472                     [dag_bit_indices[x] for x in node.qargs],
    473                 )
--> 474             synth_dag = method.run(unitary, **kwargs)
    475             if synth_dag is not None:
    476                 dag.substitute_node_with_dag(node, synth_dag)

~/projects/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py in run(self, unitary, **options)
    730             # select synthesizers that can lower to the target
    731             if target is not None:
--> 732                 decomposers2q = self._decomposer_2q_from_target(
    733                     target, qubits, approximation_degree
    734                 )

~/projects/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py in _decomposer_2q_from_target(self, target, qubits, approximation_degree)
    644 
    645         # possible supercontrolled decomposers (i.e. TwoQubitBasisDecomposer)
--> 646         supercontrolled_basis = {
    647             k: v for k, v in available_2q_basis.items() if is_supercontrolled(v)
    648         }

~/projects/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py in <dictcomp>(.0)
    645         # possible supercontrolled decomposers (i.e. TwoQubitBasisDecomposer)
    646         supercontrolled_basis = {
--> 647             k: v for k, v in available_2q_basis.items() if is_supercontrolled(v)
    648         }
    649         for basis_1q, basis_2q in product(available_1q_basis, supercontrolled_basis.keys()):

~/projects/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py in is_supercontrolled(gate)
    636 
    637         def is_supercontrolled(gate):
--> 638             kak = TwoQubitWeylDecomposition(Operator(gate).data)
    639             return isclose(kak.a, pi / 4) and isclose(kak.c, 0.0)
    640 

~/projects/qiskit-terra/qiskit/quantum_info/operators/operator.py in __init__(self, data, input_dims, output_dims)
     83             # conditional gates, measure, or reset will cause an
     84             # exception to be raised.
---> 85             self._data = self._init_instruction(data).data
     86         elif hasattr(data, "to_operator"):
     87             # If the data object has a 'to_operator' attribute this is given

~/projects/qiskit-terra/qiskit/quantum_info/operators/operator.py in _init_instruction(cls, instruction)
    539         if isinstance(instruction, QuantumCircuit):
    540             instruction = instruction.to_instruction()
--> 541         op._append_instruction(instruction)
    542         return op
    543 

~/projects/qiskit-terra/qiskit/quantum_info/operators/operator.py in _append_instruction(self, obj, qargs)
    583             # cannot compose this gate and raise an error.
    584             if obj.definition is None:
--> 585                 raise QiskitError(f"Cannot apply Operation: {obj.name}")
    586             if not isinstance(obj.definition, QuantumCircuit):
    587                 raise QiskitError(

QiskitError: 'Cannot apply Operation: ecr'

What should happen?

it should work

Any suggestions?

it works if I get the backend from IBMQ

mtreinish commented 1 year ago

I took a quick look at this, there are two issues the first is primarily an issue in the ibm runtime backend's Target. The code used by the runtime provider to generate the Target doesn't know about the ecr gate (or cz which will be an issue on some backends): https://github.com/Qiskit/qiskit-ibm-runtime/blob/main/qiskit_ibm_runtime/utils/backend_converter.py#L42-L49 . When the runtime doesn't know about the gate name it creates an opaque gate without a definition or matrix to represent the operation.

This leads to where the bug in terra is, the 2q unitary synthesis pass should be just ignoring gates in the target which it can't compute the matrix of since if there is no matrix the pass won't be able to do anything with that as a target anyway.

mtreinish commented 1 year ago

I pushed up a fix for the unitary synthesis pass to fix it's bug when using opaque gates. But I'm going to transfer this issue to qiskit-ibm-runtime because the real issue is that the backend's target isn't using ECRGate and is instead creating a custom opaque gate with the name "ecr".

kevinsung commented 1 year ago

The code used by the runtime provider to generate the Target doesn't know about the ecr gate (or cz which will be an issue on some backends): https://github.com/Qiskit/qiskit-ibm-runtime/blob/main/qiskit_ibm_runtime/utils/backend_converter.py#L42-L49 .

The fix is simply to add ECRGate to that dictionary?

mtreinish commented 1 year ago

Yeah, just adding ECRGate to that dictionary should fix this. It will tell the Target constructor when it encounters the ecr string in the configuration and properties payload that it means ECRGate. We also should add CZGate since ibm_prague is using cz as the entangling gate.