C2QA / bosonic-qiskit

Extension of Qiskit to support hybrid boson-qubit simulations for the NQI C2QA effort.
BSD 2-Clause "Simplified" License
46 stars 11 forks source link

On error handling in bosonic qiskit #85

Closed liu-zixiong closed 9 months ago

liu-zixiong commented 1 year ago

It seems that for functions defined in operators.py, errors produced are silent.

Let me illustrate with a tester function. Within operators.py, define a function that raises errors when given a specific argument. When given arg == 0, the operator will execute properly. When given arg == 1 or 2, the operator will attempt to fail with a warning.

Separately, define a gate in circuit.py that calls this tester function. This gate is copied from the cv_d gate, the only differences are changes to variable names.

# operators.py
import warnings
def error_raiser(self, arg):
    if arg == 1:
        print("Arg = 1")
        raise Exception("This is invisible")

    if arg == 2:
        print("Arg = 2")
        raise Warning.warn("This is also invisible.")

    if arg ==0:
        pass

    return self.eye
# circuit.py
def cv_error(self, arg, qumode, duration=100, unit="ns"):
    return self.append(
        ParameterizedUnitaryGate(
            self.ops.error_raiser, [arg], num_qubits=len(qumode), label="error", duration=duration, unit=unit
        ),
        qargs=qumode,
    )

When we execute these functions using a simple circuit, we will find that arg == 0 is fine, but arg == 1 or 2 will produce an elaborate error, but without the warnings that we wrote. This elaborate traceback is the exact same traceback as appended in Issue #83.

import c2qa
import warnings

warnings.simplefilter('default')

qmr = c2qa.QumodeRegister(1, 2)
circuit = c2qa.CVCircuit(qmr)

circuit.cv_error(arg=2, qumode=qmr) # 0, 1, 2

_, result = c2qa.util.simulate(circuit, shots=1)
---------------------------------------------------------------------------

Arg = 2
[/Users/username/bosonic-qiskit/c2qa/parameterized_unitary_gate.py:74]: UserWarning: Unable to define gate
  warnings.warn("Unable to define gate")
---------------------------------------------------------------------------
QiskitError                               Traceback (most recent call last)
[/Users/username/bosonic-qiskit/jupyter_clean.ipynb] Cell 6 in 1
     [14] circuit = c2qa.CVCircuit(qmr)
     [16] circuit.cv_error(arg=2, qumode=qmr) # 0, 1, 2
---> [18] _, result = c2qa.util.simulate(circuit, shots=1)

File [~/bosonic-qiskit/c2qa/util.py:413], in simulate(circuit, shots, add_save_statevector, conditional_state_vector, per_shot_state_vector, noise_model, noise_passes, max_parallel_threads)
    411 # Transpile for simulator
    412 simulator = qiskit.providers.aer.AerSimulator()
--> 413 circuit_compiled = qiskit.transpile(circuit_compiled, simulator)
    415 # Run and get statevector
    416 result = simulator.run(
    417     circuit_compiled, shots=shots, max_parallel_threads=max_parallel_threads, noise_model=noise_model
    418 ).result()

