Skip to content
Merged
7 changes: 5 additions & 2 deletions source/pip/qsharp/_device/_atom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ def simulate(
shots=1,
noise: NoiseConfig | None = None,
type: Optional[Literal["clifford", "cpu", "gpu"]] = None,
seed: Optional[int] = None,
) -> List:
"""
Simulate a QIR program on the NeutralAtomDevice device. This includes approximate layout and scheduling of the program
Expand All @@ -242,6 +243,7 @@ def simulate(
Use `"cpu"` as a fallback option if you don't have a GPU in your system.
If `None` (default), the GPU simulator will be tried first, falling back to
CPU if a suitable GPU device could not be located.
:param seed: An optional random seed for reproducibility.
:returns: The results of each shot of the simulation as a list.
"""

Expand Down Expand Up @@ -278,11 +280,12 @@ def simulate(
str(module),
shots,
noise,
seed,
)
case "cpu":
result = run_qir_cpu(str(module), shots, noise)
result = run_qir_cpu(str(module), shots, noise, seed)
case "gpu":
result = run_qir_gpu(str(module), shots, noise)
result = run_qir_gpu(str(module), shots, noise, seed)
case _:
raise ValueError(f"Simulation type {type} is not supported")

Expand Down
2 changes: 1 addition & 1 deletion source/pip/qsharp/interop/qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from ...estimator import EstimatorParams, EstimatorResult
from ..._native import OutputSemantics, ProgramType, QasmError
from .backends import QSharpBackend, ResourceEstimatorBackend, QirTarget
from .backends import NeutralAtomBackend, QSharpBackend, ResourceEstimatorBackend, QirTarget
from .jobs import QsJob, QsSimJob, ReJob, QsJobSet
from .execution import DetaultExecutor
from qiskit import QuantumCircuit
Expand Down
2 changes: 2 additions & 0 deletions source/pip/qsharp/interop/qiskit/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
from .compilation import Compilation
from .errors import Errors
from .qirtarget import QirTarget
from .neutral_atom_target import NeutralAtomTarget
from .neutral_atom_backend import NeutralAtomBackend
from .qsharp_backend import QSharpBackend
from .re_backend import ResourceEstimatorBackend
75 changes: 69 additions & 6 deletions source/pip/qsharp/interop/qiskit/backends/backend_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
from .compilation import Compilation
from .errors import Errors
from .qirtarget import QirTarget
from ..jobs import QsJob
from ..execution import DetaultExecutor
from ..jobs import QsJob, QsSimJob, QsJobSet
from ..passes import RemoveDelays
from .... import TargetProfile

Expand Down Expand Up @@ -293,9 +294,38 @@ def run_job(
output["header"] = {}
return self._create_results(output)

@abstractmethod
def _submit_job(self, run_input: List[QuantumCircuit], **input_params) -> QsJob:
pass
def _validate_quantum_circuits(
self, run_input: Union[QuantumCircuit, List[QuantumCircuit]]
) -> List[QuantumCircuit]:
"""Normalize and validate run_input to a list of QuantumCircuits.

Wraps a bare ``QuantumCircuit`` in a list and raises ``ValueError``
if any element is not a ``QuantumCircuit``.
"""
if not isinstance(run_input, list):
run_input = [run_input]
for circuit in run_input:
if not isinstance(circuit, QuantumCircuit):
raise ValueError(str(Errors.INPUT_MUST_BE_QC))
return run_input

def _submit_job(self, run_input: List[QuantumCircuit], **options) -> QsJob:
"""Default implementation for simulation backends.

Submits a ``QsSimJob`` for a single circuit or a ``QsJobSet`` for
multiple circuits. Override for backends with different job types
(e.g. ``ResourceEstimatorBackend`` uses ``ReJob``).
"""
from uuid import uuid4

job_id = str(uuid4())
executor = options.pop("executor", DetaultExecutor())
if len(run_input) == 1:
job = QsSimJob(self, job_id, self.run_job, run_input, options, executor)
else:
job = QsJobSet(self, job_id, self.run_job, run_input, options, executor)
job.submit()
return job

def _compile(self, run_input: List[QuantumCircuit], **options) -> List[Compilation]:
# for each run input, convert to qasm
Expand All @@ -314,9 +344,42 @@ def _compile(self, run_input: List[QuantumCircuit], **options) -> List[Compilati
compilations.append(compilation)
return compilations

@abstractmethod
def _create_results(self, output: Dict[str, Any]) -> Any:
pass
"""Default implementation: build a Qiskit ``Result`` from the output dict.

Override for backends that return a different result type
(e.g. ``ResourceEstimatorBackend`` returns ``EstimatorResult``).
"""
return Result.from_dict(output)

def _map_result_bit(self, v) -> str:
"""Map a single QIR result value to a bit character.

Override in subclasses to customize the mapping — for example,
to emit a loss marker instead of the default string fallback for
unknown values.
"""
from .... import Result as QSharpResult

if v == QSharpResult.One:
return "1"
if v == QSharpResult.Zero:
return "0"
return str(v)

def _shot_to_bitstring(self, value) -> str:
"""Recursively convert a QIR shot result to a Qiskit-style bitstring.

- ``tuple`` → space-joined register parts (multiple classical registers)
- ``list`` → concatenated bits via `_map_result_bit`
- anything else → ``str(value)``
"""
if isinstance(value, tuple):
return " ".join(self._shot_to_bitstring(p) for p in value)
elif isinstance(value, list):
return "".join(self._map_result_bit(v) for v in value)
else:
return str(value)

def _transpile(self, circuit: QuantumCircuit, **options) -> QuantumCircuit:
if options.get("skip_transpilation", self._skip_transpilation):
Expand Down
Loading
Loading