Source code for qbraid.runtime.result

# Copyright 2025 qBraid
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Module containing models for schema-conformant Results.

"""
from __future__ import annotations

import copy
import datetime
from enum import Enum
from typing import Any, Generic

from qbraid_core import deprecated
from qbraid_core.system.generic import _datetime_to_str

from .result_data import GateModelResultData, ResultDataType


[docs] class Result(Generic[ResultDataType]): """Represents the results of a quantum job. This class is intended to be initialized by a QuantumJob class. Args: device_id (str): The ID of the device that executed the job. job_id (str or int): The ID of the job. success (bool): Whether the job was successful. data (ResultData): The result of the job. **details: Additional metadata about the job results """
[docs] def __init__( # pylint: disable=too-many-arguments self, device_id: str, job_id: str | int, success: bool, data: ResultDataType, **kwargs, ): """Create a new Result object.""" self.device_id = device_id self.job_id = job_id self.success = success self._data = data self._details = kwargs or {}
@property def data(self) -> ResultDataType: """Returns the result of the job.""" return self._data @property def details(self) -> dict[str, Any]: """Returns the result of the job.""" return self._details @deprecated("Use 'Result.data.get_counts()' instead.") def measurement_counts(self, *args, **kwargs) -> Any: """Returns the measurement counts of the job.""" return self.data.get_counts(*args, **kwargs) @deprecated("Use 'Result.data.measurements' instead.") def measurements(self) -> Any: """Returns the measurements of the job.""" return self.data.measurements def _format_value(self, value: Any, depth: int = 0, max_depth: int = 2) -> str: """Helper function to format nested values with a depth limit.""" if isinstance(value, str): return f"'{value}'" if isinstance(value, Enum): return f"{value.name}" if isinstance(value, datetime.datetime): return _datetime_to_str(value) if isinstance(value, dict): if depth >= max_depth: return "{...}" data = copy.deepcopy(value) if "compiledOutput" in data and data["compiledOutput"] is not None: data["compiledOutput"] = "..." return ( "{" + ", ".join( f"{k}: {self._format_value(v, depth + 1, max_depth)}" for k, v in data.items() ) + "}" ) if isinstance(value, list): if depth >= max_depth: return "[...]" return ( "[" + ", ".join(self._format_value(item, depth + 1, max_depth) for item in value) + "]" ) return repr(value) def __repr__(self): """Return a string representation of the Result object.""" out = ( f"Result(\n" f" device_id={self.device_id},\n" f" job_id={self.job_id},\n" f" success={self.success},\n" f" data={self.data}" ) if self.details: for key, value in self.details.items(): formatted_value = self._format_value(value) out += f",\n {key}={formatted_value}" out += "\n)" return out
class BatchResult(Generic[ResultDataType]): """Represents the results of a batch quantum job. Wraps a list of per-circuit :class:`Result` objects and provides aggregate access to measurement data and per-circuit metadata. Args: device_id: The ID of the device that executed the job. job_id: The ID of the job. success: Whether the job completed successfully. results: Per-circuit Result objects. **kwargs: Aggregate metadata (e.g. time_stamps, cost, status). """ def __init__( self, device_id: str, job_id: str | int, success: bool, results: list[Result[ResultDataType]], **kwargs, ): self.device_id = device_id self.job_id = job_id self.success = success self._results = results self._aggregate_details = kwargs or {} self._data: ResultDataType | None = None @property def num_circuits(self) -> int: """Returns the number of circuits in the batch.""" return len(self._results) @property def results(self) -> list[Result[ResultDataType]]: """Returns the per-circuit Result objects.""" return self._results @property def data(self) -> ResultDataType: """Returns aggregated result data across all circuits. For gate model jobs, ``get_counts()`` and ``get_probabilities()`` return lists indexed by circuit position. """ if self._data is None: self._data = self._build_aggregate_data() return self._data @property def details(self) -> list[dict[str, Any]]: """Returns per-circuit metadata, indexed by circuit position.""" return [r.details for r in self._results] def _build_aggregate_data(self) -> ResultDataType: """Collects per-circuit measurement data into a single ResultData.""" counts = [] probabilities = [] has_counts = False has_probs = False for r in self._results: rd = r.data if isinstance(rd, GateModelResultData): if rd.measurement_counts is not None: counts.append(rd.measurement_counts) has_counts = True else: counts.append({}) if rd._measurement_probabilities is not None: probabilities.append(rd._measurement_probabilities) has_probs = True else: probabilities.append({}) return GateModelResultData( measurement_counts=counts if has_counts else None, measurement_probabilities=probabilities if has_probs else None, ) def __repr__(self): """Return a string representation of the BatchResult object.""" return ( f"BatchResult(\n" f" device_id={self.device_id},\n" f" job_id={self.job_id},\n" f" success={self.success},\n" f" num_circuits={self.num_circuits}\n" f")" )