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.08k stars 2.34k forks source link

sympy2symengine error in circuit transpilation #13161

Closed ProThicc closed 5 days ago

ProThicc commented 1 week ago

Environment

What is happening?

I am trying to transpile a circuit with CCU and CU gates with different rotation angles. In the qiskit 11.4 version the transpiler was able to optimize and transpile the circuit with the basis gate set ['sx','cz','x','rz','barrier', 'save_statevector', 'delay'], But for the current version of qiskit, the transpiler throws an error for the particular gate set.

SympifyError: sympy2symengine: Cannot convert 'None' (of type <class 'NoneType'>) to a symengine type.

How can we reproduce the issue?

from qiskit_aer import AerSimulator
from qiskit import transpile, QuantumCircuit
from qiskit.circuit.library import UGate
from qiskit.circuit.add_control import add_control

qc = QuantumCircuit(4)
qca_U = add_control(UGate(-1.945632646294958, 1.5120405041931422, 2.4393357222229826),num_ctrl_qubits=2, ctrl_state='00', label = 'qca_u')
qca_U_red = add_control(UGate(-1.945632646294958, 1.5120405041931422, 2.4393357222229826),num_ctrl_qubits=1, ctrl_state='0', label = 'qca_u_red')
qc.append(qca_U, [0,2,1])
qc.append(qca_U_red, [0,1])
sim = AerSimulator()
# qcd = transpile(qc, sim, optimization_level=3,basis_gates=['ccx','ccz','cu','cx','u','mcphase','x', 'cx', 'save_statevector','delay', 'barrier'])
qcd = transpile(qc, sim, optimization_level=3, basis_gates=['barrier', 'save_statevector', 'delay','sx','cz','x','rz'])
# qcd = transpile(qc, sim, optimization_level=3)

What should happen?

