diff --git a/doc/requirements.txt b/doc/requirements.txt index 14a1a32fb..b930b301f 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -9,3 +9,4 @@ numpydoc==1.4.0 matplotlib==3.5.2 docutils==0.17.1 sphinxcontrib-bibtex==2.4.2 +qiskit==0.37.2 \ No newline at end of file diff --git a/doc/source/apidoc.rst b/doc/source/apidoc.rst index 99c0caab2..aac3b0cb0 100644 --- a/doc/source/apidoc.rst +++ b/doc/source/apidoc.rst @@ -33,3 +33,16 @@ Simulation based on the master equation. qutip_qip.compiler qutip_qip.pulse qutip_qip.noise + +Qiskit Circuit Simulation +-------------------------- +Simulation of qiskit circuits based on qutip_qip backends. + +.. autosummary:: + :toctree: apidoc/ + :template: autosummary/module.rst + + qutip_qip.qiskit.backend + qutip_qip.qiskit.converter + qutip_qip.qiskit.provider + qutip_qip.qiskit.job \ No newline at end of file diff --git a/doc/source/apidoc/qutip_qip.qiskit.rst b/doc/source/apidoc/qutip_qip.qiskit.rst new file mode 100644 index 000000000..0b29740f4 --- /dev/null +++ b/doc/source/apidoc/qutip_qip.qiskit.rst @@ -0,0 +1,56 @@ +:orphan: + +qutip\_qip.qiskit.backend +=========================== + +.. automodule:: qutip_qip.qiskit.backend + :members: + :show-inheritance: + + .. rubric:: Classes + + .. autosummary:: + + QiskitSimulatorBase + QiskitCircuitSimulator + QiskitPulseSimulator + +qutip\_qip.qiskit.converter +============================== + +.. automodule:: qutip_qip.qiskit.converter + :members: + :show-inheritance: + + .. rubric:: Methods + + .. autosummary:: + + convert_qiskit_circuit + +qutip\_qip.qiskit.provider +=========================== + +.. automodule:: qutip_qip.qiskit.provider + :members: + :show-inheritance: + + .. rubric:: Classes + + .. autosummary:: + + Provider + +qutip\_qip.qiskit.job +=========================== + +.. automodule:: qutip_qip.qiskit.job + :members: + :show-inheritance: + + .. rubric:: Classes + + .. autosummary:: + + Job + diff --git a/doc/source/figures/qiskit-gate-level-plot.png b/doc/source/figures/qiskit-gate-level-plot.png new file mode 100644 index 000000000..8590e373a Binary files /dev/null and b/doc/source/figures/qiskit-gate-level-plot.png differ diff --git a/doc/source/figures/qiskit-pulse-level-plot.png b/doc/source/figures/qiskit-pulse-level-plot.png new file mode 100644 index 000000000..f62bd46aa Binary files /dev/null and b/doc/source/figures/qiskit-pulse-level-plot.png differ diff --git a/doc/source/index.rst b/doc/source/index.rst index cce967fe7..ea653ab20 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -23,6 +23,7 @@ qutip-qip |version|: QuTiP quantum information processing qip-simulator.rst qip-processor.rst qip-vqa.rst + qip-qiskit.rst .. toctree:: :maxdepth: 2 diff --git a/doc/source/qip-qiskit.rst b/doc/source/qip-qiskit.rst new file mode 100644 index 000000000..612ce5810 --- /dev/null +++ b/doc/source/qip-qiskit.rst @@ -0,0 +1,126 @@ +.. _qip_qiskit: + +********************************** +`qutip-qip` as a Qiskit backend +********************************** + +This submodule was implemented by `Shreyas Pradhan `_ as part of Google Summer of Code 2022. + +Overview +=============== + +This submodule provides an interface to simulate circuits made in qiskit. + +Gate-level simulation on qiskit circuits is possible with :class:`.QiskitCircuitSimulator`. Pulse-level simulation is possible with :class:`.QiskitPulseSimulator` which supports simulation using the :class:`.LinearSpinChain`, :class:`.CircularSpinChain` and :class:`.DispersiveCavityQED` pulse processors. + +Running a qiskit circuit with qutip_qip +========================================== + +After constructing a circuit in qiskit, either of the qutip_qip based backends (:class:`.QiskitCircuitSimulator` and :class:`.QiskitPulseSimulator`) can be used to run that circuit. + +Example +-------- + +Let's try constructing and simulating a qiskit circuit. + +We define a simple circuit as follows: + +.. code-block:: + + from qiskit import QuantumCircuit + + circ = QuantumCircuit(2,2) + circ.h(0) + circ.h(1) + + circ.measure(0,0) + circ.measure(1,1) + +Let's run this on the :class:`.QiskitCircuitSimulator` backend: + +.. code-block:: + + from qutip_qip.qiskit.provider import QiskitCircuitSimulator + + backend = QiskitCircuitSimulator() + job = backend.run(circ) + result = job.result() + +.. code-block:: + + from qiskit.visualization import plot_histogram + >>> plot_histogram(result.get_counts()) + +.. image:: /figures/qiskit-gate-level-plot.png + :alt: probabilities plot + +Now, let's run the same circuit on :class:`.QiskitPulseSimulator`. + +While using a pulse processor, we define the circuit without measurements: + +.. code-block:: + + pulse_circ = QuantumCircuit(2,2) + pulse_circ.h(0) + pulse_circ.h(1) + +To use the :class:`.QiskitPulseSimulator` backend, we need to define the processor on which we want to run the circuit. This includes defining the pulse processor model with all the required parameters including noise. + +.. code-block:: + + from qutip_qip.device import LinearSpinChain + processor = LinearSpinChain(num_qubits=2) + +Now that we defined our processor (:class:`.LinearSpinChain` in this case), we can use it to perform the simulation: + +.. code-block:: + + from qutip_qip.qiskit.provider import QiskitPulseSimulator + + pulse_backend = QiskitPulseSimulator(processor) + pulse_job = pulse_backend.run(pulse_circ) + pulse_result = pulse_job.result() + +.. code-block:: + + >>> plot_histogram(pulse_result.get_counts()) + +.. image:: /figures/qiskit-pulse-level-plot.png + :alt: probabilities plot + +Configurable Options +======================== + +Qiskit's interface allows us to provide some options like ``shots`` while running a circuit on a backend. We also have provided some options for the qutip_qip backends. + +``shots`` +------------- +(Available for both: :class:`.QiskitCircuitSimulator` and :class:`.QiskitPulseSimulator`) +``shots`` is the number of times measurements are sampled from the simulation result. By default it is set to ``1024``. + +``allow_custom_gate`` +----------------------- +(Only available for :class:`.QiskitCircuitSimulator`) +``allow_custom_gate``, when set to ``False``, does not allowing simulating circuits that have user-defined gates; it will throw an error in that case. By default, it is set to ``True``, in which case, the backend will simulate a user-defined gate by computing its unitary matrix. + + .. note:: + + The pulse backend does not allow simulation with user-defined gates. + +An example demonstrating configuring options: + +.. code-block:: + + backend = QiskitCircuitSimulator() + job = backend.run(circ, shots=3000) + result = job.result() + +We provided the value of shots explicitly, hence our options for the simulation are set as: ``shots=3000`` and ``allow_custom_gate=True``. + +Another example: + +.. code-block:: + + backend = QiskitCircuitSimulator() + job = backend.run(circ, shots=3000, allow_custom_gate=False) + result = job.result() \ No newline at end of file diff --git a/src/qutip_qip/device/cavityqed.py b/src/qutip_qip/device/cavityqed.py index 4614d451e..ebec42f7c 100644 --- a/src/qutip_qip/device/cavityqed.py +++ b/src/qutip_qip/device/cavityqed.py @@ -88,9 +88,7 @@ def __init__( self, num_qubits, num_levels=10, correct_global_phase=True, **params ): model = CavityQEDModel( - num_qubits=num_qubits, - num_levels=num_levels, - **params, + num_qubits=num_qubits, num_levels=num_levels, **params ) super(DispersiveCavityQED, self).__init__( model=model, correct_global_phase=correct_global_phase @@ -167,6 +165,48 @@ def load_circuit(self, qc, schedule_mode="ASAP", compiler=None): self.global_phase = compiler.global_phase return tlist, coeff + def generate_init_processor_state( + self, init_circuit_state: Qobj = None + ) -> Qobj: + """ + Generate the initial state with the dimensions of the DispersiveCavityQED processor. + + Parameters + ---------- + init_circuit_state : :class:`qutip.Qobj` + Initial state provided with the dimensions of the circuit. + + Returns + ------- + :class:`qutip.Qobj` + Return the initial state with the dimensions of the DispersiveCavityQED processor model. + If initial_circuit_state was not provided, return the zero state. + """ + if init_circuit_state is None: + return basis( + [self.num_levels] + [2] * self.num_qubits, + [0] + [0] * self.num_qubits, + ) + return tensor(basis(self.num_levels, 0), init_circuit_state) + + def get_final_circuit_state(self, final_processor_state: Qobj) -> Qobj: + """ + Truncate the final processor state to get rid of the cavity subsystem. + + Parameters + ---------- + final_processor_state : :class:`qutip.Qobj` + State provided with the dimensions of the DispersiveCavityQED processor model. + + Returns + ------- + :class:`qutip.Qobj` + Return the truncated final state with the dimensions of the circuit. + """ + return final_processor_state.ptrace( + range(1, len(final_processor_state.dims[0])) + ) + class CavityQEDModel(Model): """ diff --git a/src/qutip_qip/device/modelprocessor.py b/src/qutip_qip/device/modelprocessor.py index e7bf340b9..a500e7a44 100644 --- a/src/qutip_qip/device/modelprocessor.py +++ b/src/qutip_qip/device/modelprocessor.py @@ -3,7 +3,7 @@ import numpy as np -from qutip import Qobj, QobjEvo, tensor, mesolve +from qutip import Qobj, QobjEvo, tensor, mesolve, basis from ..operations import globalphase from ..circuit import QubitCircuit from .processor import Processor @@ -243,6 +243,45 @@ def load_circuit(self, qc, schedule_mode="ASAP", compiler=None): self.set_tlist(tlist) return tlist, coeffs + def generate_init_processor_state( + self, init_circuit_state: Qobj = None + ) -> Qobj: + """ + Generate the initial state with the dimensions of the processor. + + Parameters + ---------- + init_circuit_state : :class:`qutip.Qobj` + Initial state provided with the dimensions of the circuit. + + Returns + ------- + :class:`qutip.Qobj` + Return the initial state with the dimensions + of the processor model. If initial_circuit_state + was not provided, return the zero state. + """ + if init_circuit_state is None: + return basis([2] * self.num_qubits, [0] * self.num_qubits) + return init_circuit_state + + def get_final_circuit_state(self, final_processor_state: Qobj) -> Qobj: + """ + Convert the state with the dimensions of the processor model to + a state with the dimensions of the circuit. + + Parameters + ---------- + final_processor_state : :class:`qutip.Qobj` + State provided with the dimensions of the processor model. + + Returns + ------- + :class:`qutip.Qobj` + Return the final state with the dimensions of the circuit. + """ + return final_processor_state + def _to_array(params, num_qubits): """ diff --git a/src/qutip_qip/qiskit/backend.py b/src/qutip_qip/qiskit/backend.py index aa901875e..90d11911a 100644 --- a/src/qutip_qip/qiskit/backend.py +++ b/src/qutip_qip/qiskit/backend.py @@ -1,25 +1,24 @@ -from email.header import Header -from qutip import tensor, basis +"""Backends for simulating qiskit circuits.""" + import numpy as np import uuid import random from collections import Counter -from qutip_qip import circuit -from qutip_qip.operations.gateclass import Z +import qutip +import qiskit +from qutip import basis from qutip_qip.circuit import QubitCircuit from qutip_qip.circuit.circuitsimulator import CircuitResult +from qutip_qip.device import Processor from .job import Job from .converter import convert_qiskit_circuit from qiskit.providers import BackendV1, Options -from qiskit.providers.models import ( - BackendConfiguration, - QasmBackendConfiguration, -) +from qiskit.providers.models import QasmBackendConfiguration from qiskit.result import Result, Counts from qiskit.result.models import ExperimentResult, ExperimentResultData -from qiskit.quantum_info import Statevector +from qiskit.quantum_info import Statevector, DensityMatrix from qiskit.circuit import QuantumCircuit from qiskit.qobj import QobjExperimentHeader @@ -30,7 +29,14 @@ class QiskitSimulatorBase(BackendV1): """ def __init__(self, configuration=None, provider=None, **fields): + + if configuration is None: + configuration = QasmBackendConfiguration.from_dict( + self._DEFAULT_CONFIGURATION + ) + super().__init__(configuration=configuration, provider=provider) + self.options.set_validator( "shots", (1, self.configuration().max_shots) ) @@ -50,7 +56,7 @@ def run(self, qiskit_circuit: QuantumCircuit, **run_options) -> Job: Valid options are: shots : int - Number of times to perform the simulation + Number of times to sample the results. allow_custom_gate: bool Allow conversion of circuit using unitary matrices for custom gates. @@ -60,18 +66,31 @@ def run(self, qiskit_circuit: QuantumCircuit, **run_options) -> Job: qutip_qip.qiskit.job.Job Job that stores results and execution data """ - # configure the options - self.set_options( - shots=run_options["shots"] - if "shots" in run_options - else self._default_options().shots, - allow_custom_gate=run_options["allow_custom_gate"] - if "allow_custom_gate" in run_options - else self._default_options().allow_custom_gate, - ) - qutip_circ = convert_qiskit_circuit( - qiskit_circuit, allow_custom_gate=self.options.allow_custom_gate - ) + if isinstance(self, QiskitCircuitSimulator): + # configure the options + self.set_options( + shots=run_options["shots"] + if "shots" in run_options + else self._default_options().shots, + allow_custom_gate=run_options["allow_custom_gate"] + if "allow_custom_gate" in run_options + else self._default_options().allow_custom_gate, + ) + qutip_circ = convert_qiskit_circuit( + qiskit_circuit, + allow_custom_gate=self.options.allow_custom_gate, + ) + else: + # configure the options + self.set_options( + shots=run_options["shots"] + if "shots" in run_options + else self._default_options().shots + ) + qutip_circ = convert_qiskit_circuit( + qiskit_circuit, allow_custom_gate=False + ) + job_id = str(uuid.uuid4()) job = Job( @@ -81,18 +100,52 @@ def run(self, qiskit_circuit: QuantumCircuit, **run_options) -> Job: ) return job + def _sample_shots(self, count_probs: dict) -> Counts: + """ + Sample measurements from a given probability distribution + + Parameters + ---------- + count_probs: dict + Probability distribution corresponding + to different classical outputs. + + Returns + ------- + qiskit.result.Counts + Returns the Counts object sampled according to + the given probabilities and configured shots. + """ + shots = self.options.shots + samples = random.choices( + list(count_probs.keys()), list(count_probs.values()), k=shots + ) + return Counts(Counter(samples)) + + def _get_probabilities(self, state): + """ + Given a state, return an array of corresponding probabilities. + """ + if state.type == "oper": + # diagonal elements of a density matrix are + # the probabilities + return state.diag() + + # squares of coefficients are the probabilities + # for a ket vector + return np.array([np.abs(coef) ** 2 for coef in state]) + class QiskitCircuitSimulator(QiskitSimulatorBase): """ Qiskit backend dealing with operator-level circuit simulation using qutip_qip's CircuitSimulator. - - """ MAX_QUBITS_MEMORY = 10 + BACKEND_NAME = "circuit_simulator" _DEFAULT_CONFIGURATION = { - "backend_name": "circuit_simulator", + "backend_name": BACKEND_NAME, "backend_version": "0.1", "n_qubits": MAX_QUBITS_MEMORY, "url": "https://github.com/qutip/qutip-qip", @@ -110,43 +163,16 @@ class QiskitCircuitSimulator(QiskitSimulatorBase): def __init__(self, configuration=None, provider=None, **fields): - if configuration is None: - configuration = QasmBackendConfiguration.from_dict( - QiskitCircuitSimulator._DEFAULT_CONFIGURATION - ) - super().__init__( configuration=configuration, provider=provider, **fields ) - def _sample_shots(self, count_probs: dict) -> Counts: - """ - Sample measurements from a given probability distribution - - Parameters - ---------- - count_probs: dict - Probability distribution corresponding - to different classical outputs. - - Returns - ------- - qiskit.result.Counts - Returns the Counts object sampled according to - the given probabilities and configured shots. - """ - shots = self.options.shots - samples = random.choices( - list(count_probs.keys()), list(count_probs.values()), k=shots - ) - return Counts(Counter(samples)) - def _parse_results( self, statistics: CircuitResult, job_id: str, qutip_circuit: QubitCircuit, - ) -> Result: + ) -> qiskit.result.Result: """ Returns a parsed object of type qiskit.result.Result for the CircuitSimulator @@ -246,6 +272,171 @@ def _run_job(self, job_id: str, qutip_circuit: QubitCircuit) -> Result: @classmethod def _default_options(cls): """ - Default options for the backend. To be updated. + Default options for the backend. + + Options + ------- + shots : int + Number of times to sample the results. + + allow_custom_gate : bool + Allow conversion of circuit using unitary matrices + for custom gates. """ return Options(shots=1024, allow_custom_gate=True) + + +class QiskitPulseSimulator(QiskitSimulatorBase): + """ + Qiskit backend dealing with pulse-level simulation. + + Parameters + ---------- + processor : qutip_qip.device.Processor + The processor model to be used for simulation. Processor object to + be provided after initialising it with the required parameters. + + Attributes + ---------- + processor : qutip_qip.device.Processor + The processor model to be used for simulation. + """ + + processor = None + MAX_QUBITS_MEMORY = 10 + BACKEND_NAME = "pulse_simulator" + _DEFAULT_CONFIGURATION = { + "backend_name": BACKEND_NAME, + "backend_version": "0.1", + "n_qubits": MAX_QUBITS_MEMORY, + "url": "https://github.com/qutip/qutip-qip", + "simulator": True, + "local": True, + "conditional": False, + "open_pulse": False, + "memory": False, + "max_shots": int(1e6), + "coupling_map": None, + "description": "A qutip-qip based pulse-level simulator based on the open system solver.", + "basis_gates": [], + "gates": [], + } + + def __init__( + self, processor: Processor, configuration=None, provider=None, **fields + ): + + self.processor = processor + super().__init__( + configuration=configuration, provider=provider, **fields + ) + + def _parse_results( + self, final_state: qutip.Qobj, job_id: str, qutip_circuit: QubitCircuit + ) -> qiskit.result.Result: + """ + Returns a parsed object of type qiskit.result.Result + for the pulse simulators. + + Parameters + ---------- + density_matrix : qutip.Qobj + The resulting density matrix obtained from `run_state` on + a circuit using the Pulse simulator processors. + + job_id : str + Unique ID identifying a job. + + qutip_circuit : QubitCircuit + The circuit being simulated + + Returns + ------- + qiskit.result.Result + Result of the pulse simulation + """ + count_probs = {} + counts = None + + # calculate probabilities of required states + if final_state: + for i, prob in enumerate(self._get_probabilities(final_state)): + if not np.isclose(prob, 0): + count_probs[hex(i)] = prob + # sample the shots from obtained probabilities + counts = self._sample_shots(count_probs) + + exp_res_data = ExperimentResultData( + counts=counts, + statevector=Statevector(data=np.array(final_state)) + if final_state.type == "ket" + else DensityMatrix(data=np.array(final_state)), + ) + + header = QobjExperimentHeader.from_dict( + { + "name": qutip_circuit.name + if hasattr(qutip_circuit, "name") + else "", + "n_qubits": qutip_circuit.N, + } + ) + + exp_res = ExperimentResult( + shots=self.options.shots, + success=True, + data=exp_res_data, + header=header, + ) + + result = Result( + backend_name=self.configuration().backend_name, + backend_version=self.configuration().backend_version, + qobj_id=id(qutip_circuit), + job_id=job_id, + success=True, + results=[exp_res], + ) + + return result + + def _run_job(self, job_id: str, qutip_circuit: QubitCircuit) -> Result: + """ + Run a QubitCircuit on the Pulse Simulator. + + Parameters + ---------- + job_id : str + Unique ID identifying a job. + + qutip_circuit : QubitCircuit + The circuit obtained after conversion + from QuantumCircuit to QubitCircuit. + + Returns + ------- + qiskit.result.Result + Result of the simulation + """ + zero_state = self.processor.generate_init_processor_state() + + self.processor.load_circuit(qutip_circuit) + result = self.processor.run_state(zero_state) + + final_state = self.processor.get_final_circuit_state(result.states[-1]) + + return self._parse_results( + final_state=final_state, job_id=job_id, qutip_circuit=qutip_circuit + ) + + @classmethod + def _default_options(cls): + """ + Default options for the backend. + + Options + ------- + shots : int + Number of times to sample the results. + """ + return Options(shots=1024) diff --git a/src/qutip_qip/qiskit/converter.py b/src/qutip_qip/qiskit/converter.py index 5dfcff86c..746d4345d 100644 --- a/src/qutip_qip/qiskit/converter.py +++ b/src/qutip_qip/qiskit/converter.py @@ -1,3 +1,5 @@ +"""Conversion of circuits from qiskit to qutip_qip.""" + from qutip_qip.circuit import QubitCircuit from qiskit.quantum_info import Operator import numpy as np @@ -18,6 +20,7 @@ "ry": "RY", "rz": "RZ", "swap": "SWAP", + "u": "QASMU", } _map_controlled_gates = { @@ -93,7 +96,7 @@ def convert_qiskit_circuit( qiskit_circuit : QuantumCircuit QuantumCircuit to be converted to QubitCircuit. - all_custom_gate : bool + allow_custom_gate : bool If False, this function will raise an error if gate conversion is done using a custom gate's unitary matrix. diff --git a/src/qutip_qip/qiskit/job.py b/src/qutip_qip/qiskit/job.py index c607e8fef..f2509eca2 100644 --- a/src/qutip_qip/qiskit/job.py +++ b/src/qutip_qip/qiskit/job.py @@ -1,3 +1,5 @@ +"""Class for a running job.""" + from qiskit.providers import JobV1, JobStatus from qiskit.result import Result @@ -8,14 +10,14 @@ class Job(JobV1): Parameters ---------- - backend : QiskitCircuitSimulator + backend : :class:`.QiskitCircuitSimulator` The backend used to simulate a circuit in the job. job_id : str Unique ID identifying a job. - result : qiskit.result.Result + result : :class:`qiskit.result.Result` The result of a simulation run. """ diff --git a/src/qutip_qip/qiskit/provider.py b/src/qutip_qip/qiskit/provider.py index 56ffb6668..3aaa3c9e6 100644 --- a/src/qutip_qip/qiskit/provider.py +++ b/src/qutip_qip/qiskit/provider.py @@ -1,5 +1,7 @@ +"""Provider for the simulator backends.""" + from qiskit.providers.provider import ProviderV1 -from .backend import QiskitCircuitSimulator +from .backend import QiskitCircuitSimulator, QiskitPulseSimulator class Provider(ProviderV1): @@ -16,7 +18,10 @@ def __init__(self): super().__init__() self.name = "qutip_provider" - self._backends = {"circuit_simulator": QiskitCircuitSimulator()} + self._backends = { + QiskitCircuitSimulator.BACKEND_NAME: QiskitCircuitSimulator(), + QiskitPulseSimulator.BACKEND_NAME: QiskitPulseSimulator(), + } def backends(self, name: str = None, filters=None, **kwargs) -> list: """ diff --git a/tests/test_qiskit.py b/tests/test_qiskit.py index 46f02c1ed..a1aeb2c9e 100644 --- a/tests/test_qiskit.py +++ b/tests/test_qiskit.py @@ -3,11 +3,19 @@ import random from numpy.testing import assert_allclose from qutip_qip.circuit import QubitCircuit +from qutip_qip.device import ( + LinearSpinChain, + CircularSpinChain, + DispersiveCavityQED, +) try: from qiskit import QuantumCircuit from qiskit.providers.aer import AerSimulator - from qutip_qip.qiskit import Provider + from qutip_qip.qiskit.provider import ( + QiskitCircuitSimulator, + QiskitPulseSimulator, + ) from qutip_qip.qiskit.converter import ( convert_qiskit_circuit, _get_qutip_index, @@ -150,22 +158,6 @@ class TestSimulator: gives correct results. """ - def _compare_results(self, qiskit_circuit: QuantumCircuit): - provider = Provider() - - qutip_backend = provider.get_backend("circuit_simulator") - qutip_job = qutip_backend.run(qiskit_circuit) - qutip_result = qutip_job.result() - qutip_sv = qutip_result.data()["statevector"] - - qiskit_backend = AerSimulator(method="statevector") - qiskit_circuit.save_state() - qiskit_job = qiskit_backend.run(qiskit_circuit) - qiskit_result = qiskit_job.result() - qiskit_sv = qiskit_result.data()["statevector"] - - assert_allclose(qutip_sv, qiskit_sv) - def test_circuit_simulator(self): """ Test whether the circuit_simulator matches the @@ -198,9 +190,7 @@ def test_allow_custom_gate(self): my_gate = sub_circ.to_gate() circ.append(my_gate, [1]) - provider = Provider() - - qutip_backend = provider.get_backend("circuit_simulator") + qutip_backend = QiskitCircuitSimulator() # running this with allow_custom_gate=False should raise # a RuntimeError due to the custom sub-circuit qutip_backend.run(circ, allow_custom_gate=False) @@ -219,10 +209,73 @@ def test_measurements(self): circ.measure(0, 0) circ.measure(1, 1) - provider = Provider() - - qutip_backend = provider.get_backend("circuit_simulator") + qutip_backend = QiskitCircuitSimulator() qutip_job = qutip_backend.run(circ) qutip_result = qutip_job.result() assert qutip_result.get_counts(circ) == predefined_counts + + def test_lsc_simulator(self): + """ + Test whether the pulse backend based on the LinearSpinChain model + matches predefined correct results. + """ + circ, predefined_counts = self._init_pulse_test() + + result = self._run_pulse_processor(LinearSpinChain(num_qubits=2), circ) + assert result.get_counts() == predefined_counts + + def test_csc_simulator(self): + """ + Test whether the pulse backend based on the CircularSpinChain model + matches predefined correct results. + """ + circ, predefined_counts = self._init_pulse_test() + + result = self._run_pulse_processor( + CircularSpinChain(num_qubits=2), circ + ) + assert result.get_counts() == predefined_counts + + def test_cavityqed_simulator(self): + """ + Test whether the pulse backend based on the DispersiveCavityQED + model matches predefined correct results. + """ + circ, predefined_counts = self._init_pulse_test() + + result = self._run_pulse_processor( + DispersiveCavityQED(num_qubits=2, num_levels=10), circ + ) + assert result.get_counts() == predefined_counts + + def _compare_results(self, qiskit_circuit: QuantumCircuit): + + qutip_backend = QiskitCircuitSimulator() + qutip_job = qutip_backend.run(qiskit_circuit) + qutip_result = qutip_job.result() + qutip_sv = qutip_result.data()["statevector"] + + qiskit_backend = AerSimulator(method="statevector") + qiskit_circuit.save_state() + qiskit_job = qiskit_backend.run(qiskit_circuit) + qiskit_result = qiskit_job.result() + qiskit_sv = qiskit_result.data()["statevector"] + + assert_allclose(qutip_sv, qiskit_sv) + + def _run_pulse_processor(self, processor, circ): + qutip_backend = QiskitPulseSimulator(processor) + qutip_job = qutip_backend.run(circ) + return qutip_job.result() + + def _init_pulse_test(self): + random.seed(1) + + circ = QuantumCircuit(2, 2) + circ.h(0) + circ.h(1) + + predefined_counts = {"0": 233, "11": 267, "1": 254, "10": 270} + + return circ, predefined_counts