Coverage for /home/runner/work/tket/tket/pytket/pytket/backends/backendresult.py: 90%
278 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-09 15:08 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-09 15:08 +0000
1# Copyright Quantinuum
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""`BackendResult` class and associated methods."""
17import operator
18import warnings
19from collections import Counter
20from collections.abc import Collection, Iterable, Sequence
21from functools import reduce
22from typing import Any, NamedTuple, TypeVar, cast
24import numpy as np
26from pytket.circuit import (
27 _DEBUG_ONE_REG_PREFIX,
28 _DEBUG_ZERO_REG_PREFIX,
29 BasisOrder,
30 Bit,
31 Circuit,
32 Qubit,
33 UnitID,
34)
35from pytket.unit_id import UnitType
36from pytket.utils.distribution import EmpiricalDistribution, ProbabilityDistribution
37from pytket.utils.outcomearray import OutcomeArray, readout_counts
38from pytket.utils.results import (
39 get_n_qb_from_statevector,
40 permute_basis_indexing,
41 permute_rows_cols_in_unitary,
42 probs_from_state,
43)
45from .backend_exceptions import InvalidResultType
48class StoredResult(NamedTuple):
49 """NamedTuple with optional fields for all result types."""
51 counts: Counter[OutcomeArray] | None = None
52 shots: OutcomeArray | None = None
53 state: np.ndarray | None = None
54 unitary: np.ndarray | None = None
55 density_matrix: np.ndarray | None = None
58class BackendResult:
59 """Encapsulate generic results from pytket Backend instances.
61 In the case of a real quantum device or a shots-based simulator
62 a BackendResult will typically be a collection of measurements (shots and counts).
64 Results can also be the output of ideal simulations of circuits.
65 These can take the form of statevectors, unitary arrays or density matrices.
67 :param q_bits: Sequence of qubits.
68 :param c_bits: Sequence of classical bits.
69 :param counts: The counts in the result.
70 :param shots: The shots in the result.
71 :param state: The resulting statevector (from a statevector simulation).
72 :param unitary: The resulting unitary operator (from a unitary simulation).
73 :param density_matrix: The resulting density matrix
74 (from a density-matrix simulator).
75 :param ppcirc: If provided, classical postprocessing to be applied to all measured
76 results (i.e. shots and counts).
77 """
79 def __init__( # noqa: PLR0913
80 self,
81 *,
82 q_bits: Sequence[Qubit] | None = None,
83 c_bits: Sequence[Bit] | None = None,
84 counts: Counter[OutcomeArray] | None = None,
85 shots: OutcomeArray | None = None,
86 state: Any = None,
87 unitary: Any = None,
88 density_matrix: Any = None,
89 ppcirc: Circuit | None = None,
90 ):
91 # deal with mutable defaults
92 if q_bits is None:
93 q_bits = []
94 if c_bits is None:
95 c_bits = []
97 self._counts = counts
98 self._shots = shots
100 self._state = state
101 self._unitary = unitary
102 self._density_matrix = density_matrix
104 self._ppcirc = ppcirc
106 self.c_bits: dict[Bit, int] = {}
107 self.q_bits: dict[Qubit, int] = {}
109 def _process_unitids(
110 var: Sequence[UnitID], attr: str, lent: int, uid: type[UnitID]
111 ) -> None:
112 if var:
113 setattr(self, attr, {unit: i for i, unit in enumerate(var)})
114 if lent != len(var):
115 raise ValueError(
116 f"Length of {attr} ({len(var)}) does not"
117 f" match input data dimensions ({lent})."
118 )
119 else:
120 setattr(self, attr, {uid(i): i for i in range(lent)}) # type: ignore
122 if self.contains_measured_results:
123 _bitlength = 0
124 if self._counts is not None:
125 if shots is not None:
126 raise ValueError(
127 "Provide either counts or shots, both is not valid."
128 )
129 try:
130 _bitlength = next(self._counts.elements()).width
131 except StopIteration:
132 _bitlength = len(c_bits)
134 if self._shots is not None:
135 _bitlength = self._shots.width
137 _process_unitids(c_bits, "c_bits", _bitlength, Bit)
139 if self.contains_state_results:
140 _n_qubits = 0
141 if self._unitary is not None:
142 _n_qubits = int(np.log2(self._unitary.shape[-1]))
143 elif self._state is not None:
144 _n_qubits = get_n_qb_from_statevector(self._state)
145 elif self._density_matrix is not None: 145 ↛ 148line 145 didn't jump to line 148 because the condition on line 145 was always true
146 _n_qubits = int(np.log2(self._density_matrix.shape[-1]))
148 _process_unitids(q_bits, "q_bits", _n_qubits, Qubit)
150 def __repr__(self) -> str:
151 return (
152 f"BackendResult(q_bits={self.q_bits},c_bits={self.c_bits},"
153 f"counts={self._counts},shots={self._shots},state={self._state},"
154 f"unitary={self._unitary},density_matrix={self._density_matrix})"
155 )
157 @property
158 def contains_measured_results(self) -> bool:
159 """Whether measured type results (shots or counts) are stored"""
160 return (self._counts is not None) or (self._shots is not None)
162 @property
163 def contains_state_results(self) -> bool:
164 """Whether state type results (state vector or unitary or density_matrix)
165 are stored"""
166 return (
167 (self._state is not None)
168 or (self._unitary is not None)
169 or (self._density_matrix is not None)
170 )
172 def __eq__(self, other: object) -> bool:
173 if not isinstance(other, BackendResult): 173 ↛ 174line 173 didn't jump to line 174 because the condition on line 173 was never true
174 return NotImplemented
175 return (
176 self.q_bits == other.q_bits
177 and self.c_bits == other.c_bits
178 and (
179 (self._shots is None and other._shots is None)
180 or cast("OutcomeArray", self._shots)
181 == cast("OutcomeArray", other._shots)
182 )
183 and self._counts == other._counts
184 and np.array_equal(self._state, other._state)
185 and np.array_equal(self._unitary, other._unitary)
186 and np.array_equal(self._density_matrix, other._density_matrix)
187 )
189 def get_bitlist(self) -> list[Bit]:
190 """Return list of Bits in internal storage order.
192 :raises AttributeError: BackendResult does not include a Bits list.
193 :return: Sorted list of Bits.
194 :rtype: List[Bit]
195 """
196 return _sort_keys_by_val(self.c_bits)
198 def get_qbitlist(self) -> list[Qubit]:
199 """Return list of Qubits in internal storage order.
201 :raises AttributeError: BackendResult does not include a Qubits list.
202 :return: Sorted list of Qubits.
203 :rtype: List[Qubit]
204 """
206 return _sort_keys_by_val(self.q_bits)
208 def _get_measured_res( # noqa: PLR0912
209 self, bits: Sequence[Bit], ppcirc: Circuit | None = None
210 ) -> StoredResult:
211 vals: dict[str, Any] = {}
213 if not self.contains_measured_results:
214 raise InvalidResultType("shots/counts")
216 if self._ppcirc is not None:
217 if ppcirc is None: 217 ↛ 220line 217 didn't jump to line 220 because the condition on line 217 was always true
218 ppcirc = self._ppcirc
219 else:
220 raise ValueError("Postprocessing circuit already provided.")
222 try:
223 chosen_readouts = [self.c_bits[bit] for bit in bits]
224 except KeyError:
225 raise ValueError("Requested Bit not in result.") # noqa: B904
227 if self._counts is not None:
228 if ppcirc is not None:
229 # Modify self._counts:
230 new_counts: Counter[OutcomeArray] = Counter()
231 for oa, n in self._counts.items():
232 readout = oa.to_readout()
233 values = {bit: bool(readout[i]) for bit, i in self.c_bits.items()}
234 new_values = ppcirc._classical_eval(values) # noqa: SLF001
235 new_oa = OutcomeArray.from_readouts(
236 [[int(new_values[bit]) for bit in bits]]
237 )
238 new_counts[new_oa] += n
239 else:
240 new_counts = self._counts
241 vals["counts"] = reduce(
242 operator.add,
243 (
244 Counter({outcome.choose_indices(chosen_readouts): count})
245 for outcome, count in new_counts.items()
246 ),
247 Counter(),
248 )
249 if self._shots is not None:
250 if ppcirc is not None:
251 # Modify self._shots:
252 readouts = self._shots.to_readouts()
253 new_readouts = []
254 for i in range(self._shots.n_outcomes):
255 readout = readouts[i, :]
256 values = {bit: bool(readout[i]) for bit, i in self.c_bits.items()}
257 new_values = ppcirc._classical_eval(values) # noqa: SLF001
258 new_readout = [0] * self._shots.width
259 for bit, i in self.c_bits.items(): # noqa: PLW2901
260 if new_values[bit]:
261 new_readout[i] = 1
262 new_readouts.append(new_readout)
263 new_shots = OutcomeArray.from_readouts(new_readouts)
264 else:
265 new_shots = self._shots
266 vals["shots"] = new_shots.choose_indices(chosen_readouts)
268 return StoredResult(**vals)
270 def _permute_statearray_qb_labels(
271 self,
272 array: np.ndarray,
273 relabling_map: dict[Qubit, Qubit],
274 ) -> np.ndarray:
275 """Permute statevector/unitary according to a relabelling of Qubits.
277 :param array: The statevector or unitary
278 :type array: np.ndarray
279 :param relabling_map: Map from original Qubits to new.
280 :type relabling_map: Dict[Qubit, Qubit]
281 :return: Permuted array.
282 :rtype: np.ndarray
283 """
284 original_labeling: Sequence[Qubit] = self.get_qbitlist()
285 n_labels = len(original_labeling)
286 permutation = [0] * n_labels
287 for i, orig_qb in enumerate(original_labeling):
288 permutation[i] = original_labeling.index(relabling_map[orig_qb])
289 if permutation == list(range(n_labels)):
290 # Optimization: nothing to do; return original array.
291 return array
292 permuter = (
293 permute_basis_indexing
294 if len(array.shape) == 1
295 else permute_rows_cols_in_unitary
296 )
297 return permuter(array, tuple(permutation))
299 def _get_state_res(self, qubits: Sequence[Qubit]) -> StoredResult:
300 vals: dict[str, Any] = {}
301 if not self.contains_state_results:
302 raise InvalidResultType("state/unitary/density_matrix")
304 if not _check_permuted_sequence(qubits, self.q_bits):
305 raise ValueError(
306 "For state/unitary/density_matrix results only a permutation of"
307 " all qubits can be requested."
308 )
309 qb_mapping = {selfqb: qubits[index] for selfqb, index in self.q_bits.items()}
310 if self._state is not None:
311 vals["state"] = self._permute_statearray_qb_labels(self._state, qb_mapping)
312 if self._unitary is not None:
313 vals["unitary"] = self._permute_statearray_qb_labels(
314 self._unitary, qb_mapping
315 )
316 if self._density_matrix is not None:
317 vals["density_matrix"] = self._permute_statearray_qb_labels(
318 self._density_matrix, qb_mapping
319 )
320 return StoredResult(**vals)
322 def get_result(
323 self,
324 request_ids: Sequence[UnitID] | None = None,
325 basis: BasisOrder = BasisOrder.ilo,
326 ppcirc: Circuit | None = None,
327 ) -> StoredResult:
328 """Retrieve all results, optionally according to a specified UnitID ordering
329 or subset.
331 :param request_ids: Ordered set of either Qubits or Bits for which to
332 retrieve results, defaults to None in which case all results are returned.
333 For statevector/unitary/density_matrix results some permutation of all
334 qubits must be requested.
335 For measured results (shots/counts), some subset of the relevant bits must
336 be requested.
337 :type request_ids: Optional[Sequence[UnitID]], optional
338 :param basis: Toggle between ILO (increasing lexicographic order of bit ids) and
339 DLO (decreasing lexicographic order) for column ordering if request_ids is
340 None. Defaults to BasisOrder.ilo.
341 :param ppcirc: Classical post-processing circuit to apply to measured results
342 :raises ValueError: Requested UnitIds (request_ids) contain a mixture of qubits
343 and bits.
344 :raises RuntimeError: Classical bits not set.
345 :raises ValueError: Requested (Qu)Bit not in result.
346 :raises RuntimeError: "Qubits not set."
347 :raises ValueError: For state/unitary/density_matrix results only a permutation
348 of all qubits can be requested.
349 :return: All stored results corresponding to requested IDs.
350 :rtype: StoredResult
351 """
352 if request_ids is None:
353 if self.contains_measured_results:
354 request_ids = sorted(
355 self.c_bits.keys(), reverse=(basis == BasisOrder.dlo)
356 )
357 elif self.contains_state_results: 357 ↛ 362line 357 didn't jump to line 362 because the condition on line 357 was always true
358 request_ids = sorted(
359 self.q_bits.keys(), reverse=(basis == BasisOrder.dlo)
360 )
361 else:
362 raise InvalidResultType("No results stored.")
364 if all(i.type == UnitType.bit for i in request_ids):
365 return self._get_measured_res(request_ids, ppcirc) # type: ignore
367 if all(i.type == UnitType.qubit for i in request_ids):
368 return self._get_state_res(request_ids) # type: ignore
370 raise ValueError(
371 "Requested UnitIds (request_ids) contain a mixture of qubits and bits."
372 )
374 def get_shots(
375 self,
376 cbits: Sequence[Bit] | None = None,
377 basis: BasisOrder = BasisOrder.ilo,
378 ppcirc: Circuit | None = None,
379 ) -> np.ndarray:
380 """Return shots if available.
382 :param cbits: ordered subset of Bits, returns all results by default, defaults
383 to None
384 :type cbits: Optional[Sequence[Bit]], optional
385 :param basis: Toggle between ILO (increasing lexicographic order of bit ids) and
386 DLO (decreasing lexicographic order) for column ordering if cbits is None.
387 Defaults to BasisOrder.ilo.
388 :param ppcirc: Classical post-processing circuit to apply to measured results
389 :raises InvalidResultType: Shot results are not available
390 :return: 2D array of readouts, each row a separate outcome and each column a
391 bit value.
392 :rtype: np.ndarray
394 The order of the columns follows the order of `cbits`, if provided.
395 """
396 if cbits is None:
397 cbits = sorted(self.c_bits.keys(), reverse=(basis == BasisOrder.dlo))
398 res = self.get_result(cbits, ppcirc=ppcirc)
399 if res.shots is not None: 399 ↛ 401line 399 didn't jump to line 401 because the condition on line 399 was always true
400 return res.shots.to_readouts()
401 raise InvalidResultType("shots")
403 def get_counts(
404 self,
405 cbits: Sequence[Bit] | None = None,
406 basis: BasisOrder = BasisOrder.ilo,
407 ppcirc: Circuit | None = None,
408 ) -> Counter[tuple[int, ...]]:
409 """Return counts of outcomes if available.
411 :param cbits: ordered subset of Bits, returns all results by default, defaults
412 to None
413 :type cbits: Optional[Sequence[Bit]], optional
414 :param basis: Toggle between ILO (increasing lexicographic order of bit ids) and
415 DLO (decreasing lexicographic order) for column ordering if cbits is None.
416 Defaults to BasisOrder.ilo.
417 :param ppcirc: Classical post-processing circuit to apply to measured results
418 :raises InvalidResultType: Counts are not available
419 :return: Counts of outcomes
420 :rtype: Counter[Tuple(int)]
421 """
422 if cbits is None:
423 cbits = sorted(self.c_bits.keys(), reverse=(basis == BasisOrder.dlo))
424 res = self.get_result(cbits, ppcirc=ppcirc)
425 if res.counts is not None:
426 return readout_counts(res.counts)
427 if res.shots is not None: 427 ↛ 429line 427 didn't jump to line 429 because the condition on line 427 was always true
428 return readout_counts(res.shots.counts())
429 raise InvalidResultType("counts")
431 def get_state(
432 self,
433 qbits: Sequence[Qubit] | None = None,
434 basis: BasisOrder = BasisOrder.ilo,
435 ) -> np.ndarray:
436 """Return statevector if available.
438 :param qbits: permutation of Qubits, defaults to None
439 :type qbits: Optional[Sequence[Qubit]], optional
440 :param basis: Toggle between ILO (increasing lexicographic order of qubit ids)
441 and DLO (decreasing lexicographic order) for column ordering if qbits is
442 None. Defaults to BasisOrder.ilo.
443 :raises InvalidResultType: Statevector not available
444 :return: Statevector, (complex 1-D numpy array)
445 :rtype: np.ndarray
446 """
447 if qbits is None:
448 qbits = sorted(self.q_bits.keys(), reverse=(basis == BasisOrder.dlo))
449 res = self.get_result(qbits)
450 if res.state is not None:
451 return res.state
452 if res.unitary is not None:
453 state: np.ndarray = res.unitary[:, 0]
454 return state
455 raise InvalidResultType("state")
457 def get_unitary(
458 self,
459 qbits: Sequence[Qubit] | None = None,
460 basis: BasisOrder = BasisOrder.ilo,
461 ) -> np.ndarray:
462 """Return unitary if available.
464 :param qbits: permutation of Qubits, defaults to None
465 :type qbits: Optional[Sequence[Qubit]], optional
466 :param basis: Toggle between ILO (increasing lexicographic order of qubit ids)
467 and DLO (decreasing lexicographic order) for column ordering if qbits is
468 None. Defaults to BasisOrder.ilo.
469 :raises InvalidResultType: Statevector not available
470 :return: Unitary, (complex 2-D numpy array)
471 :rtype: np.ndarray
472 """
473 if qbits is None:
474 qbits = sorted(self.q_bits.keys(), reverse=(basis == BasisOrder.dlo))
475 res = self.get_result(qbits)
476 if res.unitary is not None:
477 return res.unitary
478 raise InvalidResultType("unitary")
480 def get_density_matrix(
481 self,
482 qbits: Sequence[Qubit] | None = None,
483 basis: BasisOrder = BasisOrder.ilo,
484 ) -> np.ndarray:
485 """Return density_matrix if available.
487 :param qbits: permutation of Qubits, defaults to None
488 :type qbits: Optional[Sequence[Qubit]], optional
489 :param basis: Toggle between ILO (increasing lexicographic order of qubit ids)
490 and DLO (decreasing lexicographic order) for column ordering if qbits is
491 None. Defaults to BasisOrder.ilo.
492 :raises InvalidResultType: Statevector not available
493 :return: density_matrix, (complex 2-D numpy array)
494 :rtype: np.ndarray
495 """
496 if qbits is None:
497 qbits = sorted(self.q_bits.keys(), reverse=(basis == BasisOrder.dlo))
498 res = self.get_result(qbits)
499 if res.density_matrix is not None:
500 return res.density_matrix
501 raise InvalidResultType("density_matrix")
503 def get_distribution(
504 self, units: Sequence[UnitID] | None = None
505 ) -> dict[tuple[int, ...], float]:
506 """Calculate an exact or approximate probability distribution over outcomes.
508 If the exact statevector is known, the exact probability distribution is
509 returned. Otherwise, if measured results are available the distribution
510 is estimated from these results.
512 This method is deprecated. Please use :py:meth:`get_empirical_distribution` or
513 :py:meth:`get_probability_distribution` instead.
515 DEPRECATED: will be removed after pytket 1.32.
517 :param units: Optionally provide the Qubits or Bits
518 to marginalise the distribution over, defaults to None
519 :type units: Optional[Sequence[UnitID]], optional
520 :return: A distribution as a map from bitstring to probability.
521 :rtype: Dict[Tuple[int, ...], float]
522 """
523 warnings.warn(
524 "The `BackendResult.get_distribution()` method is deprecated: "
525 "please use `get_empirical_distribution()` or "
526 "`get_probability_distribution()` instead.",
527 DeprecationWarning,
528 stacklevel=2,
529 )
530 try:
531 state = self.get_state(units) # type: ignore
532 return probs_from_state(state)
533 except InvalidResultType:
534 counts = self.get_counts(units) # type: ignore
535 total = sum(counts.values())
536 return {outcome: count / total for outcome, count in counts.items()}
538 def get_empirical_distribution(
539 self, bits: Sequence[Bit] | None = None
540 ) -> EmpiricalDistribution[tuple[int, ...]]:
541 """Convert to a :py:class:`pytket.utils.distribution.EmpiricalDistribution`
542 where the observations are sequences of 0s and 1s.
544 :param bits: Optionally provide the :py:class:`Bit` s over which to
545 marginalize the distribution.
546 :return: A distribution where the observations are sequences of 0s and 1s.
547 """ # noqa: RUF002
548 if not self.contains_measured_results:
549 raise InvalidResultType(
550 "Empirical distribution only available for measured result types."
551 )
552 return EmpiricalDistribution(self.get_counts(bits))
554 def get_probability_distribution(
555 self, qubits: Sequence[Qubit] | None = None, min_p: float = 0.0
556 ) -> ProbabilityDistribution[tuple[int, ...]]:
557 """Convert to a :py:class:`pytket.utils.distribution.ProbabilityDistribution`
558 where the possible outcomes are sequences of 0s and 1s.
560 :param qubits: Optionally provide the :py:class:`Qubit` s over which to
561 marginalize the distribution.
562 :param min_p: Optional probability below which to ignore values (for
563 example to avoid spurious values due to rounding errors in
564 statevector computations). Default 0.
565 :return: A distribution where the possible outcomes are tuples of 0s and 1s.
566 """ # noqa: RUF002
567 if not self.contains_state_results:
568 raise InvalidResultType(
569 "Probability distribution only available for statevector result types."
570 )
571 state = self.get_state(qubits)
572 return ProbabilityDistribution(probs_from_state(state), min_p=min_p)
574 def get_debug_info(self) -> dict[str, float]:
575 """Calculate the success rate of each assertion averaged across shots.
577 Each assertion in pytket is decomposed into a sequence of transformations
578 and measurements. An assertion is successful if and only if all its associated
579 measurements yield the correct results.
581 :return: The debug results as a map from assertion to average success rate.
582 :rtype: Dict[str, float]
583 """
584 _tket_debug_zero_prefix = _DEBUG_ZERO_REG_PREFIX + "_"
585 _tket_debug_one_prefix = _DEBUG_ONE_REG_PREFIX + "_"
586 debug_bit_dict: dict[str, dict[str, Any]] = {}
587 for bit in self.c_bits:
588 if bit.reg_name.startswith(_tket_debug_zero_prefix): 588 ↛ 591line 588 didn't jump to line 591 because the condition on line 588 was always true
589 expectation = 0
590 assertion_name = bit.reg_name.split(_tket_debug_zero_prefix, 1)[1]
591 elif bit.reg_name.startswith(_tket_debug_one_prefix):
592 expectation = 1
593 assertion_name = bit.reg_name.split(_tket_debug_one_prefix, 1)[1]
594 else:
595 continue
596 if assertion_name not in debug_bit_dict:
597 debug_bit_dict[assertion_name] = {"bits": [], "expectations": []}
598 debug_bit_dict[assertion_name]["bits"].append(bit)
599 debug_bit_dict[assertion_name]["expectations"].append(expectation)
601 debug_result_dict: dict[str, float] = {}
602 for assertion_name, bits_info in debug_bit_dict.items():
603 counts = self.get_counts(bits_info["bits"])
604 debug_result_dict[assertion_name] = counts[
605 tuple(bits_info["expectations"])
606 ] / sum(counts.values())
607 return debug_result_dict
609 def to_dict(self) -> dict[str, Any]:
610 """Generate a dictionary serialized representation of BackendResult,
611 suitable for writing to JSON.
613 :return: JSON serializable dictionary.
614 :rtype: Dict[str, Any]
615 """
616 outdict: dict[str, Any] = {}
617 outdict["qubits"] = [q.to_list() for q in self.get_qbitlist()]
618 outdict["bits"] = [c.to_list() for c in self.get_bitlist()]
619 if self._shots is not None:
620 outdict["shots"] = self._shots.to_dict()
621 if self._counts is not None:
622 outdict["counts"] = [
623 {"outcome": oc.to_dict(), "count": count}
624 for oc, count in self._counts.items()
625 ]
627 if self._state is not None:
628 outdict["state"] = _complex_ar_to_dict(self._state)
629 if self._unitary is not None:
630 outdict["unitary"] = _complex_ar_to_dict(self._unitary)
631 if self._density_matrix is not None:
632 outdict["density_matrix"] = _complex_ar_to_dict(self._density_matrix)
634 return outdict
636 @classmethod
637 def from_dict(cls, res_dict: dict[str, Any]) -> "BackendResult":
638 """Construct BackendResult object from JSON serializable dictionary
639 representation, as generated by BackendResult.to_dict.
641 :return: Instance of BackendResult constructed from dictionary.
642 :rtype: BackendResult
643 """
644 init_dict = dict.fromkeys(
645 (
646 "q_bits",
647 "c_bits",
648 "shots",
649 "counts",
650 "state",
651 "unitary",
652 "density_matrix",
653 )
654 )
656 if "qubits" in res_dict: 656 ↛ 658line 656 didn't jump to line 658 because the condition on line 656 was always true
657 init_dict["q_bits"] = [Qubit.from_list(tup) for tup in res_dict["qubits"]]
658 if "bits" in res_dict: 658 ↛ 660line 658 didn't jump to line 660 because the condition on line 658 was always true
659 init_dict["c_bits"] = [Bit.from_list(tup) for tup in res_dict["bits"]]
660 if "shots" in res_dict:
661 init_dict["shots"] = OutcomeArray.from_dict(res_dict["shots"])
662 if "counts" in res_dict:
663 init_dict["counts"] = Counter(
664 {
665 OutcomeArray.from_dict(elem["outcome"]): elem["count"]
666 for elem in res_dict["counts"]
667 }
668 )
669 if "state" in res_dict:
670 init_dict["state"] = _complex_ar_from_dict(res_dict["state"])
671 if "unitary" in res_dict:
672 init_dict["unitary"] = _complex_ar_from_dict(res_dict["unitary"])
673 if "density_matrix" in res_dict:
674 init_dict["density_matrix"] = _complex_ar_from_dict(
675 res_dict["density_matrix"]
676 )
678 return BackendResult(**init_dict)
681T = TypeVar("T")
684def _sort_keys_by_val(dic: dict[T, int]) -> list[T]:
685 if not dic:
686 return []
687 vals, _ = zip(*sorted(dic.items(), key=lambda x: x[1]), strict=False)
688 return list(cast("Iterable[T]", vals))
691def _check_permuted_sequence(first: Collection[Any], second: Collection[Any]) -> bool:
692 return len(first) == len(second) and set(first) == set(second)
695def _complex_ar_to_dict(ar: np.ndarray) -> dict[str, list]:
696 """Dictionary of real, imaginary parts of complex array, each in list form."""
697 return {"real": ar.real.tolist(), "imag": ar.imag.tolist()}
700def _complex_ar_from_dict(dic: dict[str, list]) -> np.ndarray:
701 """Construct complex array from dictionary of real and imaginary parts"""
703 out = np.array(dic["real"], dtype=complex)
704 out.imag = np.array(dic["imag"], dtype=float)
705 return out