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.25k stars 2.37k forks source link

Defining a custom gate called `qft` in QASM apparently collides with qiskit QFT in v1.2 #13120

Closed lochsh closed 1 month ago

lochsh commented 2 months ago

Environment

What is happening?

If I transpile this qasm:

OPENQASM 2.0;
include "qelib1.inc";

gate qft q0,q1 { h q1; barrier q0,q1; crz(pi/2) q0,q1; h q0; barrier q0,q1; }
qreg q30[2];
qft q30[0],q30[1];

then I get a TranspilerError:

Python 3.10.12 (main, Jul 29 2024, 16:56:48) [GCC 11.4.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.27.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import qiskit

In [2]: import qiskit.qasm2

In [3]: qc = qiskit.qasm2.load("example.qasm")

In [4]: qiskit.transpile(qc)
backtrace ```python --------------------------------------------------------------------------- TranspilerError Traceback (most recent call last) File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/transpiler/passmanager.py:464, in _replace_error..wrapper(*meth_args, **meth_kwargs) 463 try: --> 464 return meth(*meth_args, **meth_kwargs) 465 except PassManagerError as ex: File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/transpiler/passmanager.py:226, in PassManager.run(self, circuits, output_name, callback, num_processes) 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 ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/passmanager/passmanager.py:231, in BasePassManager.run(self, in_programs, callback, num_processes, **kwargs) 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: File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/passmanager/passmanager.py:232, in (.0) 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: File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/passmanager/passmanager.py:292, in _run_workflow(program, pass_manager, **kwargs) 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. File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/passmanager/base_tasks.py:218, in BaseController.execute(self, passmanager_ir, state, callback) 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. File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/transpiler/basepasses.py:195, in TransformationPass.execute(self, passmanager_ir, state, callback) 189 def execute( 190 self, 191 passmanager_ir: PassManagerIR, 192 state: PassManagerState, 193 callback: Callable = None, 194 ) -> tuple[PassManagerIR, PassManagerState]: --> 195 new_dag, state = super().execute( 196 passmanager_ir=passmanager_ir, 197 state=state, 198 callback=callback, 199 ) 201 if state.workflow_status.previous_run == RunState.SUCCESS: File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/passmanager/base_tasks.py:98, in GenericPass.execute(self, passmanager_ir, state, callback) 97 if self not in state.workflow_status.completed_passes: ---> 98 ret = self.run(passmanager_ir) 99 run_state = RunState.SUCCESS File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/transpiler/passes/synthesis/high_level_synthesis.py:443, in HighLevelSynthesis.run(self, dag) 441 continue --> 443 decomposition, modified = self._recursively_handle_op(node.op, qubits) 445 if not modified: File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/transpiler/passes/synthesis/high_level_synthesis.py:527, in HighLevelSynthesis._recursively_handle_op(self, op, qubits) 523 # WARNING: if adding new things in here, ensure that `_definitely_skip_node` is also 524 # up-to-date. 525 526 # Try to apply plugin mechanism --> 527 decomposition = self._synthesize_op_using_plugins(op, qubits) 528 if decomposition is not None: File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/transpiler/passes/synthesis/high_level_synthesis.py:625, in HighLevelSynthesis._synthesize_op_using_plugins(self, op, qubits) 623 plugin_method = plugin_specifier --> 625 decomposition = plugin_method.run( 626 op, 627 coupling_map=self._coupling_map, 628 target=self._target, 629 qubits=qubits, 630 **plugin_args, 631 ) 633 # The synthesis methods that are not suited for the given higher-level-object 634 # will return None. File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/transpiler/passes/synthesis/high_level_synthesis.py:999, in QFTSynthesisFull.run(self, high_level_object, coupling_map, target, qubits, **options) 998 if not isinstance(high_level_object, QFTGate): --> 999 raise TranspilerError( 1000 "The synthesis plugin 'qft.full` only applies to objects of type QFTGate." 1001 ) 1003 reverse_qubits = options.get("reverse_qubits", False) TranspilerError: "The synthesis plugin 'qft.full` only applies to objects of type QFTGate." The above exception was the direct cause of the following exception: TranspilerError Traceback (most recent call last) Cell In[4], line 1 ----> 1 qiskit.transpile(qc) File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/compiler/transpiler.py:391, 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, num_processes) 363 # Edge cases require using the old model (loose constraints) instead of building a target, 364 # but we don't populate the passmanager config with loose constraints unless it's one of 365 # the known edge cases to control the execution path. 366 pm = generate_preset_pass_manager( 367 optimization_level, 368 target=target, (...) 388 dt=dt, 389 ) --> 391 out_circuits = pm.run(circuits, callback=callback, num_processes=num_processes) 393 for name, circ in zip(output_name, out_circuits): 394 circ.name = name File ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/transpiler/passmanager.py:441, 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 ~/.cache/pypoetry/virtualenvs/my-project-HV3wIkQ7-py3.10/lib/python3.10/site-packages/qiskit/transpiler/passmanager.py:466, in _replace_error..wrapper(*meth_args, **meth_kwargs) 464 return meth(*meth_args, **meth_kwargs) 465 except PassManagerError as ex: --> 466 raise TranspilerError(ex.message) from ex ```
TranspilerError: "The synthesis plugin 'qft.full` only applies to objects of type QFTGate."

If I rename the custom gate defined in the QASM file to qft_, then transpilation succeeds. It seems like the name of the custom gate is colliding with qiskit's built-in QFT gate? The error does not occur in version 1.1 of qiskit.

How can we reproduce the issue?

QASM file contents, assume file is called example.qasm

OPENQASM 2.0;
include "qelib1.inc";

gate qft q0,q1 { h q1; barrier q0,q1; crz(pi/2) q0,q1; h q0; barrier q0,q1; }
qreg q30[2];
qft q30[0],q30[1];

Python to reproduce with qiskit 1.2:

import qiskit
import qiskit.qasm2

qc = qiskit.qasm2.load("example.qasm")
qiskit.transpile(qc)

What should happen?

Transpilation should succeed and not be dependent on the name of the custom gate, if the gate name is a valid QASM identifier.

Any suggestions?

No response

alexanderivrii commented 2 months ago

Indeed, as of #11463, the name "qft" is "reserved" for objects of type QFTGate, the same way as the name "h" is "reserved" for Hadamard gates (creating a custom gate and calling it "h" would probably horrendously fail as well). Possibly this decision needs to be documented more clearly.

However, despite having an explicit qasm test for QFTGate: https://github.com/Qiskit/qiskit/blob/1962704cf120499cee5f017eb21d3e217353c4d7/test/python/circuit/library/test_qft.py#L278-L287 it seems that it still does not work as I've expected.

First, in the above example, the qasm string qc_qasm is:

OPENQASM 2.0;
include "qelib1.inc";
gate qft q0,q1,q2,q3 { h q3; cp(pi/2) q3,q2; cp(pi/4) q3,q1; cp(pi/8) q3,q0; h q2; cp(pi/2) q2,q1; cp(pi/4) q2,q0; h q1; cp(pi/2) q1,q0; h q0; swap q0,q3; swap q1,q2; }
gate qft_1563762431680 q0,q1,q2 { h q2; cp(pi/2) q2,q1; cp(pi/4) q2,q0; h q1; cp(pi/2) q1,q0; h q0; swap q0,q2; }
qreg q0[5];
qft q0[1],q0[2],q0[0],q0[4];
qft_1563762431680 q0[0],q0[1],q0[2];
h q0[0];

If the QFTGate is special, do we really need to specify its definition in the qasm string? Do we need to have a different name for the second instance of the gate (here qft_1563762431680)?

Second, the reconstructed circuit does not have any QFTGate objects on it, but rather two custom gates (called qft and qft_1563762431680). Unfortunately, the isinstance check in HighLevelSynthesis plugin still fails for the gate qft as per the original issue.

Third, we can probably remove this instance check. Then the gate qft will get synthesized using the plugin mechanism (disregarding whichever definition is given in the qasm), while the gate qft_1563762431680 will get synthesized using the custom definition. This asymmetry makes no sense.

@jakelishman, @Cryoris, any pointers how to best resolve these issues?

alexanderivrii commented 1 month ago

Update: we have discussed this problem between the developers, and (contrary to my previous comment) it is now fine to create custom gates called "qft".