Qiskit / qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.
https://www.ibm.com/quantum/qiskit
Apache License 2.0
5.26k stars 2.37k forks source link

GenericBackendV2 with standard and controlflow operations fails #12735

Open sbrandhsn opened 4 months ago

sbrandhsn commented 4 months ago

Environment

What is happening?

The contructor of GenericBackendV2 fails on some standard gates and when a control-flow operation is supplied to the basis_gates parameter. I think we should support these cases, i.e. allow the construction of GenericBackendV2 on any standard gate and also when a user specifies the control-flow operations directly instead over the control_flow=True parameter.

How can we reproduce the issue?

gates = ['cx', 'id', 'rz', 'sx', 'x', 'reset', 'delay', 'measure']
gates = list(get_standard_gate_name_mapping())
ctrlfow = CONTROL_FLOW_OP_NAMES
ctrlfow = ["if_else", "while_loop", "for_loop", "switch_case", "break_loop", "continue_loop"]

be = GenericBackendV2(5, basis_gates=gates + ctrlfow)

What should happen?

Above parameter values for the GenericBackendV2 should not lead to an error (IMO). :-)

Any suggestions?

For the standard gates, it looks like the issue is coming from gates with more than two-qubits. When we both works, we should probably add a new test that exercises transpile(qc, backend=GenericBackendV2(gates, ctrlfow))...

shravanpatel30 commented 4 months ago

Hi @sbrandhsn, I have made some changes to the GenericBackendV2 that fixes the above issue. Can you please elaborate a little on what kind of test should I add? Once I add the test I will open a PR for this issue.

sbrandhsn commented 4 months ago

Sounds great, thanks for looking into this @shravanpatel30 I think a first test would be to check if the backend was correctly constructed, i.e. GenericBackendV2(...).operation_names == input_basis_gates. A secondary check may be to run transpile on the backend with the input basis gates. This may fail due to reasons unrelated to GenericBackendV2 but it would be good to attempt it and see what the outcome is.

shravanpatel30 commented 4 months ago

Hi @sbrandhsn, the backend constructed with my new changes passed the GenericBackendV2(...).operation_names == input_basis_gates test but as you expected, if I try to transpile using the same backend it throws an error AttributeError: 'PyDAG' object has no attribute 'remove_node_retain_edges_by_id'. The transpile will work if optimization_level=0 but it will throw error for optimization_level=1 or 2 or 3. Do you want me to add any other tests or should I open a PR?

sbrandhsn commented 3 months ago

Thanks! Can you see if this error only occurs for gates with more than 2 qubits? If this only happens for more than 2 qubits, we can display a warning, raise an error or generalize the respective transpiler methods. The latter potentially sounds like a lot of effort. :-)

shravanpatel30 commented 3 months ago

Hi @sbrandhsn, I did some poking around to see where the AttributeError was coming from, and I found out that sometime last week the rustworkx requirement was bumped up to >=0.15.0 (I was using rustworkx 0.14.0), and so after updating rustworkx the transpile function is working as expected. I am able to transpile the GenericBackendV2 constructed with all the standard gates and control flow operations.

The only caveat when transpiling is, when we have >2 qubit gates in the basis_gates argument of GenericBackendV2 we need to also specify basis_gates in the transpile function which consists of only 1 and 2-qubit gates, like ["u1", "u2", "u3", "cx", "id"]. If we leave basis_gates=None in the transpile function (which implies do not unroll the circuit) then we get an error. This error comes from the fact that for 3 and 4-qubit gates the usual coupling map (which has 2 length tuples like [[0,1], [1,0], [0,2], [2,0], ...]) is not enough to express their coupling. For 3 and 4 qubit gates we will need a coupling map which has 3 or 4 length tuples like [[0,1,2], [2,1,0], [0,2,3], [3,2,0], ...] or [[0,1,2,3], [3,2,1,0], [1,2,3,4], [4,3,2,1], ...].

