Source code for uniqc.simulator.simulator

"""Unified quantum circuit simulator.

Provides ``Simulator`` and ``NoisySimulator`` — the public simulator classes
that accept *any* quantum-circuit representation (``Circuit`` object, OriginIR
string, or OpenQASM 2.0 string) and auto-detect the input format at runtime.

Key exports:
    - Simulator: Ideal simulator (statevector / density matrix backends).
    - NoisySimulator: Noisy simulator with error-model injection.
"""

__all__ = ["Simulator", "NoisySimulator"]


from uniqc.compile.originir.originir_base_parser import OriginIR_BaseParser
from uniqc.compile.qasm.qasm_base_parser import OpenQASM2_BaseParser

from .base_simulator import BaseNoisySimulator, BaseSimulator
from .error_model import ErrorLoader


def _to_originir_str(quantum_code):
    """Normalise *AnyQuantumCircuit* to an OriginIR or QASM string.

    Accepts a ``Circuit`` object (calls ``.originir``), an OriginIR string, or
    a QASM string.  Raises ``TypeError`` for unsupported types.
    """
    from uniqc.circuit_builder.qcircuit import Circuit as _Circuit

    if isinstance(quantum_code, _Circuit):
        return quantum_code.originir
    if isinstance(quantum_code, str):
        return quantum_code
    raise TypeError(f"Expected Circuit, originir string, or qasm string, got {type(quantum_code).__name__}")


[docs] class Simulator(BaseSimulator): """Unified ideal quantum circuit simulator. Accepts any of the following as *quantum_code*: - A :class:`~uniqc.circuit_builder.Circuit` object - An OriginIR string (``circuit.originir``) - An OpenQASM 2.0 string (``circuit.qasm``) The input format is detected automatically: OriginIR is tried first, and if parsing fails the code falls back to QASM. Args: backend_type: Backend type (``"statevector"`` or ``"densitymatrix"``). available_qubits: List of available qubit indices (optional). available_topology: List of available qubit pairs (optional). **extra_kwargs: Additional arguments passed to :class:`BaseSimulator` (e.g. ``least_qubit_remapping``). """ def __init__( self, backend_type="statevector", available_qubits: list[int] = None, available_topology: list[list[int]] = None, **extra_kwargs, ): super().__init__(backend_type, available_qubits, available_topology, **extra_kwargs) self.parser = OriginIR_BaseParser() # ------------------------------------------------------------------ # simulate_preprocess — auto-detect input format # ------------------------------------------------------------------
[docs] def simulate_preprocess(self, quantum_code): """Parse and preprocess a quantum program. *quantum_code* may be a :class:`Circuit`, an OriginIR string, or a QASM string. The format is detected automatically. Returns: Tuple of (processed_program_body, measurement_qubits). """ quantum_code = _to_originir_str(quantum_code) # Try OriginIR first. self._clear() self.parser = OriginIR_BaseParser() try: self.parser.parse(quantum_code) except Exception: # Fall back to QASM. self._clear() self.parser = OpenQASM2_BaseParser() self.parser.parse(quantum_code) self._extract_actual_used_qubits() if self.available_qubits or self.available_topology: self._check_available_qubits() processed_program_body = self._process_program_body() measure_qubit = self._process_measure() measure_qubit_cbit = sorted(measure_qubit, key=lambda k: k[1]) measure_qubit = [q for q, _ in measure_qubit_cbit] return processed_program_body, measure_qubit
def _clear(self): super()._clear() self.parser = OriginIR_BaseParser()
[docs] class NoisySimulator(BaseNoisySimulator): """Unified noisy quantum circuit simulator. Same input flexibility as :class:`Simulator` (``Circuit``, OriginIR, or QASM string), with additional support for gate-level error injection and readout-error modelling. Args: backend_type: Backend type (must be ``"density_matrix"`` for noise). available_qubits: List of available qubit indices (optional). available_topology: List of available qubit pairs (optional). error_loader: :class:`ErrorLoader` instance for gate error injection. readout_error: Dict mapping qubit index to ``[p01, p10]`` readout error rates. """ def __init__( self, backend_type="statevector", available_qubits: list[int] = None, available_topology: list[list[int]] = None, error_loader: ErrorLoader = None, readout_error: dict[int, list[float]] = {}, ): super().__init__( backend_type, available_qubits, available_topology, error_loader, readout_error, ) self.parser = OriginIR_BaseParser()
[docs] def simulate_preprocess(self, quantum_code): """Parse, preprocess, and inject errors into a quantum program. *quantum_code* may be a :class:`Circuit`, an OriginIR string, or a QASM string. Returns: Tuple of (error-injected program_body, measurement_qubits). """ quantum_code = _to_originir_str(quantum_code) # Try OriginIR first. self._clear() self.parser = OriginIR_BaseParser() try: self.parser.parse(quantum_code) except Exception: self._clear() self.parser = OpenQASM2_BaseParser() self.parser.parse(quantum_code) self._extract_actual_used_qubits() if self.available_qubits or self.available_topology: self._check_available_qubits() processed_program_body = self._process_program_body() measure_qubit = self._process_measure() measure_qubit_cbit = sorted(measure_qubit, key=lambda k: k[1]) measure_qubit = [q for q, _ in measure_qubit_cbit] # Apply error injection (same logic as BaseNoisySimulator). if self.error_loader: self.error_loader.process_opcodes(processed_program_body) processed_program_body = self.error_loader.opcodes return processed_program_body, measure_qubit
def _clear(self): super()._clear() self.parser = OriginIR_BaseParser()