Source code for qdk_chemistry.algorithms.circuit_executor.qdk

"""QDK/Chemistry Circuit Executor implementation using QDK.

This module provides a CircuitExecutor implementation that uses the QDK backends
to execute quantum circuits. It accepts QDK/Chemistry Circuit and QuantumErrorProfile
data classes and returns measurement bitstring results via CircuitExecutorData.

Supported QDK backends include:
    * QDK Full State Simulator
    * QDK Sparse State Simulator
"""

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
from typing import Literal

from qdk import qsharp
from qdk.openqasm import run as sparse_state_run_qasm

try:
    from qdk.simulation import run_qir
except ImportError:
    from qsharp._simulation import run_qir

from qdk_chemistry.algorithms.circuit_executor.base import CircuitExecutor
from qdk_chemistry.data import Circuit, CircuitExecutorData, QuantumErrorProfile, Settings
from qdk_chemistry.utils import Logger

__all__: list[str] = ["QdkFullStateSimulator", "QdkFullStateSimulatorSettings"]


def _process_raw_results(raw_results: list) -> tuple[dict[str, int], dict[str, int] | None]:
    """Convert raw measurement results into bitstring counts, separating clean and lost shots.

    Reorders bits to Little Endian convention and uses 'L' to mark lost qubits.

    Args:
        raw_results: Raw measurement results from the simulator.

    Returns:
        A tuple of (bitstring_counts, loss_bitstrings). loss_bitstrings is None
        if no shots experienced qubit loss.

    """
    _map = {"One": "1", "Loss": "L", "Zero": "0"}
    clean_counts: dict[str, int] = {}
    loss_counts: dict[str, int] = {}

    for one_run in raw_results:
        has_loss = False
        chars = []
        for x in reversed(one_run):
            label = str(x)
            if label not in _map:
                raise ValueError(f"Unexpected measurement result '{label}'; expected one of {set(_map)}")
            c = _map[label]
            if c == "L":
                has_loss = True
            chars.append(c)
        key = "".join(chars)
        if has_loss:
            loss_counts[key] = loss_counts.get(key, 0) + 1
        else:
            clean_counts[key] = clean_counts.get(key, 0) + 1

    return clean_counts, loss_counts if loss_counts else None


[docs] class QdkFullStateSimulatorSettings(Settings): """Settings for the QDK Full State Simulator circuit executor."""
[docs] def __init__(self) -> None: """Initialize QDK Full State Simulator settings.""" super().__init__() self._set_default( "type", "string", "cpu", "Type of simulator to use: 'cpu', 'gpu', or 'clifford'", ["cpu", "gpu", "clifford"] ) self._set_default("seed", "int", 42, "Random seed for simulation reproducibility")
[docs] class QdkFullStateSimulator(CircuitExecutor): """QDK Full State Simulator circuit executor implementation."""
[docs] def __init__( self, simulator_type: Literal["cpu", "gpu", "clifford"] = "cpu", seed: int = 42, ) -> None: """Initialize the QDK Full State Simulator circuit executor. Args: simulator_type: The type of simulator to use. seed: The random seed for simulation reproducibility. """ super().__init__() self._settings = QdkFullStateSimulatorSettings() self._settings.set("type", simulator_type) self._settings.set("seed", seed)
def _run_impl( self, circuit: Circuit, shots: int, noise: QuantumErrorProfile | None = None, ) -> CircuitExecutorData: """Execute the given quantum circuit using the QDK Full State Simulator. Args: circuit: The quantum circuit to execute. shots: The number of shots to execute the circuit. noise: Optional noise profile to apply during execution. Returns: CircuitExecutorData: Object containing the results of the circuit execution. """ Logger.trace_entering() qir = circuit.get_qir() Logger.debug("QIR compiled") noise_config = noise.to_qdk_noise_config() if noise is not None else None raw_results = run_qir( qir, shots=shots, noise=noise_config, seed=self._settings.get("seed"), type=self._settings.get("type") ) bitstring_counts, loss_bitstrings = _process_raw_results(raw_results) return CircuitExecutorData( bitstring_counts=bitstring_counts, total_shots=shots, executor=self.name(), executor_metadata=raw_results, loss_bitstrings=loss_bitstrings, )
[docs] def name(self) -> str: """Return the algorithm name as qdk_full_state_simulator.""" return "qdk_full_state_simulator"
class QdkSparseStateSimulatorSettings(Settings): """Settings for the QDK Sparse State Simulator circuit executor.""" def __init__(self) -> None: """Initialize QDK Sparse State Simulator settings.""" Logger.trace_entering() super().__init__() self._set_default("seed", "int", 42, "Random seed for simulation reproducibility") class QdkSparseStateSimulator(CircuitExecutor): """QDK Sparse State Simulator circuit executor implementation.""" def __init__(self) -> None: """Initialize the QDK Sparse State Simulator circuit executor.""" Logger.trace_entering() super().__init__() self._settings = QdkSparseStateSimulatorSettings() def _run_impl( self, circuit: Circuit, shots: int, noise: QuantumErrorProfile | None = None, ) -> CircuitExecutorData: """Execute the given quantum circuit using the QDK Sparse State Simulator. Args: circuit: The quantum circuit to execute. shots: The number of shots to execute the circuit. noise: Optional noise profile to apply during execution. Returns: CircuitExecutorData: Object containing the results of the circuit execution. """ Logger.trace_entering() noise_config = noise.to_qdk_noise_config() if noise is not None else None if circuit._qsharp_factory is not None: # noqa: SLF001 raw_results = qsharp.run( circuit._qsharp_factory.program, # noqa: SLF001 shots, *circuit._qsharp_factory.parameter.values(), # noqa: SLF001 noise=noise_config, seed=self._settings.get("seed"), ) bitstring_counts, loss_bitstrings = _process_raw_results(raw_results) else: qasm = circuit.get_qasm() raw_results = sparse_state_run_qasm( qasm, shots=shots, noise=noise_config, seed=self._settings.get("seed"), ) bitstring_counts, loss_bitstrings = _process_raw_results(raw_results) return CircuitExecutorData( bitstring_counts=bitstring_counts, total_shots=shots, executor=self.name(), executor_metadata=raw_results, loss_bitstrings=loss_bitstrings, ) def name(self) -> str: """Return the algorithm name as qdk_sparse_state_simulator.""" return "qdk_sparse_state_simulator"