def _append_zeros(state, qubits: List[int], results: List[int]): for q, r in zip(qubits, results): state = K.expand_dims(state, axis=q) if r: state = K.concatenate([K.zeros_like(state), state], axis=q) else: state = K.concatenate([state, K.zeros_like(state)], axis=q) return state
def control_unitary(unitary): shape = tuple(unitary.shape) if shape != (2, 2): raise_error(ValueError, "Cannot use ``control_unitary`` method for " "input matrix of shape {}.".format(shape)) zeros = K.zeros((2, 2), dtype='DTYPECPX') part1 = K.concatenate([K.eye(2, dtype='DTYPECPX'), zeros], axis=0) part2 = K.concatenate([zeros, unitary], axis=0) return K.concatenate([part1, part2], axis=1)
def cache(self): if self._cache is None: cache = self.GateCache() qubits = set(self.target_qubits) # Create |00...0><00...0| for qubits that are traced out n = len(self.target_qubits) row0 = K.cast([1] + (2**n - 1) * [0], dtype='DTYPECPX') shape = K.cast((2**n - 1, 2**n), dtype='DTYPEINT') rows = K.zeros(shape, dtype='DTYPECPX') cache.zero_matrix = K.concatenate([row0[K.newaxis], rows], axis=0) cache.zero_matrix = K.reshape(cache.zero_matrix, 2 * n * (2, )) # Calculate initial transpose order order = tuple(sorted(self.target_qubits)) order += tuple(i for i in range(self.nqubits) if i not in qubits) order += tuple(i + self.nqubits for i in order) cache.einsum_order = order # Calculate final transpose order order1 = tuple(i for i in range(self.nqubits) if i not in qubits) order2 = tuple(self.target_qubits) order = (order1 + tuple(i + self.nqubits for i in order1) + order2 + tuple(i + self.nqubits for i in order2)) cache.final_order = tuple( order.index(i) for i in range(2 * self.nqubits)) # Shapes cache.einsum_shape = K.cast(2 * (2**n, 2**(self.nqubits - n)), dtype='DTYPEINT') cache.output_shape = K.cast(2 * (2**self.nqubits, ), dtype='DTYPEINT') cache.reduced_shape = K.cast(2 * (2**(self.nqubits - n), ), dtype='DTYPEINT') self._cache = cache return self._cache
def test_apply_fsim(nqubits, targets, controls, compile, einsum_str): """Check ``K.op.apply_twoqubit_gate`` for random gates.""" state = random_complex((2**nqubits, )) rotation = random_complex((2, 2)) phase = random_complex((1, )) target_state = state.numpy().reshape(nqubits * (2, )) gatenp = np.eye(4, dtype=target_state.dtype) gatenp[1:3, 1:3] = rotation.numpy() gatenp[3, 3] = phase.numpy()[0] gatenp = gatenp.reshape(4 * (2, )) slicer = nqubits * [slice(None)] for c in controls: slicer[c] = 1 slicer = tuple(slicer) target_state[slicer] = np.einsum(einsum_str, target_state[slicer], gatenp) target_state = target_state.ravel() gate = K.concatenate([K.reshape(rotation, (4, )), phase], axis=0) def apply_operator(state): qubits = qubits_tensor(nqubits, targets, controls) return K.op.apply_fsim(state, gate, qubits, nqubits, *targets, get_threads()) if compile: apply_operator = K.compile(apply_operator) state = apply_operator(state) np.testing.assert_allclose(target_state, state.numpy())
def tensor(self): """Returns the full state vector as a tensor of shape ``(2 ** nqubits,)``. This is done by merging the state pieces to a single tensor. Using this method will double memory usage. """ if self.qubits.list == list(range(self.nglobal)): with K.device(self.device): state = K.concatenate([x[K.newaxis] for x in self.pieces], axis=0) state = K.reshape(state, self.shapes["full"]) elif self.qubits.list == list(range(self.nlocal, self.nqubits)): with K.device(self.device): state = K.concatenate([x[:, K.newaxis] for x in self.pieces], axis=1) state = K.reshape(state, self.shapes["full"]) else: # fall back to the transpose op with K.device(self.device): state = K.zeros(self.shapes["full"]) state = K.transpose_state(self.pieces, state, self.nqubits, self.qubits.reverse_transpose_order) return state
def density_matrix_call(self, state): state = K.reshape(state, self.tensor_shape) if self.is_controlled_by: ncontrol = len(self.control_qubits) nactive = self.nqubits - ncontrol n = 2**ncontrol state = K.transpose(state, self.control_cache.order(True)) state = K.reshape(state, 2 * (n, ) + 2 * nactive * (2, )) state01 = K.gather(state, indices=range(n - 1), axis=0) state01 = K.squeeze(K.gather(state01, indices=[n - 1], axis=1), axis=1) state01 = self.einsum(self.calculation_cache.right0, state01, K.conj(self.matrix)) state10 = K.gather(state, indices=range(n - 1), axis=1) state10 = K.squeeze(K.gather(state10, indices=[n - 1], axis=0), axis=0) state10 = self.einsum(self.calculation_cache.left0, state10, self.matrix) state11 = K.squeeze(K.gather(state, indices=[n - 1], axis=0), axis=0) state11 = K.squeeze(K.gather(state11, indices=[n - 1], axis=0), axis=0) state11 = self.einsum(self.calculation_cache.right, state11, K.conj(self.matrix)) state11 = self.einsum(self.calculation_cache.left, state11, self.matrix) state00 = K.gather(state, indices=range(n - 1), axis=0) state00 = K.gather(state00, indices=range(n - 1), axis=1) state01 = K.concatenate([state00, state01[:, K.newaxis]], axis=1) state10 = K.concatenate([state10, state11[K.newaxis]], axis=0) state = K.concatenate([state01, state10[K.newaxis]], axis=0) state = K.reshape(state, 2 * self.nqubits * (2, )) state = K.transpose(state, self.control_cache.reverse(True)) else: state = self.einsum(self.calculation_cache.right, state, K.conj(self.matrix)) state = self.einsum(self.calculation_cache.left, state, self.matrix) return K.reshape(state, self.flat_shape)
def add_shot(self, probabilities=None): """Adds a measurement shot to an existing measurement symbol. Useful for sampling more than one shots with collapse measurement gates. """ if self.nshots: if probabilities is not None: self.probabilities = probabilities self.nshots += 1 # sample new shot new_shot = K.cpu_fallback(K.sample_shots, self.probabilities, 1) self._decimal = K.concatenate([self.decimal, new_shot], axis=0) self._binary = None else: if probabilities is None: raise_error(ValueError, "Cannot add shots in measurement that " "for which the probability distribution " "is not specified.") self.set_probabilities(probabilities)
def state_vector_call(self, state): state = K.reshape(state, self.tensor_shape) if self.is_controlled_by: ncontrol = len(self.control_qubits) nactive = self.nqubits - ncontrol state = K.transpose(state, self.control_cache.order(False)) # Apply `einsum` only to the part of the state where all controls # are active. This should be `state[-1]` state = K.reshape(state, (2**ncontrol, ) + nactive * (2, )) updates = self.einsum(self.calculation_cache.vector, state[-1], self.matrix) # Concatenate the updated part of the state `updates` with the # part of of the state that remained unaffected `state[:-1]`. state = K.concatenate([state[:-1], updates[K.newaxis]], axis=0) state = K.reshape(state, self.nqubits * (2, )) # Put qubit indices back to their proper places state = K.transpose(state, self.control_cache.reverse(False)) else: einsum_str = self.calculation_cache.vector state = self.einsum(einsum_str, state, self.matrix) return K.reshape(state, self.flat_shape)
def construct_unitary(self): t = K.cast(self.parameters) phase = K.exp(1j * t / 2.0)[K.newaxis] diag = K.concatenate([K.conj(phase), phase], axis=0) return K.diag(diag)