From d6e4d52f81715e91e95784326df46f91b06ff9e0 Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Thu, 8 Sep 2022 10:49:40 +0200 Subject: [PATCH] Fix bug for circuit with selected measurement result (#167) * Fix bug for circuit with selected measurement result The parameter measure_results was not correctly processed in QubitCircuit.run because the Simulator is initialized twice. This commit removes the parameters `state`, `cbits` and `measure_results` from the `__init__` of CircuitSimulator and only initializes the simulator before it is run. The CircuitSimulator class should only include information about the circuit while the information about the state is only provided when running the simulation. --- doc/source/qip-simulator.rst | 4 +- src/qutip_qip/circuit/circuit.py | 59 +++++++---------------- src/qutip_qip/circuit/circuitsimulator.py | 27 +++++------ tests/test_circuit.py | 17 +++++++ 4 files changed, 47 insertions(+), 60 deletions(-) diff --git a/doc/source/qip-simulator.rst b/doc/source/qip-simulator.rst index 9330dc93..92c4f373 100644 --- a/doc/source/qip-simulator.rst +++ b/doc/source/qip-simulator.rst @@ -156,8 +156,8 @@ and computation proceeds. To demonstrate, we continue with our previous circuit: .. testcode:: from qutip_qip.circuit import CircuitSimulator - - sim = CircuitSimulator(qc, state=zero_state) + sim = CircuitSimulator(qc) + sim.initialize(zero_state) This initializes the simulator object and carries out any pre-computation required. There are two ways to carry out state evolution with the simulator. diff --git a/src/qutip_qip/circuit/circuit.py b/src/qutip_qip/circuit/circuit.py index 61cca858..9ef5af7e 100644 --- a/src/qutip_qip/circuit/circuit.py +++ b/src/qutip_qip/circuit/circuit.py @@ -490,30 +490,19 @@ def run( final_state : Qobj output state of the circuit run. """ - if state.isket: - sim = CircuitSimulator( - self, - state, - cbits, - U_list, - measure_results, - "state_vector_simulator", - precompute_unitary, - ) + mode = "state_vector_simulator" elif state.isoper: - sim = CircuitSimulator( - self, - state, - cbits, - U_list, - measure_results, - "density_matrix_simulator", - precompute_unitary, - ) + mode = "density_matrix_simulator" else: raise TypeError("State is not a ket or a density matrix.") - return sim.run(state, cbits).get_final_states(0) + sim = CircuitSimulator( + self, + U_list, + mode, + precompute_unitary, + ) + return sim.run(state, cbits, measure_results).get_final_states(0) def run_statistics( self, state, U_list=None, cbits=None, precompute_unitary=False @@ -530,11 +519,6 @@ def run_statistics( initialization of the classical bits. U_list: list of Qobj, optional list of predefined unitaries corresponding to circuit. - measure_results : tuple of ints, optional - optional specification of each measurement result to enable - post-selection. If specified, the measurement results are - set to the tuple of bits (sequentially) instead of being - chosen at random. precompute_unitary: Boolean, optional Specify if computation is done by pre-computing and aggregating gate unitaries. Possibly a faster method in the case of @@ -546,27 +530,18 @@ def run_statistics( Return a CircuitResult object containing output states and and their probabilities. """ - if state.isket: - sim = CircuitSimulator( - self, - state, - cbits, - U_list, - mode="state_vector_simulator", - precompute_unitary=precompute_unitary, - ) + mode = "state_vector_simulator" elif state.isoper: - sim = CircuitSimulator( - self, - state, - cbits, - U_list, - mode="density_matrix_simulator", - precompute_unitary=precompute_unitary, - ) + mode = "density_matrix_simulator" else: raise TypeError("State is not a ket or a density matrix.") + sim = CircuitSimulator( + self, + U_list, + mode, + precompute_unitary, + ) return sim.run_statistics(state, cbits) def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]): diff --git a/src/qutip_qip/circuit/circuitsimulator.py b/src/qutip_qip/circuit/circuitsimulator.py index d0b2daac..d756fcd8 100644 --- a/src/qutip_qip/circuit/circuitsimulator.py +++ b/src/qutip_qip/circuit/circuitsimulator.py @@ -11,6 +11,7 @@ gate_sequence_product, ) from qutip import basis, ket2dm, Qobj, tensor +import warnings __all__ = ["CircuitSimulator", "CircuitResult"] @@ -253,12 +254,12 @@ class CircuitSimulator: def __init__( self, qc, - state=None, - cbits=None, U_list=None, - measure_results=None, mode="state_vector_simulator", precompute_unitary=False, + state=None, + cbits=None, + measure_results=None, ): """ Simulate state evolution for Quantum Circuits. @@ -268,21 +269,9 @@ def __init__( qc : :class:`.QubitCircuit` Quantum Circuit to be simulated. - state: ket or oper - ket or density matrix - - cbits: list of int, optional - initial value of classical bits - U_list: list of Qobj, optional list of predefined unitaries corresponding to circuit. - measure_results : tuple of ints, optional - optional specification of each measurement result to enable - post-selection. If specified, the measurement results are - set to the tuple of bits (sequentially) instead of being - chosen at random. - mode: string, optional Specify if input state (and therefore computation) is in state-vector mode or in density matrix mode. @@ -321,7 +310,13 @@ def __init__( else: self._process_ops() - self.initialize(state, cbits, measure_results) + if any(p is not None for p in (state, cbits, measure_results)): + warnings.warn( + "Initializing the quantum state, cbits and measure_results " + "when initializing the simulator is deprecated. " + "The inputs are ignored. " + "They should, instead, be provided when running the simulation." + ) def _process_ops(self): """ diff --git a/tests/test_circuit.py b/tests/test_circuit.py index 713a2cc0..157e581d 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -598,6 +598,23 @@ def test_measurement_circuit(self): else: assert simulator.cbits[0] != simulator.cbits[1] + def test_circuit_with_selected_measurement_result(self): + qc = QubitCircuit(N=1, num_cbits=1) + qc.add_gate("SNOT", targets=0) + qc.add_measurement("M0", targets=0, classical_store=0) + + # We reset the random seed so that + # if we don's select the measurement result, + # the two circuit should return the same value. + np.random.seed(0) + final_state = qc.run(qp.basis(2, 0), cbits=[0], measure_results=[0]) + fid = pytest.approx(qp.fidelity(final_state, basis(2, 0))) + assert fid == 1.0 + np.random.seed(0) + final_state = qc.run(qp.basis(2, 0), cbits=[0], measure_results=[1]) + fid = pytest.approx(qp.fidelity(final_state, basis(2, 1))) + assert fid == 1.0 + def test_gate_product(self): filename = "qft.qasm"