"""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 collections import Counter
from typing import Literal
import qsharp
from qsharp._simulation import run_qir
from qsharp.openqasm import run as sparse_state_run_qasm
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"]
[docs]
class QdkFullStateSimulatorSettings(Settings):
"""Settings for the QDK Full State Simulator circuit executor."""
[docs]
def __init__(self) -> None:
"""Initialize QDK Full State Simulator settings."""
Logger.trace_entering()
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.
"""
Logger.trace_entering()
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")
)
Logger.debug(f"Measurement results obtained: {raw_results}")
# Reorder bits in each measurement result to match Little Endian convention
bitstrings = ["".join("0" if str(x) == "Zero" else "1" for x in reversed(one_run)) for one_run in raw_results]
counts = dict(Counter(bitstrings))
return CircuitExecutorData(
bitstring_counts=counts,
total_shots=shots,
executor=self.name(),
executor_metadata=raw_results,
)
[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()
if noise is not None:
raise NotImplementedError("Gate specific noise is not yet supported for the QDK Sparse State Simulator. ")
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
)
Logger.debug(f"Measurement results obtained: {raw_results}")
bitstrings = [
"".join("0" if str(x) == "Zero" else "1" for x in reversed(one_run)) for one_run in raw_results
]
bitstring_counts = dict(Counter(bitstrings))
else:
qasm = circuit.get_qasm()
raw_results = sparse_state_run_qasm(
qasm,
shots=shots,
as_bitstring=True,
seed=self._settings.get("seed"),
)
Logger.debug(f"Measurement results obtained: {raw_results}")
# Reverse the order of bits in each measurement result to match Little Endian convention
bitstring_counts = {bitstring[::-1]: count for bitstring, count in Counter(raw_results).items()}
return CircuitExecutorData(
bitstring_counts=bitstring_counts,
total_shots=shots,
executor=self.name(),
executor_metadata=raw_results,
)
def name(self) -> str:
"""Return the algorithm name as qdk_sparse_state_simulator."""
return "qdk_sparse_state_simulator"