Now that the transpile is working, coming to your earlier comment about adding a test where we transpile the GenericBackendV2, can you describe a bit more on what test I should include?

shravanpatel30 commented 3 months ago

Adding to my above comment, transpile will also work if the basis_gates of GenericBackendV2 and transpile are same. This means if there are same 3 and 4-qubit gates in both basis_gates arguments then it will work without any errors. To make things a bit clear, below example works without any errors:

from qiskit.circuit.random import random_circuit
from qiskit.compiler import transpile

#basis gates has 3 qubit gates alongwith 1 and 2-qubit gates
basis_gates = ["id", "rz", "sx", "x", "cx", 'ccx', 'rccx', "if_else", "switch_case"] 

backend = GenericBackendV2(5, basis_gates=basis_gates)

qc = random_circuit(num_qubits=4, depth=5)

qc_t = transpile(qc, backend=backend, optimization_level=3, basis_gates=basis_gates)

This will work for any optimization_level.

sbrandhsn commented 3 months ago

Hi Shravan, thanks for looking into this! :-)

I'm surprised you need to specify basis_gates in addition to backend for transpile to work. The way you construct your backend in above snippet implies that transpile does not need to run routing because backend has a coupling map with all-to-all connectivity. Can you generate a pass manager via pm = generate_preset_pass_manager(optimization_level=2, backend=2) and set the layout + routing stage of that pass manager to none (pm.layout=None, pm.routing=None) before using pm.run(qc)? See https://docs.quantum.ibm.com/api/qiskit/transpiler#transpiler-stage-details if you are curious about staged pass managers. I'm curious to see if that would transpile correctly. If it does, I think that would be a good test case.

shravanpatel30 commented 3 months ago

Hi @sbrandhsn, I tried using generate_preset_pass_manager and it correctly transpiles the circuit for optimization_level 0 and 1 but fails for 2 and 3. Here is what I tried:

from qiskit.providers.fake_provider import GenericBackendV2
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

gates = ['delay', 'x', 'reset', 'sx', 'rz', 'ecr', 'rcccx', 'ccx', 'switch_case', 'for_loop', 'id', 'measure', 'if_else']

backend = GenericBackendV2(4, basis_gates=gates)

qc = QuantumCircuit(4)
qc.h([0,2])
qc.cx(2,3)
qc.ccx(1,2,3)
qc.rcccx(0,1,2,3)
# qc.draw()

pm = generate_preset_pass_manager(backend=backend, optimization_level=1, seed_transpiler=10, layout_method=None, routing_method=None)

qc_pm = pm.run(qc)
qc_pm.draw('mpl')

For optimization_level 2 and 3, the vf2_utils gives an error that it only wants 2-length tuples as qargs and not 3 or 4-length tuples (for 3 and 4 qubit gates). Below is the full traceback when I run above code for optimization_level_2:

This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
This Target object contains multiqubit gates that operate on > 2 qubits. This will not be reflected in the output coupling map.
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[1], line 19
     15 # qc.draw()
     17 pm = generate_preset_pass_manager(backend=backend, optimization_level=2, seed_transpiler=10, layout_method=None, routing_method=None)
---> 19 qc_pm = pm.run(qc)
     20 qc_pm.draw('mpl')

