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

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. 

14 

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 

22 

23import numpy as np 

24 

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) 

42 

43from .backend_exceptions import InvalidResultType 

44 

45 

46class StoredResult(NamedTuple): 

47 """NamedTuple with optional fields for all result types.""" 

48 

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 

54 

55 

56class BackendResult: 

57 """Encapsulate generic results from pytket Backend instances. 

58 

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). 

61 

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. 

64 

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 """ 

76 

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 = [] 

94 

95 self._counts = counts 

96 self._shots = shots 

97 

98 self._state = state 

99 self._unitary = unitary 

100 self._density_matrix = density_matrix 

101 

102 self._ppcirc = ppcirc 

103 

104 self.c_bits: dict[Bit, int] = dict() 

105 self.q_bits: dict[Qubit, int] = dict() 

106 

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 

119 

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) 

131 

132 if self._shots is not None: 

133 _bitlength = self._shots.width 

134 

135 _process_unitids(c_bits, "c_bits", _bitlength, Bit) 

136 

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])) 

145 

146 _process_unitids(q_bits, "q_bits", _n_qubits, Qubit) 

147 

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 ) 

154 

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) 

159 

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 ) 

169 

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 ) 

185 

186 def get_bitlist(self) -> list[Bit]: 

187 """Return list of Bits in internal storage order. 

188 

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) 

194 

195 def get_qbitlist(self) -> list[Qubit]: 

196 """Return list of Qubits in internal storage order. 

197 

198 :raises AttributeError: BackendResult does not include a Qubits list. 

199 :return: Sorted list of Qubits. 

200 :rtype: List[Qubit] 

201 """ 

202 

203 return _sort_keys_by_val(self.q_bits) 

204 

205 def _get_measured_res( 

206 self, bits: Sequence[Bit], ppcirc: Circuit | None = None 

207 ) -> StoredResult: 

208 vals: dict[str, Any] = {} 

209 

210 if not self.contains_measured_results: 

211 raise InvalidResultType("shots/counts") 

212 

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.") 

218 

219 try: 

220 chosen_readouts = [self.c_bits[bit] for bit in bits] 

221 except KeyError: 

222 raise ValueError("Requested Bit not in result.") 

223 

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) 

264 

265 return StoredResult(**vals) 

266 

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. 

273 

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)) 

295 

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") 

300 

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) 

318 

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. 

327 

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.") 

360 

361 if all(isinstance(i, Bit) for i in request_ids): 

362 return self._get_measured_res(request_ids, ppcirc) # type: ignore 

363 

364 if all(isinstance(i, Qubit) for i in request_ids): 

365 return self._get_state_res(request_ids) # type: ignore 

366 

367 raise ValueError( 

368 "Requested UnitIds (request_ids) contain a mixture of qubits and bits." 

369 ) 

370 

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. 

378 

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 

390 

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") 

399 

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. 

407 

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") 

427 

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. 

434 

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") 

453 

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. 

460 

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") 

476 

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. 

483 

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") 

499 

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. 

504 

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. 

508 

509 This method is deprecated. Please use :py:meth:`get_empirical_distribution` or 

510 :py:meth:`get_probability_distribution` instead. 

511 

512 DEPRECATED: will be removed after pytket 1.32. 

513 

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()} 

533 

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. 

539 

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)) 

549 

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. 

555 

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) 

569 

570 def get_debug_info(self) -> dict[str, float]: 

571 """Calculate the success rate of each assertion averaged across shots. 

572 

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. 

576 

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) 

596 

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 

604 

605 def to_dict(self) -> dict[str, Any]: 

606 """Generate a dictionary serialized representation of BackendResult, 

607 suitable for writing to JSON. 

608 

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 ] 

622 

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) 

629 

630 return outdict 

631 

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. 

636 

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 ) 

651 

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 ) 

673 

674 return BackendResult(**init_dict) 

675 

676 

677T = TypeVar("T") 

678 

679 

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)) 

685 

686 

687def _check_permuted_sequence(first: Collection[Any], second: Collection[Any]) -> bool: 

688 return len(first) == len(second) and set(first) == set(second) 

689 

690 

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()} 

694 

695 

696def _complex_ar_from_dict(dic: dict[str, list]) -> np.ndarray: 

697 """Construct complex array from dictionary of real and imaginary parts""" 

698 

699 out = np.array(dic["real"], dtype=complex) 

700 out.imag = np.array(dic["imag"], dtype=float) 

701 return out