Source code for qutip_qip.qiskit.pulse_simulator

import numpy as np

from qutip import Qobj
from qutip_qip.circuit import QubitCircuit
from qutip_qip.device import Processor
from qutip_qip.qiskit import QiskitSimulatorBase
from qutip_qip.qiskit.utils import (
    QUTIP_TO_QISKIT_GATE_MAP,
    convert_qiskit_circuit_to_qutip,
    get_probabilities,
    sample_shots,
)

from qiskit import QuantumCircuit
from qiskit.result import Result
from qiskit.result.models import ExperimentResult, ExperimentResultData
from qiskit.quantum_info import Statevector, DensityMatrix

DEFAULT_BASIS_GATE_LIST = list(QUTIP_TO_QISKIT_GATE_MAP.keys())


[docs] class QiskitPulseSimulator(QiskitSimulatorBase): """ ``qiskit`` backend dealing with pulse-level simulation. Parameters ---------- processor : :class:`.Processor` The processor model to be used for simulation. An instance of the required :class:`.Processor` object is to be provided after initialising it with the required parameters. num_qubits : int, Optional num_qubits for the Pulse Simulator Backend. Defaults to 10 qubits. basis_gates : list[str], Optional The basis gates names in QuTip. Defaults to (PHASEGATE, X, Y, Z, RX, RY, RZ, Hadamard, S, T, SWAP, QASMU, CX, CY, CZ, CRX, CRY, CRZ, CPHASE) max_shots : int, Optional Maximum number of shots the Backend support while sampling. max_circuits : int, Optional The maximum number of circuits that can be run in a single job. name : str, Optional Name of the Pulse Simulator Backend description : str, Optional Description of the Pulse Simulator Backend version : str, Optional Version of Pulse Simulator Backend Attributes ---------- processor : :class:`.Processor` The processor model to be used for simulation. """ def __init__( self, processor: Processor, num_qubits: int = 10, basis_gates: list[str] = DEFAULT_BASIS_GATE_LIST, max_shots: int = 1e6, max_circuits: int = 1, name: str = "pulse_simulator", description: str = "A qutip-qip based pulse-level \ simulator based on the open system solver.", version: str = "0.1", ): super().__init__( name=name, description=description, version=version, num_qubits=num_qubits, basis_gates=basis_gates, max_shots=max_shots, max_circuits=max_circuits, ) self._processor = processor @property def processor(self) -> Processor: return self._processor def _run_job(self, job_id: str, qiskit_circuit: list[QuantumCircuit]) -> Result: """ Run a :class:`.QubitCircuit` on the Pulse Simulator. Parameters ---------- job_id : str Unique ID identifying a job. qiskit_circuit : list[:class:`.QuantumCircuit`] The circuit obtained after conversion from :class:`.QuantumCircuit` to :class:`.QubitCircuit`. Returns ------- :class:`qiskit.result.Result` Result of the simulation. """ final_states = [] qutip_circuits = [] for circuit in qiskit_circuit: qutip_circuit = convert_qiskit_circuit_to_qutip(circuit) qutip_circuits.append(qutip_circuit) self.processor.load_circuit(qutip_circuit) zero_state = self.processor.generate_init_processor_state() result = self.processor.run_state(zero_state) final_states.append( self.processor.get_final_circuit_state(result.states[-1]) ) return self._parse_results( job_id=job_id, final_states=final_states, qutip_circuits=qutip_circuits, ) def _parse_results( self, job_id: str, qutip_circuits: list[QubitCircuit], final_states: list[Qobj], ) -> Result: """ Returns a parsed object of type :class:`qiskit.result.Result` for the pulse simulators. Parameters ---------- job_id : str Unique ID identifying a job. qutip_circuits : list[:class:`.QubitCircuit`] The circuits being simulated. final_states : list[:class:`.Qobj`] The resulting density matrices obtained from `run_state` on circuits using the Pulse simulator processors. Returns ------- :class:`qiskit.result.Result` Result of the pulse simulation. """ if len(final_states) != len(qutip_circuits): raise ValueError( "Number of final_states must be = to number of qutip circuits" ) exp_res = [] num_circuits = len(final_states) for i in range(num_circuits): count_probs = {} counts = None final_state = final_states[i] qutip_circuit = qutip_circuits[i] # calculate probabilities of required states if final_state: for i, prob in enumerate(get_probabilities(final_state)): if not np.isclose(prob, 0): count_probs[hex(i)] = prob # sample the shots from obtained probabilities counts = sample_shots(count_probs, self.options["shots"]) exp_res_data = ExperimentResultData( counts=counts, statevector=( Statevector(data=final_state.full()) if final_state.type == "ket" else DensityMatrix(data=final_state.full()) ), ) header = { "name": (qutip_circuit.name if hasattr(qutip_circuit, "name") else ""), "n_qubits": qutip_circuit.num_qubits, } exp_res.append( ExperimentResult( shots=self._options.shots, success=True, header=header, data=exp_res_data, ) ) result = Result( backend_name=self.name, backend_version=self.backend_version, job_id=job_id, success=True, results=exp_res, ) return result