def analyze_remainder( self, R: UnitaryMatrix, locations: Sequence[Sequence[CircuitLocation]], ) -> list[CircuitLocation]: """ Perform remainder analysis on `R` to sort `locations`. Args: R (UnitaryMatrix): The remainder to analyze. locations (Sequence[Sequence[CircuitLocation]]): List of locations grouped by block size. Returns: (list[CircuitLocation]): Sorted list of locations for next block based on remainder analysis. """ _logger.info('Performing remainder analysis.') pauli_coefs = pauli_expansion(unitary_log_no_i(R.get_numpy())) locations_by_index: dict[int, set[CircuitLocation]] = {} for location_group in locations: for loc in location_group: for qudit_index in loc: if qudit_index not in locations_by_index: locations_by_index[qudit_index] = set() locations_by_index[qudit_index].add(loc) location_scores_by_size: dict[int, dict[CircuitLocation, float]] = {} for location_group in locations: for loc in location_group: if len(loc) not in location_scores_by_size: location_scores_by_size[len(loc)] = {} location_scores_by_size[len(loc)][loc] = 0.0 for coef_idx, coef in enumerate(pauli_coefs): for qudit_index in self.decode_qubits(coef_idx): for loc in locations_by_index[qudit_index]: location_scores_by_size[len(loc)][loc] += np.abs(coef) sorted_locations_by_size: dict[int, list[CircuitLocation]] = { size: sorted( list(location_scores.keys()), key=lambda x: location_scores[x], reverse=True, ) for size, location_scores in location_scores_by_size.items() } sorted_locations_length: dict[int, int] = { size: len(sorted_locations) for size, sorted_locations in sorted_locations_by_size.items() } sorted_locations_by_sorted_size = sorted( sorted_locations_by_size.items(), key=lambda x: x[0], ) sorted_locations: list[CircuitLocation] = [] for size, locations in sorted_locations_by_sorted_size: for i in range(self.fail_limit): if i < sorted_locations_length[size]: sorted_locations.append(locations[i]) sorted_locations.extend(sorted_locations_by_sorted_size[-1][1]) _logger.debug('Found order of locations:') _logger.debug(sorted_locations) return sorted_locations
def apply_right( self, utry: UnitaryMatrix, location: Sequence[int], inverse: bool = False, ) -> None: """ Apply the specified unitary on the right of this UnitaryBuilder. .-----. .------. 0 -| |---| |- 1 -| |---| utry |- . . '------' . . . . n-1 -| |------------ '-----' Args: utry (UnitaryMatrix): The unitary to apply. location (Sequence[int]): The qudits to apply the unitary on. inverse (bool): If true, apply the inverse of the unitary. Notes: Applying the unitary on the right is equivalent to multiplying the unitary on the left of the tensor. This operation is performed using tensor contraction. """ if not isinstance(utry, UnitaryMatrix): raise TypeError('Expected UnitaryMatrix, got %s', type(utry)) if not CircuitLocation.is_location(location, self.get_size()): raise TypeError('Invalid location.') if len(location) != utry.get_size(): raise ValueError('Unitary and location size mismatch.') for utry_radix, bldr_radix_idx in zip(utry.get_radixes(), location): if utry_radix != self.get_radixes()[bldr_radix_idx]: raise ValueError('Unitary and location radix mismatch.') left_perm = list(location) mid_perm = [x for x in range(self.get_size()) if x not in location] right_perm = [x + self.get_size() for x in range(self.get_size())] left_dim = int(np.prod([self.get_radixes()[x] for x in left_perm])) utry = utry.get_dagger() if inverse else utry utry_np = utry.get_numpy() perm = left_perm + mid_perm + right_perm self.tensor = self.tensor.transpose(perm) self.tensor = self.tensor.reshape((left_dim, -1)) self.tensor = utry_np @ self.tensor shape = list(self.get_radixes()) * 2 shape = [shape[p] for p in perm] self.tensor = self.tensor.reshape(shape) inv_perm = list(np.argsort(perm)) self.tensor = self.tensor.transpose(inv_perm)