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