Open sbrandhsn opened 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.
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.
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?
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. :-)
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?
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
.
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.
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.
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
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
:
For optimization_level=1
:
For optimization_level=2
:
For optimization_level=3
:
I have also linked a PR, please take a look at the changes I made.
Hey @shravanpatel30, sorry - that was a typo. I meant pm.layout=None
and pm.routing=None
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
:
For optimization_level=1
:
For optimization_level=2
:
For optimization_level=3
:
Thanks, sounds like this can be used in a test case. :-) Thank you for looking into this.
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.
Environment
What is happening?
The contructor of
GenericBackendV2
fails on some standard gates and when a control-flow operation is supplied to thebasis_gates
parameter. I think we should support these cases, i.e. allow the construction ofGenericBackendV2
on any standard gate and also when a user specifies the control-flow operations directly instead over thecontrol_flow=True
parameter.How can we reproduce the issue?
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))
...