Source code for mqt.ddsim.unitarysimulator

"""Backend for DDSIM Unitary Simulator."""

from __future__ import annotations

import time
from typing import TYPE_CHECKING, Any, Sequence

import numpy as np
import numpy.typing as npt
from qiskit import QiskitError
from qiskit.providers import Options
from qiskit.result.models import ExperimentResult, ExperimentResultData
from qiskit.transpiler import Target

from .header import DDSIMHeader
from .pyddsim import ConstructionMode, UnitarySimulator, get_matrix
from .qasmsimulator import QasmSimulatorBackend
from .target import DDSIMTargetBuilder

if TYPE_CHECKING:
    from qiskit import QuantumCircuit


[docs] class UnitarySimulatorBackend(QasmSimulatorBackend): """Decision diagram-based unitary simulator.""" _US_TARGET = Target( description="MQT DDSIM Unitary Simulator Target", num_qubits=15, # corresponds to 16GiB memory for storing the full matrix ) @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_barrier(target) def __init__(self) -> None: super().__init__(name="unitary_simulator", description="MQT DDSIM Unitary Simulator") @classmethod def _default_options(cls) -> Options: return Options(shots=1, mode="recursive", parameter_binds=None) @property def target(self) -> Target: return self._US_TARGET @classmethod def _run_experiment(cls, qc: QuantumCircuit, **options: Any) -> ExperimentResult: start_time = time.time() seed = options.get("seed", -1) mode = options.get("mode", "recursive") if mode == "sequential": construction_mode = ConstructionMode.sequential elif mode == "recursive": construction_mode = ConstructionMode.recursive else: msg = ( f"Construction mode {mode} not supported by DDSIM unitary simulator. Available modes are " "'recursive' and 'sequential'" ) raise QiskitError(msg) sim = UnitarySimulator(qc, seed=seed, mode=construction_mode) sim.construct() # Extract resulting matrix from final DD and write data unitary: npt.NDArray[np.complex128] = np.zeros((2**qc.num_qubits, 2**qc.num_qubits), dtype=np.complex128) get_matrix(sim, unitary) end_time = time.time() data = ExperimentResultData( unitary=unitary, construction_time=sim.get_construction_time(), max_dd_nodes=sim.get_max_node_count(), dd_nodes=sim.get_final_node_count(), time_taken=end_time - start_time, ) return ExperimentResult( shots=1, success=True, status="DONE", seed=seed, data=data, metadata=qc.metadata, header=DDSIMHeader(qc), ) def _validate(self, quantum_circuits: Sequence[QuantumCircuit]) -> None: """Semantic validations of the quantum circuits which cannot be done via schemas. 1. No shots 2. No measurements in the middle. """ for qc in quantum_circuits: name = qc.name n_qubits = qc.num_qubits max_qubits = self.target.num_qubits if n_qubits > max_qubits: msg = f"Number of qubits {n_qubits} is greater than maximum ({max_qubits}) for '{self.name}'." raise QiskitError(msg) if qc.metadata is not None and "shots" in qc.metadata and qc.metadata["shots"] != 1: qc.metadata["shots"] = 1 for obj in qc.data: if obj[0].name in {"measure", "reset"}: operation_name = obj[0].name msg = f"Unsupported '{self.name}' instruction '{operation_name}' in circuit '{name}'." raise QiskitError(msg)