Source code for mqt.ddsim.qasmsimulator

"""Backend for DDSIM."""

from __future__ import annotations

import time
import uuid
from typing import TYPE_CHECKING, Any, Mapping, Sequence, Union

from qiskit import QuantumCircuit
from qiskit.providers import BackendV2, Options
from qiskit.providers.models import BackendStatus
from qiskit.result import Result
from qiskit.result.models import ExperimentResult, ExperimentResultData
from qiskit.transpiler import Target

from . import __version__
from .header import DDSIMHeader
from .job import DDSIMJob
from .pyddsim import CircuitSimulator
from .target import DDSIMTargetBuilder

if TYPE_CHECKING:
    from qiskit.circuit import Parameter
    from qiskit.circuit.parameterexpression import ParameterValueType

    Parameters = Union[Mapping[Parameter, ParameterValueType], Sequence[ParameterValueType]]


[docs] class QasmSimulatorBackend(BackendV2): """Python interface to MQT DDSIM.""" _SHOW_STATE_VECTOR = False _TARGET = Target(description="MQT DDSIM Simulator Target", num_qubits=128) @staticmethod def _add_operations_to_target(target: Target) -> None: DDSIMTargetBuilder.add_0q_gates(target) DDSIMTargetBuilder.add_1q_gates(target) DDSIMTargetBuilder.add_2q_gates(target) DDSIMTargetBuilder.add_3q_gates(target) DDSIMTargetBuilder.add_multi_qubit_gates(target) DDSIMTargetBuilder.add_non_unitary_operations(target) DDSIMTargetBuilder.add_barrier(target) def _initialize_target(self) -> None: if len(self.target.operations) > 0: return self._add_operations_to_target(self.target) def __init__( self, name: str = "qasm_simulator", description: str = "MQT DDSIM QASM Simulator", ) -> None: super().__init__(name=name, description=description, backend_version=__version__) self._initialize_target() @classmethod def _default_options(cls) -> Options: return Options( shots=None, parameter_binds=None, seed_simulator=None, approximation_step_fidelity=1.0, approximation_steps=0, approximation_strategy="fidelity", ) @property def target(self) -> Target: return self._TARGET @property def max_circuits(self) -> int | None: return None
[docs] @staticmethod def assign_parameters( quantum_circuits: Sequence[QuantumCircuit], parameter_values: Sequence[Parameters] | None, ) -> list[QuantumCircuit]: if not any(qc.parameters for qc in quantum_circuits) and not parameter_values: return list(quantum_circuits) if parameter_values is None: msg = "No parameter values provided although at least one parameterized circuit was supplied." raise ValueError(msg) if len(quantum_circuits) != len(parameter_values): msg = f"The number of circuits ({len(quantum_circuits)}) does not match the number of provided parameter sets ({len(parameter_values)})." raise ValueError(msg) bound_circuits = [ qc.assign_parameters(parameters=values) for qc, values in zip(quantum_circuits, parameter_values) ] # fix the circuit names for qcb, qc in zip(bound_circuits, quantum_circuits): qcb.name = qc.name return bound_circuits
[docs] def run( self, quantum_circuits: QuantumCircuit | Sequence[QuantumCircuit], parameter_values: Sequence[Parameters] | None = None, **options: Any, ) -> DDSIMJob: if isinstance(quantum_circuits, QuantumCircuit): quantum_circuits = [quantum_circuits] job_id = str(uuid.uuid4()) local_job = DDSIMJob(self, job_id, self._run_job, quantum_circuits, parameter_values, **options) local_job.submit() return local_job
def _validate(self, quantum_circuits: Sequence[QuantumCircuit]) -> None: pass def _run_job( self, job_id: int, quantum_circuits: Sequence[QuantumCircuit], parameter_values: Sequence[Parameters] | None, **options: Any, ) -> Result: self._validate(quantum_circuits) start = time.time() bound_circuits = self.assign_parameters(quantum_circuits, parameter_values) result_list = [self._run_experiment(q_circ, **options) for q_circ in bound_circuits] end = time.time() return Result( backend_name=self.name, backend_version=self.backend_version, qobj_id=None, job_id=job_id, success=all(res.success for res in result_list), results=result_list, status="COMPLETED", time_taken=end - start, ) def _run_experiment(self, qc: QuantumCircuit, **options: dict[str, Any]) -> ExperimentResult: start_time = time.time() approximation_step_fidelity = options.get("approximation_step_fidelity", 1.0) approximation_steps = options.get("approximation_steps", 1) approximation_strategy = options.get("approximation_strategy", "fidelity") seed = options.get("seed_simulator", -1) shots = options.get("shots", 1024) sim = CircuitSimulator( qc, approximation_step_fidelity=approximation_step_fidelity, approximation_steps=approximation_steps, approximation_strategy=approximation_strategy, seed=seed, ) counts = sim.simulate(shots=shots) end_time = time.time() data = ExperimentResultData( counts={hex(int(result, 2)): count for result, count in counts.items()}, statevector=None if not self._SHOW_STATE_VECTOR else sim.get_vector(), time_taken=end_time - start_time, ) return ExperimentResult( shots=shots, success=True, status="DONE", seed=seed, data=data, metadata=qc.metadata, header=DDSIMHeader(qc), )
[docs] def status(self) -> BackendStatus: """Return backend status. Returns: BackendStatus: the status of the backend. """ return BackendStatus( backend_name=self.name, backend_version=self.backend_version, operational=True, pending_jobs=0, status_msg="", )