Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
lochsh opened this issue Sep 10, 2024 · 2 comments · Fixed by #13181
Closed

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

lochsh opened this issue Sep 10, 2024 · 2 comments · Fixed by #13181
Assignees
Labels
bug Something isn't working

Comments

@lochsh
Copy link

lochsh commented Sep 10, 2024

Environment

  • Qiskit version: 1.2.0
  • Python version: 3.10
  • Operating system: Ubuntu 22.04

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
---------------------------------------------------------------------------
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.<locals>.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 <listcomp>(.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.<locals>.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

@lochsh lochsh added the bug Something isn't working label Sep 10, 2024
@alexanderivrii
Copy link
Contributor

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:

def test_qasm(self):
"""Test qasm for circuits with QFTGates."""
qr = QuantumRegister(5, "q0")
qc = QuantumCircuit(qr)
qc.append(QFTGate(num_qubits=4), [1, 2, 0, 4])
qc.append(QFTGate(num_qubits=3), [0, 1, 2])
qc.h(qr[0])
qc_qasm = dumps(qc)
reconstructed = QuantumCircuit.from_qasm_str(qc_qasm)
self.assertEqual(Operator(qc), Operator(reconstructed))

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
Copy link
Contributor

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".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants