def __init__(self, num_rails, initial_state=None): self.num_rails = num_rails self.state = [] self.gates = [] if initial_state is not None: if initial_state.shape == [num_rails, 1]: # Qubit-wise definition. state = None for qubit in initial_state: if qubit == 0: if state is None: state = zero else: state = tensorprod(state, zero) elif qubit == 1: if state is None: state = one else: state = tensorprod(state, one) else: raise ValueError('Qubit can only be either 0 or 1') else: if initial_state.shape != (2 ** num_rails, 1): # Not a full description. raise ValueError('Incomplete or improper definition of' ' initial_state') else: initial_state = tensor(zero, num_rails) self.state.append(initial_state)
from tensorprod import tensor from circuit import Circuit from grover import iterator from measurement import measure if __name__ == '__main__': N = input('Enter the number of items in the search space: ') n = int(np.ceil(np.log2(N)) + 1) M = input('Enter the number of search targets: ') targets = [] for i in range(M): target = raw_input('Target %d in binary: ' % (i + 1)) targets.append(target) num_iter = input('Number of iterations: ') initial_state = tensor(plus, n) circuit = Circuit(n, initial_state) # Apply the Z gate to the ancilla bit to make it |-> for phase kickback # Multiply by root 2 to renormalize without considering the ancilla qubit state = np.sqrt(2) * circuit.add_gate(Z, [ n, ]) # Switch to interactive mode plt.ion() # Plot the initial probability amplitudes and angle amps_fig = plt.figure(0) amps = amps_fig.add_subplot(111) amps.bar(np.arange(state.size / 2) + 0.1, abs(state[::2])**2) amps_fig.show() angles_fig = plt.figure(1)
from tensorprod import tensor from circuit import Circuit from grover import iterator from measurement import measure if __name__ == "__main__": N = input("Enter the number of items in the search space: ") n = int(np.ceil(np.log2(N)) + 1) M = input("Enter the number of search targets: ") targets = [] for i in range(M): target = raw_input("Target %d in binary: " % (i + 1)) targets.append(target) num_iter = input("Number of iterations: ") initial_state = tensor(plus, n) circuit = Circuit(n, initial_state) # Apply the Z gate to the ancilla bit to make it |-> for phase kickback # Multiply by root 2 to renormalize without considering the ancilla qubit state = np.sqrt(2) * circuit.add_gate(Z, [n]) # Switch to interactive mode plt.ion() # Plot the initial probability amplitudes and angle amps_fig = plt.figure(0) amps = amps_fig.add_subplot(111) amps.bar(np.arange(state.size / 2) + 0.1, abs(state[::2]) ** 2) amps_fig.show() angles_fig = plt.figure(1) angles = angles_fig.add_subplot(111, polar=True) theta = np.arccos(np.sqrt((2.0 ** (n - 1) - M) / (2 ** (n - 1))))
def add_gate(self, gate, qubits, controls=[]): """ Add a gate to the circuit. Can currently handle only single-qubit gates with one or more controls. Parameters ---------- gate : np.ndarray 2D matrix defining the transformation. Must be unitary. Size of the matrix must be (NxN) where N is 2^len(qubits) qubits : list List of rail or qubit numbers upon which this gate acts. Numbers must be between 1 and num_rails. controls : list Control qubits upon which this gate depends. Absolute value of qubit numbers must be between 1 and num_rails. Use a negative sign to indicate 0-control. Returns ------- state : np.ndarray (num_rails x 1) dimensional state vector after successful application of the gate. """ # Implementation notes: # # 1. Currently computing whole operator matrix for the entire state # space from the single-qubit gate, the qubit being operated upon # and the control qubits. # 2. Qubit-wise computations are not feasible, because we would then # have to operate multi-qubit gates on some qubits *only*, which # would be difficult because it is hard to extract qubits from the # system state. # 3. Implementing control by looking at the probability of the control # qubit being 0 or 1 and then applying the operation with that # probability is harder than simply writing out the whole operation # matrix. n = len(qubits) N = 2 ** n if gate.shape != (N, N): raise ValueError('Gate is of improper shape') if n != 1 or gate.shape != (2, 2): raise NotImplementedError('Cannot handle gates that are not single' '-qubit') controls = np.array(controls) # First, determine the x-operator, to invert zero-control rails. x_operator = np.array([[1,],]) for i in xrange(1, self.num_rails+1): if i in abs(controls): if controls[np.where(abs(controls) == i)] < 0: # Zero-control x_operator = tensorprod(x_operator, X) else: x_operator = tensorprod(x_operator, I) else: x_operator = tensorprod(x_operator, I) # Now, determine the actual operation of the gate itself. operator = np.array([[1,],]) for i in xrange(1, self.num_rails+1): if i in qubits: if i in abs(controls): raise ValueError('A qubit cannot be used for the gate and' ' also be a control') else: operator = tensorprod(operator, gate) else: # Ignore control qubits for now. Treat them like identity. operator = tensorprod(operator, I) # Now go back and identify (pun intended) parts of the operator where # there is control. controls = abs(controls) controls.sort() # Identity is a big I block that will be used to wipe out those places # where there should have been a control, but we put a gate instead. identity = tensor(I, self.num_rails-1) for i in controls[::-1]: # Block size is the number of contiguous 0's that appear at the # i'th qubit when you write the binary numbers in order. block_size = 2 ** (self.num_rails - i) # We start with one square of dimension (block_size x block_size) # at the (0, 0) index and make it identity. We then skip one # block, go to the third block starting from (0, 0) and repeat. # We do this both sideways and downwards. That effectively adds # the requisite control to the entire operator. for j in xrange(0, 2**self.num_rails, 2*block_size): for k in xrange(0, 2**self.num_rails, 2*block_size): # p and q are starting positions of blocks of dimension # (block_size x block_size) in indentity. These are # required because identity is only half as big in side # length when compared with operator. p = j / 2 q = k / 2 operator[j:j+block_size, k:k+block_size] = ( identity[p:p+block_size, q:q+block_size] ) # Compute the overall gate matrix gate_matrix = np.dot(x_operator, np.dot(operator, x_operator)) # Compute and return the state after application of the gate state = np.dot(gate_matrix, self.state[-1]) self.state.append(state) return state