def test_get_unitary(gate: Gate) -> None: size = gate.get_size() circ = Circ(size, radixes=gate.get_radixes()) circ.append_gate(gate, list(range(size))) num_params = circ.get_num_params() x = np.random.random((num_params, )) circuit = Circuit(circ) assert np.allclose(circ.get_unitary(x).get_numpy(), circuit.get_unitary(x))
def test_value(self, gate: Gate) -> None: circuit = Circuit(gate.get_size(), gate.get_radixes()) assert circuit.is_differentiable() circuit.append_gate(gate, list(range(gate.get_size()))) if isinstance(gate, DifferentiableUnitary): assert circuit.is_differentiable() else: assert not circuit.is_differentiable()
def test_get_grad(gate: Gate) -> None: size = gate.get_size() circ = Circ(size, radixes=gate.get_radixes()) circ.append_gate(gate, list(range(size))) num_params = circ.get_num_params() x = np.random.random((num_params, )) circuit = Circuit(circ) grad_python = circ.get_grad(x) grad_rust = circuit.get_grad(x) for py, rs in zip(grad_python, grad_rust): assert np.allclose(py, rs)
def __init__(self, gate: Gate, frozen_params: dict[int, float]) -> None: """ Create a gate which fixes some of the parameters it takes. Args: gate (Gate): The Gate to fix the parameters of. frozen_params (dict[int, float]): A dictionary mapping parameters indices to the fixed value they should be. Raises: ValueError: If any of the `frozen_params` indices are greater than the number of parameters `gate` takes or less than 0 or if the total amount of `frozen_params` is larger than the number of parameters `gate` takes. """ if not isinstance(gate, Gate): raise TypeError('Expected gate, got %s.' % type(gate)) if not isinstance(frozen_params, dict): raise TypeError( 'Expected dict for frozen_params, ' 'got %s.' % type(frozen_params), ) if not len(frozen_params) <= gate.get_num_params(): raise ValueError( 'Too many fixed parameters specified, expected at most' ' %d, got %d' % (gate.get_num_params(), len(frozen_params)), ) keys = list(frozen_params.keys()) values = list(frozen_params.values()) if not all(isinstance(p, int) for p in keys): fail_idx = [isinstance(p, int) for p in keys].index(False) raise TypeError( 'Expected frozen_params keys to be int, got %s.' % type(keys[fail_idx]), ) if not all(isinstance(p, (int, float)) for p in values): typechecks = [isinstance(p, (int, float)) for p in values] fail_idx = typechecks.index(False) raise TypeError( 'Expected frozen_params values to be float, got %s.' % type(values[fail_idx]), ) if not all(0 <= p < gate.get_num_params() for p in keys): fail_idx = [0 <= p < gate.get_num_params() for p in keys].index(False) raise ValueError( 'Expected parameter index to be non-negative integer' ' < %d, got %d.' % (gate.get_num_params(), keys[fail_idx]), ) self.gate = gate self.num_params = gate.get_num_params() - len(frozen_params) self.size = gate.get_size() self.radixes = gate.get_radixes() self.frozen_params = frozen_params self.unfixed_param_idxs = [ i for i in range(gate.get_num_params()) if i not in self.frozen_params.keys() ]
def test_get_unitary_and_grad(gate: Gate) -> None: size = gate.get_size() circ = Circ(size, radixes=gate.get_radixes()) circ.append_gate(gate, list(range(size))) num_params = circ.get_num_params() x = np.random.random((num_params, )) circuit = Circuit(circ) utry_python, grad_python = circ.get_unitary_and_grad(x) utry_rust, grad_rust = circuit.get_unitary_and_grad(x) assert np.allclose(utry_python.get_numpy(), utry_rust) for i, (py, rs) in enumerate(zip(grad_python, grad_rust)): assert np.allclose(py, rs)
def test_location_mismatch_2(self, qutrit_gate: Gate) -> None: circuit = Circuit(qutrit_gate.get_size(), qutrit_gate.get_radixes()) location = list(range(qutrit_gate.get_size())) location[-1] += 1 params = [0] * qutrit_gate.get_num_params() op = Operation(qutrit_gate, location, params) try: circuit.check_valid_operation(op) except ValueError: return except BaseException: assert False, 'Unexpected Exception' assert False
def __init__(self, gate: Gate, tag: Any) -> None: """Associate `tag` with `gate`.""" if not isinstance(gate, Gate): raise TypeError('Expected gate object, got %s' % type(gate)) self.gate = gate self.tag = tag self.name = 'Tagged(%s:%s)' % (gate.get_name(), tag) self.num_params = gate.get_num_params() self.size = gate.get_size() self.radixes = gate.get_radixes() # If input is a constant gate, we can cache the unitary. if self.num_params == 0: self.utry = gate.get_unitary()
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 __init__( self, gate: Gate, num_controls: int = 1, radixes: Sequence[int] = [], ) -> None: """Construct a ControlledGate.""" if not isinstance(gate, Gate): raise TypeError('Expected gate object, got %s.' % type(gate)) if not is_integer(num_controls): raise TypeError( 'Expected integer for num_controls, got %s.' % type(num_controls), ) if num_controls < 1: raise ValueError( 'Expected positive integer for num_controls, got %d.' % num_controls, ) if len(radixes) != 0 and not is_valid_radixes(radixes, num_controls): raise TypeError('Invalid radixes.') self.gate = gate self.size = gate.get_size() + num_controls self.num_controls = num_controls self.radixes = tuple(radixes or [2] * self.size) + gate.get_radixes() self.name = '%d-Controlled(%s)' % (num_controls, gate.get_name()) self.num_params = gate.get_num_params() self.Ic = np.identity(2 ** num_controls) # TODO: General radix support self.It = np.identity(gate.get_dim()) self.OneProj = np.zeros(self.Ic.shape) self.OneProj[-1, -1] = 1 self.left = np.kron((self.Ic - self.OneProj), self.It) # If input is a constant gate, we can cache the unitary. if self.num_params == 0: U = self.gate.get_unitary() right = np.kron(self.OneProj, U) self.utry = UnitaryMatrix(self.left + right, self.get_radixes())
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 test_valid_2(self, gate: Gate) -> None: circuit = Circuit(gate.get_size() + 2, (2, 2) + gate.get_radixes()) location = [x + 2 for x in list(range(gate.get_size()))] params = [0] * gate.get_num_params() circuit.check_valid_operation(Operation(gate, location, params))
def test_valid_1(self, gate: Gate) -> None: circuit = Circuit(gate.get_size(), gate.get_radixes()) location = list(range(gate.get_size())) params = [0] * gate.get_num_params() circuit.check_valid_operation(Operation(gate, location, params))
def test_gate_size_matches_radixes(self, gate: Gate) -> None: assert len(gate.get_radixes()) == gate.get_size()
def test_get_radixes_qutrit(self, qutrit_gate: Gate) -> None: assert all(radix == 3 for radix in qutrit_gate.get_radixes())
def test_get_radixes(self, gate: Gate) -> None: assert isinstance(gate.get_radixes(), tuple) assert all(isinstance(radix, int) for radix in gate.get_radixes()) assert all(radix > 0 for radix in gate.get_radixes())
def __init__( self, two_qudit_gate: Gate = CNOTGate(), single_qudit_gate_1: Gate = U3Gate(), single_qudit_gate_2: Gate | None = None, initial_layer_gate: Gate | None = None, ) -> None: """ Construct a SimpleLayerGenerator. Args: two_qudit_gate (Gate): A two-qudit gate that starts this layer generator's building block. (Default: CNOTGate()) single_qudit_gate_1 (Gate): A single-qudit gate that follows `two_qudit_gate` in the building block. (Default: U3Gate()) single_qudit_gate_2 (Gate | None): An alternate single-qudit gate to be used as the second single-qudit gate in the building block. If left as None, defaults to `single_qudit_gate_1`. (Default: None) initial_layer_gate (Gate | None): An alternate single-qudit gate that creates the initial layer. If left as None, defaults to `single_qudit_gate_1`. (Default: None) Raises: ValueError: If `two_qudit_gate`'s size is not 2, or if any of the single-qudit gates' size is not 1. ValueError: If `single_qudit_gate_1`'s radix does not match the radix of `two_qudit_gate`'s first qudit, or if `single_qudit_gate_2`'s radix does not match the radix of `two_qudit_gate`'s second qudit. """ if not isinstance(two_qudit_gate, Gate): raise TypeError( 'Expected gate for two_qudit_gate, got %s.' % type(two_qudit_gate), ) if two_qudit_gate.get_size() != 2: raise ValueError( 'Expected two-qudit gate' ', got a gate that acts on %d qudits.' % two_qudit_gate.get_size(), ) if not isinstance(single_qudit_gate_1, Gate): raise TypeError( 'Expected gate for single_qudit_gate_1, got %s.' % type(single_qudit_gate_1), ) if single_qudit_gate_1.get_size() != 1: raise ValueError( 'Expected single-qudit gate' ', got a gate that acts on %d qudits.' % single_qudit_gate_1.get_size(), ) if single_qudit_gate_2 is None: single_qudit_gate_2 = single_qudit_gate_1 if initial_layer_gate is None: initial_layer_gate = single_qudit_gate_1 if not isinstance(single_qudit_gate_2, Gate): raise TypeError( 'Expected gate for single_qudit_gate_2, got %s.' % type(single_qudit_gate_2), ) if single_qudit_gate_2.get_size() != 1: raise ValueError( 'Expected single-qudit gate' ', got a gate that acts on %d qudits.' % single_qudit_gate_2.get_size(), ) if not isinstance(initial_layer_gate, Gate): raise TypeError( 'Expected gate for initial_layer_gate, got %s.' % type(initial_layer_gate), ) if initial_layer_gate.get_size() != 1: raise ValueError( 'Expected single-qudit gate' ', got a gate that acts on %d qudits.' % initial_layer_gate.get_size(), ) two_radix_1 = two_qudit_gate.get_radixes()[0] two_radix_2 = two_qudit_gate.get_radixes()[1] if two_radix_1 != single_qudit_gate_1.get_radixes()[0]: raise ValueError( 'Radix mismatch between two_qudit_gate and single_qudit_gate_1' ': %d != %d.' % (two_radix_1, single_qudit_gate_1.get_radixes()[0]), ) if two_radix_2 != single_qudit_gate_2.get_radixes()[0]: raise ValueError( 'Radix mismatch between two_qudit_gate and single_qudit_gate_2' ': %d != %d.' % (two_radix_2, single_qudit_gate_2.get_radixes()[0]), ) self.two_qudit_gate = two_qudit_gate self.single_qudit_gate_1 = single_qudit_gate_1 self.single_qudit_gate_2 = single_qudit_gate_2 self.initial_layer_gate = initial_layer_gate