Example #1
0
class ConstantUnitaryGate(ConstantGate):
    """A constant unitary operator."""
    def __init__(
        self,
        utry: UnitaryLike,
        radixes: Sequence[int] = [],
    ) -> None:
        self.utry = UnitaryMatrix(utry, radixes)
        self.size = self.utry.get_size()
        self.radixes = self.utry.get_radixes()
Example #2
0
    def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
        """Synthesize `utry` into a circuit, see SynthesisPass for more info."""

        # 0. Skip any unitaries too small for the configured block.
        if self.block_size_start > utry.get_size():
            _logger.warning(
                'Skipping synthesis: block size is larger than input unitary.',
            )
            return Circuit.from_unitary(utry)

        # 1. Create empty circuit with same size and radixes as `utry`.
        circuit = Circuit(utry.get_size(), utry.get_radixes())

        # 2. Calculate block sizes
        block_size_end = utry.get_size() - 1
        if self.block_size_limit is not None:
            block_size_end = self.block_size_limit

        # 3. Calculate relevant coupling_graphs
        # TODO: Look for topology info in `data`, use all-to-all otherwise.
        model = MachineModel(utry.get_size())
        locations = [
            model.get_locations(i)
            for i in range(self.block_size_start, block_size_end + 1)
        ]

        # 3. Bottom-up synthesis: build circuit up one gate at a time
        layer = 1
        last_cost = 1.0
        last_loc = None

        while True:
            remainder = utry.get_dagger() @ circuit.get_unitary()
            sorted_locations = self.analyze_remainder(remainder, locations)

            for loc in sorted_locations:

                # Never predict the previous location
                if loc == last_loc:
                    continue

                _logger.info(f'Trying next predicted location {loc}.')
                circuit.append_gate(VariableUnitaryGate(len(loc)), loc)
                circuit.instantiate(
                    utry,
                    **self.instantiate_options,  # type: ignore
                )
                cost = self.cost.calc_cost(circuit, utry)
                _logger.info(f'Instantiated; layers: {layer}, cost: {cost:e}.')

                if cost < self.success_threshold:
                    _logger.info(f'Circuit found with cost: {cost:e}.')
                    _logger.info('Successful synthesis.')
                    return circuit

                progress_threshold = self.progress_threshold_a
                progress_threshold += self.progress_threshold_r * np.abs(cost)
                if last_cost - cost >= progress_threshold:
                    _logger.info('Progress has been made, depth increasing.')
                    last_loc = loc
                    last_cost = cost
                    layer += 1
                    break

                _logger.info('Progress has not been made.')
                circuit.pop((-1, loc[0]))
Example #3
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)