File [C:\qiskit\qiskit\transpiler\passmanager.py:441](file:///C:/qiskit/qiskit/transpiler/passmanager.py#line=440), in StagedPassManager.run(self, circuits, output_name, callback, num_processes)
    433 def run(
    434     self,
    435     circuits: _CircuitsT,
   (...)
    438     num_processes: int = None,
    439 ) -> _CircuitsT:
    440     self._update_passmanager()
--> 441     return super().run(circuits, output_name, callback, num_processes=num_processes)

File [C:\qiskit\qiskit\transpiler\passmanager.py:464](file:///C:/qiskit/qiskit/transpiler/passmanager.py#line=463), in _replace_error.<locals>.wrapper(*meth_args, **meth_kwargs)
    461 @wraps(meth)
    462 def wrapper(*meth_args, **meth_kwargs):
    463     try:
--> 464         return meth(*meth_args, **meth_kwargs)
    465     except PassManagerError as ex:
    466         raise TranspilerError(ex.message) from ex

File [C:\qiskit\qiskit\transpiler\passmanager.py:226](file:///C:/qiskit/qiskit/transpiler/passmanager.py#line=225), in PassManager.run(self, circuits, output_name, callback, num_processes)
    223 if callback is not None:
    224     callback = _legacy_style_callback(callback)
--> 226 return super().run(
    227     in_programs=circuits,
    228     callback=callback,
    229     output_name=output_name,
    230     num_processes=num_processes,
    231 )

File [C:\qiskit\qiskit\passmanager\passmanager.py:232](file:///C:/qiskit/qiskit/passmanager/passmanager.py#line=231), in BasePassManager.run(self, in_programs, callback, num_processes, **kwargs)
    228 # If we're not going to run in parallel, we want to avoid spending time `dill` serializing
    229 # ourselves, since that can be quite expensive.
    230 if len(in_programs) == 1 or not should_run_in_parallel(num_processes):
    231     out = [
--> 232         _run_workflow(program=program, pass_manager=self, callback=callback, **kwargs)
    233         for program in in_programs
    234     ]
    235     if len(in_programs) == 1 and not is_list:
    236         return out[0]

File [C:\qiskit\qiskit\passmanager\passmanager.py:292](file:///C:/qiskit/qiskit/passmanager/passmanager.py#line=291), in _run_workflow(program, pass_manager, **kwargs)
    286 initial_status = WorkflowStatus()
    288 passmanager_ir = pass_manager._passmanager_frontend(
    289     input_program=program,
    290     **kwargs,
    291 )
--> 292 passmanager_ir, final_state = flow_controller.execute(
    293     passmanager_ir=passmanager_ir,
    294     state=PassManagerState(
    295         workflow_status=initial_status,
    296         property_set=PropertySet(),
    297     ),
    298     callback=kwargs.get("callback", None),
    299 )
    300 # The `property_set` has historically been returned as a mutable attribute on `PassManager`
    301 # This makes us non-reentrant (though `PassManager` would be dependent on its internal tasks to
    302 # be re-entrant if that was required), but is consistent with previous interfaces.  We're still
    303 # safe to be called in a serial loop, again assuming internal tasks are re-runnable.  The
    304 # conversion to the backend language is also allowed to use the property set, so it must be set
    305 # before calling it.
    306 pass_manager.property_set = final_state.property_set

File [C:\qiskit\qiskit\passmanager\base_tasks.py:218](file:///C:/qiskit/qiskit/passmanager/base_tasks.py#line=217), 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 [C:\qiskit\qiskit\passmanager\base_tasks.py:218](file:///C:/qiskit/qiskit/passmanager/base_tasks.py#line=217), 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 [C:\qiskit\qiskit\passmanager\base_tasks.py:98](file:///C:/qiskit/qiskit/passmanager/base_tasks.py#line=97), 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 [C:\qiskit\qiskit\transpiler\passes\layout\vf2_layout.py:138](file:///C:/qiskit/qiskit/transpiler/passes/layout/vf2_layout.py#line=137), in VF2Layout.run(self, dag)
    136 self.avg_error_map = self.property_set["vf2_avg_error_map"]
    137 if self.avg_error_map is None:
--> 138     self.avg_error_map = vf2_utils.build_average_error_map(
    139         self.target, self.properties, self.coupling_map
    140     )
    142 result = vf2_utils.build_interaction_graph(dag, self.strict_direction)
    143 if result is None:

File [C:\qiskit\qiskit\transpiler\passes\layout\vf2_utils.py:175](file:///C:/qiskit/qiskit/transpiler/passes/layout/vf2_utils.py#line=174), in build_average_error_map(target, properties, coupling_map)
    173             if len(qargs) == 1:
    174                 qargs = (qargs[0], qargs[0])
--> 175             avg_map.add_error(qargs, qarg_error / count)
    176             built = True
    177 elif properties is not None:

ValueError: expected a sequence of length 2 (got 4)

To see if this was the only issue, I added few lines of code in vf2_utils.py to handle 3 and 4 length tuples and after doing that both generate_preset_pass_manager and transpile works for optimization_level 2 and 3. Maybe I should open a PR so that you can look at all the changes I made in generic_backend_v2 and vf2_utils, and then decide how to proceed.

sbrandhsn commented 3 months ago

Sounds good, please do that and thank you @shravanpatel30 ! I think VF2Layout should not run with layout=None and routing=None. Can you share qc_pm.draw('mpl')? Also, please set qc_pm.layout=None and qc_pm.routing=None

shravanpatel30 commented 3 months ago

When you say, I should set qc_pm.layout=None and qc_pm.routing=None, I am getting an error (because qc_pm is a QuantumCircuit and does not have setter methods for layout). Sorry, if I am misunderstanding something here. I am using the below code to generate the outputs of qc_pm.draw('mpl'). I know you said that VF2Layout should not run but it still keeps giving the ValueError: expected a sequence of length 2 (got 4) even though I set layout_method=None and routing_method=None. So, for optimization_level=2 and optimization_level=3 I am using the updated vf2_utils.py.

from qiskit.providers.fake_provider import GenericBackendV2
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

gates = ['delay', 'x', 'reset', 'sx', 'rz', 'ecr', 'rcccx', 'ccx', 'switch_case', 'for_loop', 'id', 'measure', 'if_else']

backend = GenericBackendV2(4, basis_gates=gates)

qc = QuantumCircuit(4)
qc.h([0,2])
qc.cx(2,3)
qc.ccx(1,2,3)
qc.rcccx(0,1,2,3)
# qc.draw()

pm = generate_preset_pass_manager(backend=backend, optimization_level=3, seed_transpiler=10, layout_method=None, routing_method=None)

qc_pm = pm.run(qc)
qc_pm.draw('mpl')

For optimization_level=0:

op=0

For optimization_level=1:

op=1

For optimization_level=2:

op=2

For optimization_level=3:

op=3

I have also linked a PR, please take a look at the changes I made.

sbrandhsn commented 3 months ago

Hey @shravanpatel30, sorry - that was a typo. I meant pm.layout=None and pm.routing=None

shravanpatel30 commented 3 months ago

Hi @sbrandhsn, I tried the below code with pm.layout=None and pm.routing=None and that works for all the optimization_level (for this I am using the original vf2_utils file without my changes and there are no errors).

from qiskit.providers.fake_provider import GenericBackendV2
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

gates = ['delay', 'x', 'reset', 'sx', 'rz', 'ecr', 'rcccx', 'ccx', 'switch_case', 'for_loop', 'id', 'measure', 'if_else']

backend = GenericBackendV2(4, basis_gates=gates)

qc = QuantumCircuit(4)
qc.h([0,2])
qc.cx(2,3)
qc.ccx(1,2,3)
qc.rcccx(0,1,2,3)
# qc.draw()

pm = generate_preset_pass_manager(backend=backend, optimization_level=3, seed_transpiler=10)
pm.layout = None
pm.routing = None

qc_pm = pm.run(qc)
qc_pm.draw('mpl')

For optimization_level=0:

op=0

For optimization_level=1:

op=1

For optimization_level=2:

op=2

For optimization_level=3:

op=3
sbrandhsn commented 3 months ago

Thanks, sounds like this can be used in a test case. :-) Thank you for looking into this.

shravanpatel30 commented 3 months ago

Hi @sbrandhsn, I have added the test to the PR and have also removed the changes I made to vf2_utils. Please let me know if I should change something in the PR.