def from_qubit_location( num_qubits: int, location: Sequence[int], ) -> PermutationMatrix: """ Creates the permutation matrix specified by arguments. The resulting matrix will move the first len(location) qubits into positions defined by location. Args: num_qubits (int): Total number of qubits location (Sequence[int]): The desired locations to swap the starting qubits to. Returns: (PermutationMatrix): A 2**num_qubits by 2**num_qubits permutation matrix. Examples: calc_permutation_matrix( 2, (0, 1) ) = [ [ 1, 0, 0, 0 ], [ 0, 1, 0, 0 ], [ 0, 0, 1, 0 ], [ 0, 0, 0, 1 ] ] Here the 4x4 identity is returned because there are 2 total qubits, specified by the first parameter, and the desired permutation is [0, 1] -> [0, 1]. calc_permutation_matrix( 2, (1,) ) = # Also equals calc_permutation_matrix( 2, (1, 0) ) = [ [ 1, 0, 0, 0 ], [ 0, 0, 1, 0 ], [ 0, 1, 0, 0 ], [ 0, 0, 0, 1 ] ] This is a more interesting example. The swap gate is returned here since we are working with 2 qubits and want the permutation that swaps the two qubits, giving by the permutation [0] -> [1] or in the second case [0, 1] -> [1, 0]. Both calls produce identical permutations. """ if not isinstance(num_qubits, int): raise TypeError( 'Expected integer num_qudits' ', got %s.' % type(num_qubits), ) if num_qubits <= 0: raise ValueError( 'Expected positive num_qudits' ', got %d.' % num_qubits, ) if not CircuitLocation.is_location(location, num_qubits): raise TypeError('Invalid location.') matrix = calc_permutation_matrix(num_qubits, location) return PermutationMatrix(matrix)
def __init__( self, gate: Gate, location: CircuitLocationLike, params: Sequence[float] = [], ) -> None: """ Operation Constructor. s Args: gate (Gate): The cell's gate. location (CircuitLocationLike): The set of qudits this gate affects. params (Sequence[float]): The parameters for the gate. Raises: ValueError: If `gate`'s size doesn't match `location`'s length. ValueError: If `gate`'s size doesn't match `params`'s length. """ if not isinstance(gate, Gate): raise TypeError('Expected gate, got %s.' % type(gate)) if not CircuitLocation.is_location(location): raise TypeError('Invalid location.') location = CircuitLocation(location) if len(location) != gate.get_size(): raise ValueError('Gate and location size mismatch.') self.num_params = gate.get_num_params() self.radixes = gate.get_radixes() self.size = gate.get_size() if len(params) == 0 and self.get_num_params() != 0: params = [0.0] * self.get_num_params() self.check_parameters(params) self._gate = gate self._location = location self._params = list(params)
def get_subgraph( self, location: CircuitLocationLike, renumbering: dict[int, int] = None, # type: ignore ) -> list[tuple[int, int]]: """Returns the sub_coupling_graph with qudits in location.""" if not CircuitLocation.is_location(location, self.num_qudits): raise TypeError('Invalid location.') location = CircuitLocation(location) if renumbering is None: renumbering = {x: x for x in range(self.num_qudits)} subgraph = [] for q0, q1 in self.coupling_graph: if q0 in location and q1 in location: subgraph.append((renumbering[q0], renumbering[q1])) return subgraph
def _location_search( self, locations: set[CircuitLocation], path: set[int], vertex: int, limit: int, ) -> None: """ Add paths with length equal to limit to the `locations` set. Args: locations (set[CircuitLocation]): A list that contains all paths found so far of length equal to `limit`. path (set[int]): The qudit vertices currently included in the path. vertex (int): The vertex in the graph currently being examined. limit (int): The desired length of paths in the `locations` list. """ if vertex in path: return curr_path = path.copy() curr_path.add(vertex) if len(curr_path) == limit: locations.add(CircuitLocation(curr_path)) return frontier: set[int] = { qudit for node in curr_path for qudit in self._adjacency_list[node] if qudit not in curr_path } for neighbor in frontier: self._location_search(locations, curr_path, neighbor, limit)
def __init__( self, gate: Gate, locations: Sequence[CircuitLocationLike], radixes: Sequence[int], ) -> None: """ Create a gate that has parameterized location. Args: gate (Gate): The gate to parameterize location for. locations (Sequence[CircuitLocationLike]): A sequence of locations. Each location represents a valid placement for gate. radixes (Sequence[int]): The number of orthogonal states for each qudit. Defaults to qubits. Raises: ValueError: If there are not enough locations or the locations are incorrectly sized. Notes: The locations are calculated in their own space and are not relative to a circuit. This means you should consider the VariableLocationGate as its own circuit when deciding the locations. For example, if you want to multiplex the (2, 3) and (3, 5) placements of a CNOT on a 6-qubit circuit, then you would give the VariableLocationGate the (0, 1) and (1, 2) locations and place the VariableLocationGate on qubits (2, 3, 5) on the circuit. """ if not isinstance(gate, Gate): raise TypeError('Expected gate object, got %s' % type(gate)) if not all(CircuitLocation.is_location(l) for l in locations): raise TypeError('Expected a sequence of valid locations.') locations = [CircuitLocation(l) for l in locations] if not all(len(l) == gate.get_size() for l in locations): raise ValueError('Invalid sized location.') if len(locations) < 1: raise ValueError('VLGs require at least 1 locations.') self.gate = gate self.name = 'VariableLocationGate(%s)' % gate.get_name() self.locations = list(locations) if radixes is None: # Calculate radixes radix_map: dict[int, int | None] = {i: None for i in range(self.size)} for l in locations: for radix, qudit_index in zip(gate.get_radixes(), l): if radix_map[qudit_index] is None: radix_map[qudit_index] = radix elif radix_map[qudit_index] != radix: raise ValueError( 'Gate cannot be applied to all locations' ' due to radix mismatch.', ) self.radixes = tuple(radix_map.values()) else: for l in locations: for radix, qudit_index in zip(gate.get_radixes(), l): if radixes[qudit_index] != radix: raise ValueError( 'Gate cannot be applied to all locations' ' due to radix mismatch.', ) self.radixes = tuple(radixes) self.size = len(self.radixes) self.num_params = self.gate.get_num_params() + len(locations) self.extension_size = self.size - self.gate.get_size() # TODO: This needs to changed for radixes self.I = np.identity(2**self.extension_size) self.perms = np.array([ PermutationMatrix.from_qubit_location(self.size, l) for l in self.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)
def location(self) -> CircuitLocation: return CircuitLocation(sorted(self.keys()))