def __init__(self, circuit: "DistributedCircuit"): super(DistributedState, self).__init__(circuit) self.device = circuit.memory_device self.qubits = circuit.queues.qubits self.dtype = DTYPES.get('DTYPECPX') # Create pieces n = 2**(self.nqubits - self.nglobal) with tf.device(self.device): self.pieces = [ tf.Variable(tf.zeros(n, dtype=self.dtype)) for _ in range(self.ndevices) ] dtype = DTYPES.get('DTYPEINT') self.shapes = { "full": tf.cast((2**self.nqubits, ), dtype=dtype), "device": tf.cast((len(self.pieces), n), dtype=dtype), "tensor": self.nqubits * (2, ) } self.bintodec = { "global": 2**np.arange(self.nglobal - 1, -1, -1), "local": 2**np.arange(self.nlocal - 1, -1, -1) }
def test_general_channel(backend, tfmatrices, oncircuit): """Test `gates.KrausChannel`.""" original_backend = qibo.get_backend() qibo.set_backend(backend) initial_rho = utils.random_density_matrix(2) a1 = np.sqrt(0.4) * np.array([[0, 1], [1, 0]]) a2 = np.sqrt(0.6) * np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) if tfmatrices: import tensorflow as tf from qibo.config import DTYPES a1 = tf.cast(a1, dtype=DTYPES.get('DTYPECPX')) a2 = tf.cast(a2, dtype=DTYPES.get('DTYPECPX')) gate = gates.KrausChannel([((1, ), a1), ((0, 1), a2)]) assert gate.target_qubits == (0, 1) if oncircuit: c = models.Circuit(2, density_matrix=True) c.add(gate) final_rho = c(np.copy(initial_rho)).numpy() else: if backend == "custom": final_rho = gate(np.copy(initial_rho)) else: final_rho = gate(np.copy(initial_rho).reshape(4 * (2, ))) final_rho = final_rho.numpy().reshape((4, 4)) m1 = np.kron(np.eye(2), np.array(a1)) m2 = np.array(a2) target_rho = (m1.dot(initial_rho).dot(m1.conj().T) + m2.dot(initial_rho).dot(m2.conj().T)) np.testing.assert_allclose(final_rho, target_rho) qibo.set_backend(original_backend)
def _prepare(self): unitary, phi = self.parameter matrix = np.zeros(5, dtype=DTYPES.get("NPTYPECPX")) matrix[:4] = np.reshape(unitary, (4, )) matrix[4] = np.exp(-1j * phi) with tf.device(self.device): self.matrix = tf.constant(matrix, dtype=DTYPES.get('DTYPECPX'))
def _prepare(self): theta, phi = self.parameter cos, isin = np.cos(theta), -1j * np.sin(theta) phase = np.exp(-1j * phi) matrix = np.array([cos, isin, isin, cos, phase], dtype=DTYPES.get('NPTYPECPX')) with tf.device(self.device): self.matrix = tf.constant(matrix, dtype=DTYPES.get('DTYPECPX'))
def dtype(self): if DTYPES.get("DTYPECPX") == tf.complex128: return np.complex128 elif DTYPES.get("DTYPECPX") == tf.complex64: return np.complex64 else: # pragma: no cover # case not tested because DTYPECPX is preset to a valid type raise_error(TypeError, "Unknown complex type {}." "".format(DTYPES.get("DTYPECPX")))
def _cast_initial_state(self, state: InitStateType) -> tf.Tensor: if isinstance(state, tf.Tensor): return state elif isinstance(state, np.ndarray): return tf.cast(state.astype(DTYPES.get('NPTYPECPX')), dtype=DTYPES.get('DTYPECPX')) raise_error( TypeError, "Initial state type {} is not recognized." "".format(type(state)))
def test_precision_dictionary(precision): """Check if ``set_precision`` changes the ``DTYPES`` dictionary.""" import qibo import tensorflow as tf from qibo.config import DTYPES original_precision = qibo.get_precision() qibo.set_precision(precision) if precision == "single": assert DTYPES.get("DTYPECPX") == tf.complex64 else: assert DTYPES.get("DTYPECPX") == tf.complex128 qibo.set_precision(original_precision)
def test_two_variables_backpropagation(backend): """Check that backpropagation works when using `tf.Variable` parameters.""" original_backend = qibo.get_backend() qibo.set_backend(backend) import tensorflow as tf from qibo.config import DTYPES theta = tf.Variable([0.1234, 0.4321], dtype=DTYPES.get('DTYPE')) # TODO: Fix parametrized gates so that `Circuit` can be defined outside # of the gradient tape with tf.GradientTape() as tape: c = Circuit(2) c.add(gates.RX(0, theta[0])) c.add(gates.RY(1, theta[1])) loss = tf.math.real(c()[0]) grad = tape.gradient(loss, theta) t = np.array([0.1234, 0.4321]) / 2.0 target_loss = np.cos(t[0]) * np.cos(t[1]) np.testing.assert_allclose(loss.numpy(), target_loss) target_grad1 = -np.sin(t[0]) * np.cos(t[1]) target_grad2 = -np.cos(t[0]) * np.sin(t[1]) target_grad = np.array([target_grad1, target_grad2]) / 2.0 np.testing.assert_allclose(grad.numpy(), target_grad) qibo.set_backend(original_backend)
def test_post_measurement_asymmetric_bitflips(): """Check applying asymmetric bitflips to measurement samples.""" import tensorflow as tf from qibo.config import DTYPES from qibo.tensorflow import measurements qubits = tuple(range(4)) samples = np.random.randint(0, 2, (20, 4)) result = measurements.GateResult(qubits, binary_samples=samples) p1_map = {0: 0.2, 1: 0.0, 2: 0.0, 3: 0.1} tf.random.set_seed(123) noisy_result = result.apply_bitflips(p0=0.2, p1=p1_map) p0 = 0.2 * np.ones(4) p1 = np.array([0.2, 0.0, 0.0, 0.1]) tf.random.set_seed(123) sprobs = tf.random.uniform(samples.shape, dtype=DTYPES.get('DTYPE')).numpy() target_samples = np.copy(samples).ravel() ids = (np.where(target_samples == 0)[0], np.where(target_samples == 1)[0]) target_samples[ ids[0]] = samples.ravel()[ids[0]] + (sprobs < p0).ravel()[ids[0]] target_samples[ ids[1]] = samples.ravel()[ids[1]] - (sprobs < p1).ravel()[ids[1]] target_samples = target_samples.reshape(samples.shape) np.testing.assert_allclose(noisy_result.samples(), target_samples)
def construct_unitary(self) -> np.ndarray: cost = np.cos(self._theta / 2) sint = np.sin(self._theta / 2) eplus = np.exp(1j * (self._phi + self._lam) / 2.0) eminus = np.exp(1j * (self._phi - self._lam) / 2.0) return np.array([[eplus.conj() * cost, -eminus.conj() * sint], [eminus * sint, eplus * cost]], dtype=DTYPES.get('NPTYPECPX'))
def sample(): shape = (1 + is_density_matrix) * self.nqubits * (2, ) probs = self._calculate_probabilities(tf.reshape(state, shape), is_density_matrix) logits = tf.math.log(tf.reshape(probs, probs_dim)) return tf.random.categorical(logits[tf.newaxis], nshots, dtype=DTYPES.get('DTYPEINT'))[0]
def construct_unitary(self) -> np.ndarray: theta, phi = self.parameter cos, isin = np.cos(theta), -1j * np.sin(theta) matrix = np.eye(4, dtype=DTYPES.get('NPTYPECPX')) matrix[1, 1], matrix[2, 2] = cos, cos matrix[1, 2], matrix[2, 1] = isin, isin matrix[3, 3] = np.exp(-1j * phi) return matrix
def prepare(self): self.is_prepared = True self.reprepare() qubits = self.qubits + tuple(q + self.nqubits for q in self.qubits) self.calculation_cache = self.einsum.create_cache( qubits, 2 * self.nqubits) self.calculation_cache.cast_shapes( lambda x: tf.cast(x, dtype=DTYPES.get('DTYPEINT')))
def construct_unitary(self): unitary = self.parameter rank = int(np.log2(int(unitary.shape[0]))) dtype = DTYPES.get('DTYPECPX') if isinstance(unitary, tf.Tensor): matrix = tf.identity(tf.cast(unitary, dtype=dtype)) elif isinstance(unitary, np.ndarray): matrix = tf.convert_to_tensor(unitary, dtype=dtype) return matrix
def construct_unitary(self) -> np.ndarray: theta, phi, lam = self.parameters cost = np.cos(theta / 2) sint = np.sin(theta / 2) eplus = np.exp(1j * (phi + lam) / 2.0) eminus = np.exp(1j * (phi - lam) / 2.0) return np.array([[eplus.conj() * cost, -eminus.conj() * sint], [eminus * sint, eplus * cost]], dtype=DTYPES.get('NPTYPECPX'))
def __init__(self, nqubits): super(TensorflowDensityMatrixCircuit, self).__init__(nqubits) self.density_matrix = True self.shapes = { 'TENSOR': 2 * self.nqubits * (2, ), 'VECTOR': (2**nqubits, ), 'FLAT': 2 * (2**self.nqubits, ) } self.shapes['TF_FLAT'] = tf.cast(self.shapes.get('FLAT'), dtype=DTYPES.get('DTYPEINT'))
def _apply_bitflips(noiseless_samples: tf.Tensor, probs: Tuple[float]) -> tf.Tensor: dtype = DTYPES.get('DTYPE') fprobs = tf.cast(probs, dtype=dtype) sprobs = tf.random.uniform(noiseless_samples.shape, dtype=dtype) flip0 = tf.cast(sprobs < fprobs[0], dtype=noiseless_samples.dtype) flip1 = tf.cast(sprobs < fprobs[1], dtype=noiseless_samples.dtype) noisy_samples = noiseless_samples + (1 - noiseless_samples) * flip0 noisy_samples = noisy_samples - noiseless_samples * flip1 return noisy_samples
def __init__(self, nqubits): super(TensorflowCircuit, self).__init__(nqubits) self._compiled_execute = None self.check_initial_state_shape = True self.shapes = { 'TENSOR': self.nqubits * (2, ), 'FLAT': (2**self.nqubits, ) } self.shapes['TF_FLAT'] = tf.cast(self.shapes.get('FLAT'), dtype=DTYPES.get('DTYPEINT'))
def control_unitary(unitary: tf.Tensor) -> tf.Tensor: shape = tuple(unitary.shape) if shape != (2, 2): raise_error( ValueError, "Cannot use ``control_unitary`` method for " "input matrix of shape {}.".format(shape)) dtype = DTYPES.get('DTYPECPX') zeros = tf.zeros((2, 2), dtype=dtype) part1 = tf.concat([tf.eye(2, dtype=dtype), zeros], axis=0) part2 = tf.concat([zeros, unitary], axis=0) return tf.concat([part1, part2], axis=1)
def __call__(self, state: tf.Tensor, is_density_matrix: bool = False) -> tf.Tensor: shape = tuple(state.shape) if self._nqubits is None: if is_density_matrix: self.nqubits = len(shape) // 2 else: self.nqubits = len(shape) _state = np.array(self.coefficients).reshape(shape) return tf.convert_to_tensor(_state, dtype=DTYPES.get("DTYPECPX"))
def test_variable_theta(backend, accelerators): """Check that parametrized gates accept `tf.Variable` parameters.""" original_backend = qibo.get_backend() qibo.set_backend(backend) import tensorflow as tf from qibo.config import DTYPES theta1 = tf.Variable(0.1234, dtype=DTYPES.get('DTYPE')) theta2 = tf.Variable(0.4321, dtype=DTYPES.get('DTYPE')) cvar = Circuit(2, accelerators) cvar.add(gates.RX(0, theta1)) cvar.add(gates.RY(1, theta2)) final_state = cvar().numpy() c = Circuit(2) c.add(gates.RX(0, 0.1234)) c.add(gates.RY(1, 0.4321)) target_state = c().numpy() np.testing.assert_allclose(final_state, target_state) qibo.set_backend(original_backend)
def test_entropy_singlet_state(): """Check that the singlet state has maximum entropy.""" entropy = callbacks.EntanglementEntropy([0]) state = np.zeros(4) state[0], state[-1] = 1, 1 state = state / np.sqrt(2) # Pass the state as `tf.Tensor` to test this functionality as well import tensorflow as tf from qibo.config import DTYPES state = tf.convert_to_tensor(state, dtype=DTYPES.get('DTYPECPX')) result = entropy(state).numpy() np.testing.assert_allclose(result, 1.0)
class EntanglementEntropy(PartialTrace): """Von Neumann entanglement entropy callback. .. math:: S = \\mathrm{Tr} \\left ( \\rho \\log _2 \\rho \\right ) Args: partition (list): List with qubit ids that defines the first subsystem for the entropy calculation. If `partition` is not given then the first subsystem is the first half of the qubits. Example: :: from qibo import models, gates, callbacks # create entropy callback where qubit 0 is the first subsystem entropy = callbacks.EntanglementEntropy([0]) # initialize circuit with 2 qubits and add gates c = models.Circuit(2) # add callback gates between normal gates c.add(gates.CallbackGate(entropy)) c.add(gates.H(0)) c.add(gates.CallbackGate(entropy)) c.add(gates.CNOT(0, 1)) c.add(gates.CallbackGate(entropy)) # execute the circuit final_state = c() print(entropy[:]) # Should print [0, 0, 1] which is the entanglement entropy # after every gate in the calculation. """ _log2 = tf.cast(tf.math.log(2.0), dtype=DTYPES.get('DTYPE')) @classmethod def _entropy(cls, rho: tf.Tensor) -> tf.Tensor: """Calculates entropy by diagonalizing the density matrix.""" # Diagonalize eigvals = tf.math.real(tf.linalg.eigvalsh(rho)) # Treating zero and negative eigenvalues masked_eigvals = tf.gather(eigvals, tf.where(eigvals > EIGVAL_CUTOFF))[:, 0] entropy = - tf.reduce_sum(masked_eigvals * tf.math.log(masked_eigvals)) return entropy / cls._log2 def __call__(self, state: tf.Tensor, is_density_matrix: bool = False ) -> tf.Tensor: # Construct reduced density matrix rho = super(EntanglementEntropy, self).__call__(state, is_density_matrix) # Calculate entropy of reduced density matrix return self._entropy(rho)
def __new__(cls, nqubits, matrix, numpy=False): if isinstance(matrix, np.ndarray): if not numpy: matrix = K.cast(matrix, dtype=DTYPES.get('DTYPECPX')) elif isinstance(matrix, K.Tensor): if numpy: matrix = matrix.numpy() else: raise raise_error(TypeError, "Invalid type {} of Hamiltonian " "matrix.".format(type(matrix))) if numpy: return hamiltonians.NumpyHamiltonian(nqubits, matrix) else: return hamiltonians.TensorflowHamiltonian(nqubits, matrix)
class EntanglementEntropy(callbacks.EntanglementEntropy): _log2 = tf.cast(tf.math.log(2.0), dtype=DTYPES.get('DTYPE')) def entropy(self, rho: tf.Tensor) -> tf.Tensor: # Diagonalize eigvals = tf.math.real(tf.linalg.eigvalsh(rho)) # Treating zero and negative eigenvalues masked_eigvals = tf.gather(eigvals, tf.where(eigvals > EIGVAL_CUTOFF))[:, 0] spectrum = -1 * tf.math.log(masked_eigvals) if self.compute_spectrum: self.spectrum.append(spectrum) entropy = tf.reduce_sum(masked_eigvals * spectrum) return entropy / self._log2
def test_entropy_numerical(): """Check that entropy calculation does not fail for tiny eigenvalues.""" import tensorflow as tf from qibo.config import DTYPES eigvals = np.array([ -1e-10, -1e-15, -2e-17, -1e-18, -5e-60, 1e-48, 4e-32, 5e-14, 1e-14, 9.9e-13, 9e-13, 5e-13, 1e-13, 1e-12, 1e-11, 1e-10, 1e-9, 1e-7, 1, 4, 10 ]) rho = tf.convert_to_tensor(np.diag(eigvals), dtype=DTYPES.get('DTYPECPX')) result = callbacks.EntanglementEntropy._entropy(rho).numpy() mask = eigvals > 0 target = -(eigvals[mask] * np.log2(eigvals[mask])).sum() np.testing.assert_allclose(result, target)
def sample(self, state: tf.Tensor, nshots: int) -> tf.Tensor: probs = getattr(self, self._active_call)(state) probs = tf.transpose(probs, perm=self.reduced_target_qubits) dtype = DTYPES.get('DTYPEINT') probs_dim = tf.cast((2**len(self.target_qubits), ), dtype=dtype) logits = tf.math.log(tf.reshape(probs, probs_dim))[tf.newaxis] samples_dec = tf.random.categorical(logits, nshots, dtype=dtype)[0] result = self.measurements.GateResult(self.qubits, decimal_samples=samples_dec) # optional bitflip noise if sum(sum(x.values()) for x in self.bitflip_map) > 0: result = result.apply_bitflips(*self.bitflip_map) return result
def test_hamiltonian_initialization(): """Testing hamiltonian initialization errors.""" import tensorflow as tf from qibo.config import DTYPES dtype = DTYPES.get('DTYPECPX') with pytest.raises(TypeError): H = Hamiltonian(2, "test") H1 = Hamiltonian(2, np.eye(4)) H1 = Hamiltonian(2, np.eye(4), numpy=True) H1 = Hamiltonian(2, tf.eye(4, dtype=dtype)) H1 = Hamiltonian(2, tf.eye(4, dtype=dtype), numpy=True) with pytest.raises(ValueError): H1 = Hamiltonian(-2, np.eye(4)) with pytest.raises(RuntimeError): H2 = Hamiltonian(np.eye(2), np.eye(4)) with pytest.raises(ValueError): H3 = Hamiltonian(4, np.eye(10))
def test_post_measurement_bitflips(probs): """Check applying bitflips to measurement samples.""" import tensorflow as tf from qibo.config import DTYPES from qibo.tensorflow import measurements qubits = tuple(range(4)) samples = np.random.randint(0, 2, (20, 4)) result = measurements.GateResult(qubits, binary_samples=samples) tf.random.set_seed(123) noisy_result = result.apply_bitflips(probs) tf.random.set_seed(123) if isinstance(probs, dict): probs = np.array([probs[q] for q in qubits]) sprobs = tf.random.uniform(samples.shape, dtype=DTYPES.get('DTYPE')).numpy() flipper = sprobs < probs target_samples = (samples + flipper) % 2 np.testing.assert_allclose(noisy_result.samples(), target_samples)
def nqubits(self, n: int): """Sets the number of qubit that this gate acts on. This is called automatically by the `Circuit.add` method if the gate is used on a `Circuit`. If the gate is called on a state then `nqubits` is set during the first `__call__`. When `nqubits` is set we also calculate the einsum string so that it is calculated only once per gate. """ base_gates.Gate.nqubits.fset(self, n) # pylint: disable=no-member if self.is_controlled_by: self.control_cache = cache.ControlCache(self) nactive = n - len(self.control_qubits) targets = self.control_cache.targets self.calculation_cache = self.einsum.create_cache( targets, nactive, ncontrol=len(self.control_qubits)) else: self.calculation_cache = self.einsum.create_cache(self.qubits, n) self.calculation_cache.cast_shapes( lambda x: tf.cast(x, dtype=DTYPES.get('DTYPEINT')))