C2QA / bosonic-qiskit

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

Operator functions doesn't work well when they return numpy arrays #83

Closed liu-zixiong closed 1 year ago

liu-zixiong commented 1 year ago

Hello everyone! I recently noticed this issue with ParameterizedUnitaryGate() regarding numpy.ndarray type. Although the docstrings under each function in operators.py say that ndarray operator matrices are returned, in reality ParameterizedUnitaryGate() does not accept numpy.ndarray as a valid type, and that the actual type that is returned is scipy.sparse._csc.csc_matrix.

Let me illustrate with a tester function I wrote. Within operators.py, define a function that returns a different matrix type depending on the input given. The unitary condition for matrix input into circuits is met for cases 1 - 2 by sample matrix being unitary to begin with, and cases 3- 6 via the additional 1j factor before exponentiation.

def typetester(self, flag):
        sample_list = [[0, 1], [1 ,0]]

        if flag == 0:
            return sample_list

        if flag == 1:
            return numpy.array(sample_list)

        if flag == 2:
            return scipy.sparse.csc_matrix(sample_list)

        if flag == 3:
            return scipy.sparse.linalg.expm(1j * numpy.array(sample_list))

        if flag == 4:
            sample_list = scipy.sparse.csc_matrix(sample_list)
            return scipy.sparse.linalg.expm(1j * sample_list)

        if flag == 5:
            sample_list = numpy.array(sample_list)
            sample_list = scipy.sparse.csc_matrix(sample_list)
            return scipy.sparse.linalg.expm(1j * sample_list)

        if flag == 6:
            sample_list = scipy.sparse.csc_matrix(sample_list)
            sample_list = numpy.array(sample_list)
            return scipy.sparse.linalg.expm(1j * sample_list)

When we inspect the type of the values returned, this function will output the following for its respective flag cases. 0) Python list 1) numpy.ndarray 2) scipy.sparse._csc.csc_matrix 3) numpy.ndarray 4) scipy.sparse._csc.csc_matrix 5) scipy.sparse._csc.csc_matrix 6) scipy.sparse._csc.csc_matrix

Now, define a function within circuit.py that allows us to feed an int arg into our typetester, using ParameterizedUnitaryGate. The code below is directly adapted from cv_d. The only things changed were the names of certain variables, and the label.

def cv_typetest(self, flag, qumode, duration=100, unit="ns"):
        return self.append(
            ParameterizedUnitaryGate(
                self.ops.typetester, [flag], num_qubits=len(qumode), label="typetest", duration=duration, unit=unit
            ),
            qargs=qumode,
        )

Now, we can write a circuit that uses our typetester.

from c2qa import QumodeRegister, CVCircuit, util
import warnings

warnings.filterwarnings('default')

qmr = QumodeRegister(1, 1)
circuit = CVCircuit(qmr)
circuit.cv_initialize([0, 1], qmr)

circuit.cv_typetest(<change this value>, qmr[0])
state, result = util.simulate(circuit)

When we progressively feed circuit.cv_typetest the values 0 - 6, we will find that for cases 0, 1 and 3, ParameterizedUnitaryGate will throw "UserWarning: Unable to define gate", before attempting to feed Qiskit the circuit regardless, and then Qiskit will throw a "QiskitError: "Cannot unroll the circuit to the given basis, ['ccx', 'cp', 'cswap', 'csx', 'cu'......]". The full traceback is included at the end of this post.

I suggest editing the docstring descriptions for all of the operators.py functions since it's clear that ParameterizedUnitaryGate isn't actually able to accept the numpy.ndarray type. Otherwise it may be misleading to other users.

Full Error Message for cases 0, 1, and 3.

[/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 2 in 5 [50] circuit.cv_initialize([0, 1], qmr) [52] circuit.cv_typetest(3, qmr[0]) ---> [53] state, result = util.simulate(circuit)

File [~/bosonic-qiskit/c2qa/util.py:333], in simulate(circuit, shots, add_save_statevector, conditional_state_vector, per_shot_state_vector, noise_model, noise_passes, max_parallel_threads) 331 # Transpile for simulator 332 simulator = qiskit.providers.aer.AerSimulator() --> 333 circuit_compiled = qiskit.transpile(circuit_compiled, simulator) 335 # Run and get statevector 336 result = simulator.run( 337 circuit_compiled, shots=shots, max_parallel_threads=max_parallel_threads, noise_model=noise_model 338 ).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, 383 pass_manager, 384 transpile_config["callback"], 385 transpile_config["output_name"], ... 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 typetest not found in equivalence library and no rule found to expand."

tjstavenger-pnnl commented 1 year ago

Updating the docstrings would be good. We've gone back & forth through the implementation of the operators as to what types we actually return, obviously the docstrings weren't kept up-to-date.

liu-zixiong commented 1 year ago

Thanks for the update. I manually checked through the types of all the operators, and it seems that there are actually 4 types that are returned: dia_matrix, csc_matrix, csr_matrix, bsr_matrix.

Edited the docstrings, changes have been pushed.