File [/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/qiskit/compiler/transpiler.py:381], 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)
    378     for circuit, unique_args in zip(circuits, unique_transpile_args):
    379         transpile_config, pass_manager = _combine_args(shared_args, unique_args)
    380         output_circuits.append(
--> 381             _serial_transpile_circuit(
    382                 circuit,
...
    102 unrolled_dag = UnrollCustomDefinitions(
    103     self._equiv_lib, self._basis_gates, target=self._target
    104 ).run(decomposition)

QiskitError: "Cannot unroll the circuit to the given basis, ['ccx', 'cp', 'cswap', 'csx', 'cu', 'cu1', 'cu2', 'cu3', 'cx', 'cy', 'cz', 'delay', 'diagonal', 'ecr', 'h', 'id', 'initialize', 'mcp', 'mcphase', 'mcr', 'mcrx', 'mcry', 'mcrz', 'mcswap', 'mcsx', 'mcu', 'mcu1', 'mcu2', 'mcu3', 'mcx', 'mcx_gray', 'mcy', 'mcz', 'multiplexer', 'p', 'pauli', 'r', 'roerror', 'rx', 'rxx', 'ry', 'ryy', 'rz', 'rzx', 'rzz', 's', 'sdg', 'swap', 'sx', 'sxdg', 't', 'tdg', 'u', 'u1', 'u2', 'u3', 'unitary', 'x', 'y', 'z', 'break_loop', 'continue_loop', 'for_loop', 'if_else', 'kraus', 'qerror_loc', 'quantum_channel', 'roerror', 'save_amplitudes', 'save_amplitudes_sq', 'save_clifford', 'save_density_matrix', 'save_expval', 'save_expval_var', 'save_matrix_product_state', 'save_probabilities', 'save_probabilities_dict', 'save_stabilizer', 'save_state', 'save_statevector', 'save_statevector_dict', 'save_superop', 'save_unitary', 'set_density_matrix', 'set_matrix_product_state', 'set_stabilizer', 'set_statevector', 'set_superop', 'set_unitary', 'superop', 'while_loop']. Instruction error not found in equivalence library and no rule found to expand."
Output is truncated. View as a [scrollable element] or open in a [text editor]. Adjust cell output [settings]...

Since functions in operators.py fail silently, debugging or developing new operators is difficult. Preferably there should be a try-except block somewhere that tells you if the operator did not return a valid value.

Additionally, since bosonic-qiskit is a wrapper for qiskit, I suggest that errors arising from bosonic-qiskit should be caught and stopped before they are fed into qiskit. For example, in parameterized_unitary_gate, under the _define() method, there is a warning for "Unable to define gate". I suggest changing the warning it to an exception. This is so that the user is not mislead by the traceback into thinking that the error occurs within qiskit.

# pararameterized_unitary_gate.py
def _define(self):
    try:
        mat = self.to_matrix()
        q = QuantumRegister(self.num_qubits)
        qc = QuantumCircuit(q, name=self.name)
        rules = [
            (UnitaryGate(mat, self.label), [i for i in q], []),
        ]
        for instr, qargs, cargs in rules:
            qc._append(instr, qargs, cargs)

        self.definition = qc
    except:
        warnings.warn("Unable to define gate") # <<<--- I suggest changing this to raise Exception("Unable to define gate")
        self.definition = None

Another example would be something like cv_measure(), which currently does not have an error message for if the number of classical bits passed in is lesser than the total number of qubits in the qumode we are trying to measure.

import qiskit
import c2qa

qmr = c2qa.QumodeRegister(1, 2)
creg = qiskit.ClassicalRegister(1)

circuit = c2qa.CVCircuit(qmr, creg)
circuit.cv_measure(qmr, creg) ### this will return an error

---------------------------------------------------------------------------
[/Users/username/bosonic-qiskit/c2qa/circuit.py:782]: UserWarning: Probe qubits not in use, set probe_measure to True for measure support.
  warnings.warn(
---------------------------------------------------------------------------
CircuitError                              Traceback (most recent call last)
[/Users/username/bosonic-qiskit/jupyter_clean.ipynb] Cell 6 in 1
     [13] creg = qiskit.ClassicalRegister(1)
     [15] circuit = c2qa.CVCircuit(qmr, creg)
---> [16] circuit.cv_measure(qmr, creg)

File [~/bosonic-qiskit/c2qa/circuit.py:802], in CVCircuit.cv_measure(self, qubit_qumode_list, cbit_list)
    800     self.measure(flat_list, cbit_list[0:len(flat_list)])
    801 else:
--> 802     self.measure(flat_list, cbit_list)

File [/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/qiskit/circuit/quantumcircuit.py:2353], in QuantumCircuit.measure(self, qubit, cbit)
   2280 def measure(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet:
   2281     r"""Measure a quantum bit (``qubit``) in the Z basis into a classical bit (``cbit``).
   2282 
   2283     When a quantum state is measured, a qubit is projected in the computational (Pauli Z) basis
   (...)
   2351 
   2352     """
-> 2353     return self.append(Measure(), [qubit], [cbit])

File [/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/qiskit/circuit/quantumcircuit.py:1298], in QuantumCircuit.append(self, instruction, qargs, cargs)
   1296 instructions = InstructionSet(resource_requester=requester)
   1297 if isinstance(operation, Instruction):
...
     37         yield qarg, [each_carg]
     38 else:
---> 39     raise CircuitError("register size error")

CircuitError: 'register size error'
Output is truncated. View as a [scrollable element] or open in a [text editor]. Adjust cell output [settings]...
tjstavenger-pnnl commented 1 year ago

We're swallowing the exception and writing a warning at https://github.com/C2QA/bosonic-qiskit/blob/main/c2qa/parameterized_unitary_gate.py#L61-L75 in order to fix this problem: https://github.com/C2QA/bosonic-qiskit/issues/57. We could change the warning, but making it an exception will break the serialization issue again.

The whole ParameterizedUnitaryGate and its _define() is a bit of a kludge in order to let us parameterize our gates that are defined as unitaries. Qiskit on its own doesn't let you parameterize UnitaryGate, so this is our current workaround that has worked in some cases and led to issues in others.

tjstavenger-pnnl commented 1 year ago

I would think we could easily add a validation step & raise an exception for the cv_measure function when the len(qubit_qumode_list) != len(cbit_list)

tjstavenger-pnnl commented 9 months ago

I updated the warning message to be clearer. The implementation of cv_measure has since been updated to test the size of the cbit_list.