Coverage for /home/runner/work/tket/tket/pytket/pytket/backends/backendresult.py: 90%
278 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-02 12:44 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-02 12:44 +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 """
195 return _sort_keys_by_val(self.c_bits)
197 def get_qbitlist(self) -> list[Qubit]:
198 """Return list of Qubits in internal storage order.
200 :raises AttributeError: BackendResult does not include a Qubits list.
201 :return: Sorted list of Qubits.
202 """
204 return _sort_keys_by_val(self.q_bits)
206 def _get_measured_res( # noqa: PLR0912
207 self, bits: Sequence[Bit], ppcirc: Circuit | None = None
208 ) -> StoredResult:
209 vals: dict[str, Any] = {}
211 if not self.contains_measured_results:
212 raise InvalidResultType("shots/counts")
214 if self._ppcirc is not None:
215 if ppcirc is None: 215 ↛ 218line 215 didn't jump to line 218 because the condition on line 215 was always true
216 ppcirc = self._ppcirc
217 else:
218 raise ValueError("Postprocessing circuit already provided.")
220 try:
221 chosen_readouts = [self.c_bits[bit] for bit in bits]
222 except KeyError:
223 raise ValueError("Requested Bit not in result.") # noqa: B904
225 if self._counts is not None:
226 if ppcirc is not None:
227 # Modify self._counts:
228 new_counts: Counter[OutcomeArray] = Counter()
229 for oa, n in self._counts.items():
230 readout = oa.to_readout()
231 values = {bit: bool(readout[i]) for bit, i in self.c_bits.items()}
232 new_values = ppcirc._classical_eval(values) # noqa: SLF001
233 new_oa = OutcomeArray.from_readouts(
234 [[int(new_values[bit]) for bit in bits]]
235 )
236 new_counts[new_oa] += n
237 else:
238 new_counts = self._counts
239 vals["counts"] = reduce(
240 operator.add,
241 (
242 Counter({outcome.choose_indices(chosen_readouts): count})
243 for outcome, count in new_counts.items()
244 ),
245 Counter(),
246 )
247 if self._shots is not None:
248 if ppcirc is not None:
249 # Modify self._shots:
250 readouts = self._shots.to_readouts()
251 new_readouts = []
252 for i in range(self._shots.n_outcomes):
253 readout = readouts[i, :]
254 values = {bit: bool(readout[i]) for bit, i in self.c_bits.items()}
255 new_values = ppcirc._classical_eval(values) # noqa: SLF001
256 new_readout = [0] * self._shots.width
257 for bit, i in self.c_bits.items(): # noqa: PLW2901
258 if new_values[bit]:
259 new_readout[i] = 1
260 new_readouts.append(new_readout)
261 new_shots = OutcomeArray.from_readouts(new_readouts)
262 else:
263 new_shots = self._shots
264 vals["shots"] = new_shots.choose_indices(chosen_readouts)
266 return StoredResult(**vals)
268 def _permute_statearray_qb_labels(
269 self,
270 array: np.ndarray,
271 relabling_map: dict[Qubit, Qubit],
272 ) -> np.ndarray:
273 """Permute statevector/unitary according to a relabelling of Qubits.
275 :param array: The statevector or unitary
276 :param relabling_map: Map from original Qubits to new.
277 :return: Permuted array.
278 """
279 original_labeling: Sequence[Qubit] = self.get_qbitlist()
280 n_labels = len(original_labeling)
281 permutation = [0] * n_labels
282 for i, orig_qb in enumerate(original_labeling):
283 permutation[i] = original_labeling.index(relabling_map[orig_qb])
284 if permutation == list(range(n_labels)):
285 # Optimization: nothing to do; return original array.
286 return array
287 permuter = (
288 permute_basis_indexing
289 if len(array.shape) == 1
290 else permute_rows_cols_in_unitary
291 )
292 return permuter(array, tuple(permutation))
294 def _get_state_res(self, qubits: Sequence[Qubit]) -> StoredResult:
295 vals: dict[str, Any] = {}
296 if not self.contains_state_results:
297 raise InvalidResultType("state/unitary/density_matrix")
299 if not _check_permuted_sequence(qubits, self.q_bits):
300 raise ValueError(
301 "For state/unitary/density_matrix results only a permutation of"
302 " all qubits can be requested."
303 )
304 qb_mapping = {selfqb: qubits[index] for selfqb, index in self.q_bits.items()}
305 if self._state is not None:
306 vals["state"] = self._permute_statearray_qb_labels(self._state, qb_mapping)
307 if self._unitary is not None:
308 vals["unitary"] = self._permute_statearray_qb_labels(
309 self._unitary, qb_mapping
310 )
311 if self._density_matrix is not None:
312 vals["density_matrix"] = self._permute_statearray_qb_labels(
313 self._density_matrix, qb_mapping
314 )
315 return StoredResult(**vals)
317 def get_result(
318 self,
319 request_ids: Sequence[UnitID] | None = None,
320 basis: BasisOrder = BasisOrder.ilo,
321 ppcirc: Circuit | None = None,
322 ) -> StoredResult:
323 """Retrieve all results, optionally according to a specified UnitID ordering
324 or subset.
326 :param request_ids: Ordered set of either Qubits or Bits for which to
327 retrieve results, defaults to None in which case all results are returned.
328 For statevector/unitary/density_matrix results some permutation of all
329 qubits must be requested.
330 For measured results (shots/counts), some subset of the relevant bits must
331 be requested.
332 :param basis: Toggle between ILO (increasing lexicographic order of bit ids) and
333 DLO (decreasing lexicographic order) for column ordering if request_ids is
334 None. Defaults to BasisOrder.ilo.
335 :param ppcirc: Classical post-processing circuit to apply to measured results
336 :raises ValueError: Requested UnitIds (request_ids) contain a mixture of qubits
337 and bits.
338 :raises RuntimeError: Classical bits not set.
339 :raises ValueError: Requested (Qu)Bit not in result.
340 :raises RuntimeError: "Qubits not set."
341 :raises ValueError: For state/unitary/density_matrix results only a permutation
342 of all qubits can be requested.
343 :return: All stored results corresponding to requested IDs.
344 """
345 if request_ids is None:
346 if self.contains_measured_results:
347 request_ids = sorted(
348 self.c_bits.keys(), reverse=(basis == BasisOrder.dlo)
349 )
350 elif self.contains_state_results: 350 ↛ 355line 350 didn't jump to line 355 because the condition on line 350 was always true
351 request_ids = sorted(
352 self.q_bits.keys(), reverse=(basis == BasisOrder.dlo)
353 )
354 else:
355 raise InvalidResultType("No results stored.")
357 if all(i.type == UnitType.bit for i in request_ids):
358 return self._get_measured_res(request_ids, ppcirc) # type: ignore
360 if all(i.type == UnitType.qubit for i in request_ids):
361 return self._get_state_res(request_ids) # type: ignore
363 raise ValueError(
364 "Requested UnitIds (request_ids) contain a mixture of qubits and bits."
365 )
367 def get_shots(
368 self,
369 cbits: Sequence[Bit] | None = None,
370 basis: BasisOrder = BasisOrder.ilo,
371 ppcirc: Circuit | None = None,
372 ) -> np.ndarray:
373 """Return shots if available.
375 :param cbits: ordered subset of Bits, returns all results by default, defaults
376 to None
377 :param basis: Toggle between ILO (increasing lexicographic order of bit ids) and
378 DLO (decreasing lexicographic order) for column ordering if cbits is None.
379 Defaults to BasisOrder.ilo.
380 :param ppcirc: Classical post-processing circuit to apply to measured results
381 :raises InvalidResultType: Shot results are not available
382 :return: 2D array of readouts, each row a separate outcome and each column a
383 bit value.
385 The order of the columns follows the order of `cbits`, if provided.
386 """
387 if cbits is None:
388 cbits = sorted(self.c_bits.keys(), reverse=(basis == BasisOrder.dlo))
389 res = self.get_result(cbits, ppcirc=ppcirc)
390 if res.shots is not None: 390 ↛ 392line 390 didn't jump to line 392 because the condition on line 390 was always true
391 return res.shots.to_readouts()
392 raise InvalidResultType("shots")
394 def get_counts(
395 self,
396 cbits: Sequence[Bit] | None = None,
397 basis: BasisOrder = BasisOrder.ilo,
398 ppcirc: Circuit | None = None,
399 ) -> Counter[tuple[int, ...]]:
400 """Return counts of outcomes if available.
402 :param cbits: ordered subset of Bits, returns all results by default, defaults
403 to None
404 :param basis: Toggle between ILO (increasing lexicographic order of bit ids) and
405 DLO (decreasing lexicographic order) for column ordering if cbits is None.
406 Defaults to BasisOrder.ilo.
407 :param ppcirc: Classical post-processing circuit to apply to measured results
408 :raises InvalidResultType: Counts are not available
409 :return: Counts of outcomes
410 """
411 if cbits is None:
412 cbits = sorted(self.c_bits.keys(), reverse=(basis == BasisOrder.dlo))
413 res = self.get_result(cbits, ppcirc=ppcirc)
414 if res.counts is not None:
415 return readout_counts(res.counts)
416 if res.shots is not None: 416 ↛ 418line 416 didn't jump to line 418 because the condition on line 416 was always true
417 return readout_counts(res.shots.counts())
418 raise InvalidResultType("counts")
420 def get_state(
421 self,
422 qbits: Sequence[Qubit] | None = None,
423 basis: BasisOrder = BasisOrder.ilo,
424 ) -> np.ndarray:
425 """Return statevector if available.
427 :param qbits: permutation of Qubits, defaults to None
428 :param basis: Toggle between ILO (increasing lexicographic order of qubit ids)
429 and DLO (decreasing lexicographic order) for column ordering if qbits is
430 None. Defaults to BasisOrder.ilo.
431 :raises InvalidResultType: Statevector not available
432 :return: Statevector, (complex 1-D numpy array)
433 """
434 if qbits is None:
435 qbits = sorted(self.q_bits.keys(), reverse=(basis == BasisOrder.dlo))
436 res = self.get_result(qbits)
437 if res.state is not None:
438 return res.state
439 if res.unitary is not None:
440 state: np.ndarray = res.unitary[:, 0]
441 return state
442 raise InvalidResultType("state")
444 def get_unitary(
445 self,
446 qbits: Sequence[Qubit] | None = None,
447 basis: BasisOrder = BasisOrder.ilo,
448 ) -> np.ndarray:
449 """Return unitary if available.
451 :param qbits: permutation of Qubits, defaults to None
452 :param basis: Toggle between ILO (increasing lexicographic order of qubit ids)
453 and DLO (decreasing lexicographic order) for column ordering if qbits is
454 None. Defaults to BasisOrder.ilo.
455 :raises InvalidResultType: Statevector not available
456 :return: Unitary, (complex 2-D numpy array)
457 """
458 if qbits is None:
459 qbits = sorted(self.q_bits.keys(), reverse=(basis == BasisOrder.dlo))
460 res = self.get_result(qbits)
461 if res.unitary is not None:
462 return res.unitary
463 raise InvalidResultType("unitary")
465 def get_density_matrix(
466 self,
467 qbits: Sequence[Qubit] | None = None,
468 basis: BasisOrder = BasisOrder.ilo,
469 ) -> np.ndarray:
470 """Return density_matrix if available.
472 :param qbits: permutation of Qubits, defaults to None
473 :param basis: Toggle between ILO (increasing lexicographic order of qubit ids)
474 and DLO (decreasing lexicographic order) for column ordering if qbits is
475 None. Defaults to BasisOrder.ilo.
476 :raises InvalidResultType: Statevector not available
477 :return: density_matrix, (complex 2-D numpy array)
478 """
479 if qbits is None:
480 qbits = sorted(self.q_bits.keys(), reverse=(basis == BasisOrder.dlo))
481 res = self.get_result(qbits)
482 if res.density_matrix is not None:
483 return res.density_matrix
484 raise InvalidResultType("density_matrix")
486 def get_distribution(
487 self, units: Sequence[UnitID] | None = None
488 ) -> dict[tuple[int, ...], float]:
489 """Calculate an exact or approximate probability distribution over outcomes.
491 If the exact statevector is known, the exact probability distribution is
492 returned. Otherwise, if measured results are available the distribution
493 is estimated from these results.
495 This method is deprecated. Please use :py:meth:`get_empirical_distribution` or
496 :py:meth:`get_probability_distribution` instead.
498 DEPRECATED: will be removed after pytket 1.32.
500 :param units: Optionally provide the Qubits or Bits
501 to marginalise the distribution over, defaults to None
502 :return: A distribution as a map from bitstring to probability.
503 """
504 warnings.warn(
505 "The `BackendResult.get_distribution()` method is deprecated: "
506 "please use `get_empirical_distribution()` or "
507 "`get_probability_distribution()` instead.",
508 DeprecationWarning,
509 stacklevel=2,
510 )
511 try:
512 state = self.get_state(units) # type: ignore
513 return probs_from_state(state)
514 except InvalidResultType:
515 counts = self.get_counts(units) # type: ignore
516 total = sum(counts.values())
517 return {outcome: count / total for outcome, count in counts.items()}
519 def get_empirical_distribution(
520 self, bits: Sequence[Bit] | None = None
521 ) -> EmpiricalDistribution[tuple[int, ...]]:
522 """Convert to a :py:class:`pytket.utils.distribution.EmpiricalDistribution`
523 where the observations are sequences of 0s and 1s.
525 :param bits: Optionally provide the :py:class:`~.Bit` s over which to
526 marginalize the distribution.
527 :return: A distribution where the observations are sequences of 0s and 1s.
528 """ # noqa: RUF002
529 if not self.contains_measured_results:
530 raise InvalidResultType(
531 "Empirical distribution only available for measured result types."
532 )
533 return EmpiricalDistribution(self.get_counts(bits))
535 def get_probability_distribution(
536 self, qubits: Sequence[Qubit] | None = None, min_p: float = 0.0
537 ) -> ProbabilityDistribution[tuple[int, ...]]:
538 """Convert to a :py:class:`pytket.utils.distribution.ProbabilityDistribution`
539 where the possible outcomes are sequences of 0s and 1s.
541 :param qubits: Optionally provide the :py:class:`~.Qubit` s over which to
542 marginalize the distribution.
543 :param min_p: Optional probability below which to ignore values (for
544 example to avoid spurious values due to rounding errors in
545 statevector computations). Default 0.
546 :return: A distribution where the possible outcomes are tuples of 0s and 1s.
547 """ # noqa: RUF002
548 if not self.contains_state_results:
549 raise InvalidResultType(
550 "Probability distribution only available for statevector result types."
551 )
552 state = self.get_state(qubits)
553 return ProbabilityDistribution(probs_from_state(state), min_p=min_p)
555 def get_debug_info(self) -> dict[str, float]:
556 """Calculate the success rate of each assertion averaged across shots.
558 Each assertion in pytket is decomposed into a sequence of transformations
559 and measurements. An assertion is successful if and only if all its associated
560 measurements yield the correct results.
562 :return: The debug results as a map from assertion to average success rate.
563 """
564 _tket_debug_zero_prefix = _DEBUG_ZERO_REG_PREFIX + "_"
565 _tket_debug_one_prefix = _DEBUG_ONE_REG_PREFIX + "_"
566 debug_bit_dict: dict[str, dict[str, Any]] = {}
567 for bit in self.c_bits:
568 if bit.reg_name.startswith(_tket_debug_zero_prefix): 568 ↛ 571line 568 didn't jump to line 571 because the condition on line 568 was always true
569 expectation = 0
570 assertion_name = bit.reg_name.split(_tket_debug_zero_prefix, 1)[1]
571 elif bit.reg_name.startswith(_tket_debug_one_prefix):
572 expectation = 1
573 assertion_name = bit.reg_name.split(_tket_debug_one_prefix, 1)[1]
574 else:
575 continue
576 if assertion_name not in debug_bit_dict:
577 debug_bit_dict[assertion_name] = {"bits": [], "expectations": []}
578 debug_bit_dict[assertion_name]["bits"].append(bit)
579 debug_bit_dict[assertion_name]["expectations"].append(expectation)
581 debug_result_dict: dict[str, float] = {}
582 for assertion_name, bits_info in debug_bit_dict.items():
583 counts = self.get_counts(bits_info["bits"])
584 debug_result_dict[assertion_name] = counts[
585 tuple(bits_info["expectations"])
586 ] / sum(counts.values())
587 return debug_result_dict
589 def to_dict(self) -> dict[str, Any]:
590 """Generate a dictionary serialized representation of BackendResult,
591 suitable for writing to JSON.
593 :return: JSON serializable dictionary.
594 """
595 outdict: dict[str, Any] = {}
596 outdict["qubits"] = [q.to_list() for q in self.get_qbitlist()]
597 outdict["bits"] = [c.to_list() for c in self.get_bitlist()]
598 if self._shots is not None:
599 outdict["shots"] = self._shots.to_dict()
600 if self._counts is not None:
601 outdict["counts"] = [
602 {"outcome": oc.to_dict(), "count": count}
603 for oc, count in self._counts.items()
604 ]
606 if self._state is not None:
607 outdict["state"] = _complex_ar_to_dict(self._state)
608 if self._unitary is not None:
609 outdict["unitary"] = _complex_ar_to_dict(self._unitary)
610 if self._density_matrix is not None:
611 outdict["density_matrix"] = _complex_ar_to_dict(self._density_matrix)
613 return outdict
615 @classmethod
616 def from_dict(cls, res_dict: dict[str, Any]) -> "BackendResult":
617 """Construct BackendResult object from JSON serializable dictionary
618 representation, as generated by BackendResult.to_dict.
620 :return: Instance of BackendResult constructed from dictionary.
621 """
622 init_dict = dict.fromkeys(
623 (
624 "q_bits",
625 "c_bits",
626 "shots",
627 "counts",
628 "state",
629 "unitary",
630 "density_matrix",
631 )
632 )
634 if "qubits" in res_dict: 634 ↛ 636line 634 didn't jump to line 636 because the condition on line 634 was always true
635 init_dict["q_bits"] = [Qubit.from_list(tup) for tup in res_dict["qubits"]]
636 if "bits" in res_dict: 636 ↛ 638line 636 didn't jump to line 638 because the condition on line 636 was always true
637 init_dict["c_bits"] = [Bit.from_list(tup) for tup in res_dict["bits"]]
638 if "shots" in res_dict:
639 init_dict["shots"] = OutcomeArray.from_dict(res_dict["shots"])
640 if "counts" in res_dict:
641 init_dict["counts"] = Counter(
642 {
643 OutcomeArray.from_dict(elem["outcome"]): elem["count"]
644 for elem in res_dict["counts"]
645 }
646 )
647 if "state" in res_dict:
648 init_dict["state"] = _complex_ar_from_dict(res_dict["state"])
649 if "unitary" in res_dict:
650 init_dict["unitary"] = _complex_ar_from_dict(res_dict["unitary"])
651 if "density_matrix" in res_dict:
652 init_dict["density_matrix"] = _complex_ar_from_dict(
653 res_dict["density_matrix"]
654 )
656 return BackendResult(**init_dict)
659T = TypeVar("T")
662def _sort_keys_by_val(dic: dict[T, int]) -> list[T]:
663 if not dic:
664 return []
665 vals, _ = zip(*sorted(dic.items(), key=lambda x: x[1]), strict=False)
666 return list(cast("Iterable[T]", vals))
669def _check_permuted_sequence(first: Collection[Any], second: Collection[Any]) -> bool:
670 return len(first) == len(second) and set(first) == set(second)
673def _complex_ar_to_dict(ar: np.ndarray) -> dict[str, list]:
674 """Dictionary of real, imaginary parts of complex array, each in list form."""
675 return {"real": ar.real.tolist(), "imag": ar.imag.tolist()}
678def _complex_ar_from_dict(dic: dict[str, list]) -> np.ndarray:
679 """Construct complex array from dictionary of real and imaginary parts"""
681 out = np.array(dic["real"], dtype=complex)
682 out.imag = np.array(dic["imag"], dtype=float)
683 return out