Exemple #1
0
    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
Exemple #2
0
    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)