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