{
    "name": "SympifyError",
    "message": "sympy2symengine: Cannot convert 'None' (of type <class 'NoneType'>) to a symengine type.",
    "stack": "---------------------------------------------------------------------------
SympifyError                              Traceback (most recent call last)
Cell In[23], line 13
     11 sim = AerSimulator()
     12 # qcd = transpile(qc, sim, optimization_level=3,basis_gates=['ccx','ccz','cu','cx','u','mcphase','x', 'cx', 'save_statevector','delay', 'barrier'])
---> 13 qcd = transpile(qc, sim, optimization_level=3, basis_gates=['barrier', 'save_statevector', 'delay','sx','cz','x','rz'])
     14 # qcd = transpile(qc, sim, optimization_level=3)

File ~/anaconda3/envs/qca/lib/python3.11/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 ~/anaconda3/envs/qca/lib/python3.11/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 ~/anaconda3/envs/qca/lib/python3.11/site-packages/qiskit/transpiler/passmanager.py:464, 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 ~/anaconda3/envs/qca/lib/python3.11/site-packages/qiskit/transpiler/passmanager.py:226, 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 ~/anaconda3/envs/qca/lib/python3.11/site-packages/qiskit/passmanager/passmanager.py: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 ~/anaconda3/envs/qca/lib/python3.11/site-packages/qiskit/passmanager/passmanager.py:232, in <listcomp>(.0)
    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 ~/anaconda3/envs/qca/lib/python3.11/site-packages/qiskit/passmanager/passmanager.py:292, 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 ~/anaconda3/envs/qca/lib/python3.11/site-packages/qiskit/passmanager/base_tasks.py:218, 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 ~/anaconda3/envs/qca/lib/python3.11/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:
    202         if isinstance(new_dag, DAGCircuit):
    203             # Copy calibration data from the original program

File ~/anaconda3/envs/qca/lib/python3.11/site-packages/qiskit/passmanager/base_tasks.py:98, 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 ~/anaconda3/envs/qca/lib/python3.11/site-packages/qiskit/transpiler/passes/basis/basis_translator.py:237, in BasisTranslator.run(self, dag)
    234 # Compose found path into a set of instruction substitution rules.
    236 compose_start_time = time.time()
--> 237 instr_map = _compose_transforms(basis_transforms, source_basis, dag)
    238 extra_instr_map = {
    239     qarg: _compose_transforms(transforms, qargs_local_source_basis[qarg], dag)
    240     for qarg, transforms in qarg_local_basis_transforms.items()
    241 }
    243 compose_end_time = time.time()

File ~/anaconda3/envs/qca/lib/python3.11/site-packages/qiskit/transpiler/passes/basis/basis_translator.py:669, in _compose_transforms(basis_transforms, source_basis, source_dag)
    660     logger.debug(
    661         \"Updating transform for mapped instr %s %s from \
%s\",
    662         mapped_instr_name,
    663         dag_params,
    664         dag_to_circuit(dag, copy_operations=False),
    665     )
    667 for node in doomed_nodes:
--> 669     replacement = equiv.assign_parameters(dict(zip_longest(equiv_params, node.params)))
    671     replacement_dag = circuit_to_dag(replacement)
    673     dag.substitute_node_with_dag(node, replacement_dag)

File ~/anaconda3/envs/qca/lib/python3.11/site-packages/qiskit/circuit/quantumcircuit.py:4342, in QuantumCircuit.assign_parameters(self, parameters, inplace, flat_input, strict)
   4337         raise CircuitError(
   4338             f\"Cannot bind parameters ({', '.join(str(x) for x in extras)}) not present in\"
   4339             \" the circuit.\"
   4340         )
   4341     parameter_binds = _ParameterBindsDict(raw_mapping, our_parameters)
-> 4342     target._data.assign_parameters_mapping(parameter_binds)
   4343 else:
   4344     parameter_binds = _ParameterBindsSequence(target._data.parameters, parameters)

File ~/anaconda3/envs/qca/lib/python3.11/site-packages/qiskit/circuit/parameter.py:103, in Parameter.assign(self, parameter, value)
    100     return value
    101 # This is the `super().bind` case, where we're required to return a `ParameterExpression`,
    102 # so we need to lift the given value to a symbolic expression.
--> 103 return ParameterExpression({}, symengine.sympify(value))

File symengine_wrapper.pyx:534, in symengine.lib.symengine_wrapper.sympify()

File symengine_wrapper.pyx:579, in symengine.lib.symengine_wrapper._sympify()

File symengine_wrapper.pyx:500, in symengine.lib.symengine_wrapper.sympy2symengine()

SympifyError: sympy2symengine: Cannot convert 'None' (of type <class 'NoneType'>) to a symengine type."
}

Any suggestions?

Noticed this when there is a CU gate in the circuit (It works well when there are CCU gates). Also for the basis gate set ['ccx','ccz','cu','cx','u','mcphase','x', 'cx', 'save_statevector','delay', 'barrier'] it transpiles properly

jakelishman commented 6 days ago

add_control is an internal method with several undocumented assumptions, and not part of the public interface. It's dangerous to call on arbitrary gates, and is the problem here (well, the real real problem is data-model awkwardness between Instruction.name and the Python object type, but there are mitigations for that in Qiskit).

Replace use of add_control with the proper Gate.control (so UGate(...).control(1, ctrl_state='0') or the like), and hopefully you'll find it works better.

1ucian0 commented 5 days ago

Indeed. The following code works:

from qiskit_aer import AerSimulator
from qiskit import transpile, QuantumCircuit
from qiskit.circuit.library import UGate

qc = QuantumCircuit(4)
qca_U = UGate(-1.945632646294958, 1.5120405041931422, 2.4393357222229826).control(num_ctrl_qubits=2, ctrl_state='00', label = 'qca_u')
qca_U_red = UGate(-1.945632646294958, 1.5120405041931422, 2.4393357222229826).control(num_ctrl_qubits=1, ctrl_state='0', label = 'qca_u_red')
qc.append(qca_U, [0,2,1])
qc.append(qca_U_red, [0,1])
sim = AerSimulator()
qcd = transpile(qc, sim, optimization_level=3, basis_gates=['barrier', 'save_statevector', 'delay','sx','cz','x','rz'])