def test_swap_pieces(nqubits): state = utils.random_tensorflow_complex((2 ** nqubits,), dtype=tf.float64) target_state = tf.cast(np.copy(state.numpy()), dtype=state.dtype) shape = (2, int(state.shape[0]) // 2) for _ in range(10): global_qubit = np.random.randint(0, nqubits) local_qubit = np.random.randint(0, nqubits) while local_qubit == global_qubit: local_qubit = np.random.randint(0, nqubits) transpose_order = ([global_qubit] + list(range(global_qubit)) + list(range(global_qubit + 1, nqubits))) qubits_t = qubits_tensor(nqubits, [global_qubit, local_qubit]) target_state = op.apply_swap(target_state, qubits_t, nqubits, global_qubit, local_qubit, get_threads()) target_state = tf.reshape(target_state, nqubits * (2,)) target_state = tf.transpose(target_state, transpose_order) target_state = tf.reshape(target_state, shape) state = tf.reshape(state, nqubits * (2,)) state = tf.transpose(state, transpose_order) state = tf.reshape(state, shape) piece0, piece1 = state[0], state[1] if tf.config.list_physical_devices("GPU"): # pragma: no cover # case not tested by GitHub workflows because it requires GPU check_unimplemented_error(op.swap_pieces, piece0, piece1, local_qubit - 1, nqubits - 1, get_threads()) else: op.swap_pieces(piece0, piece1, local_qubit - int(global_qubit < local_qubit), nqubits - 1, get_threads()) np.testing.assert_allclose(target_state[0], piece0.numpy()) np.testing.assert_allclose(target_state[1], piece1.numpy())
def density_matrix_call(self, state: tf.Tensor) -> tf.Tensor: state = self.gate_op(state, self.matrix, self.qubits_tensor_dm, 2 * self.nqubits, *self.target_qubits, get_threads()) adjmatrix = tf.math.conj(self.matrix) state = self.gate_op(state, adjmatrix, self.qubits_tensor, 2 * self.nqubits, *self.target_qubits_dm, get_threads()) return state
def newtonian(loss, initial_parameters, args=(), method='Powell', options=None, processes=None): """Newtonian optimization approaches based on ``scipy.optimize.minimize``. For more details check the `scipy documentation <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html>`_. .. note:: When using the method ``parallel_L-BFGS-B`` the ``processes`` option controls the number of processes used by the parallel L-BFGS-B algorithm through the ``multiprocessing`` library. By default ``processes=None``, in this case the total number of logical cores are used. Make sure to select the appropriate number of processes for your computer specification, taking in consideration memory and physical cores. In order to obtain optimal results you can control the number of threads used by each process with the ``qibo.set_threads`` method. For example, for small-medium size circuits you may benefit from single thread per process, thus set ``qibo.set_threads(1)`` before running the optimization. Args: loss (callable): Loss as a function of variational parameters to be optimized. initial_parameters (np.ndarray): Initial guess for the variational parameters. args (tuple): optional arguments for the loss function. method (str): Name of method supported by ``scipy.optimize.minimize`` and ``'parallel_L-BFGS-B'`` for a parallel version of L-BFGS-B algorithm. options (dict): Dictionary with options accepted by ``scipy.optimize.minimize``. processes (int): number of processes when using the parallel BFGS method. """ if method == 'parallel_L-BFGS-B': import psutil from qibo.config import raise_error, get_device, get_threads, log if "GPU" in get_device(): # pragma: no cover raise_error(RuntimeError, "Parallel L-BFGS-B cannot be used with GPU.") if ((processes is not None and processes * get_threads() > psutil.cpu_count()) or (processes is None and get_threads() != 1)): # pragma: no cover log.warning( 'Please consider using a lower number of threads per process,' ' or reduce the number of processes for better performance') o = ParallelBFGS(loss, args=args, options=options, processes=processes) m = o.run(initial_parameters) else: from scipy.optimize import minimize m = minimize(loss, initial_parameters, args=args, method=method, options=options) return m.fun, m.x
def _check_parallel_configuration(processes): """Check if configuration is suitable for efficient parallel execution.""" import psutil from qibo import get_device from qibo.config import raise_error, get_threads, log device = get_device() if device is not None and "GPU" in device: # pragma: no cover raise_error(RuntimeError, "Parallel evaluations cannot be used with GPU.") if ((processes is not None and processes * get_threads() > psutil.cpu_count()) or (processes is None and get_threads() != 1)): # pragma: no cover log.warning( 'Please consider using a lower number of threads per process,' ' or reduce the number of processes for better performance')
def apply_gate(state, gate, qubits, nqubits, target, omp_num_threads=get_threads()): """Applies arbitrary one-qubit gate to a state vector. Modifies ``state`` in-place. Gates can be controlled to multiple qubits. Args: state (tf.Tensor): State vector of shape ``(2 ** nqubits,)``. gate (tf.Tensor): Gate matrix of shape ``(2, 2)``. qubits (tf.Tensor): Tensor that contains control and target qubits in sorted order. See :meth:`qibo.tensorflow.cgates.TensorflowGate.qubits_tensor` for more details. nqubits (int): Total number of qubits in the state vector. target (int): Qubit ID that the gate will act on. Must be smaller than ``nqubits``. Return: state (tf.Tensor): State vector of shape ``(2 ** nqubits,)`` after ``gate`` is applied. """ return custom_module.apply_gate(state, gate, qubits, nqubits, target, omp_num_threads)
def sample_frequencies(self, probs, nshots): from qibo.config import SHOT_CUSTOM_OP_THREASHOLD if self.op is None or nshots < SHOT_CUSTOM_OP_THREASHOLD: logits = self.log(probs)[self.newaxis] samples = self.random.categorical(logits, nshots, dtype=self.dtypes('DTYPEINT'))[0] res, counts = self.unique(samples, return_counts=True) frequencies = self.zeros(int(probs.shape[0]), dtype=self.dtypes('DTYPEINT')) frequencies = self.backend.tensor_scatter_nd_add( frequencies, res[:, self.newaxis], counts) else: from qibo.config import get_threads # Generate random seed using tf dtype = self.dtypes('DTYPEINT') seed = self.backend.random.uniform(shape=tuple(), maxval=int(1e8), dtype=dtype) nqubits = int(self.np.log2(tuple(probs.shape)[0])) shape = self.cast(2**nqubits, dtype='DTYPEINT') frequencies = self.zeros(shape, dtype='DTYPEINT') frequencies = self.op.measure_frequencies(frequencies, probs, nshots, nqubits, seed, get_threads()) return frequencies
def collapse_state(state, qubits, result, nqubits, normalize=True, omp_num_threads=get_threads()): return custom_module.collapse_state(state, qubits, result, nqubits, normalize, omp_num_threads)
def transpose_state(self, pieces, state, nqubits, order): if self.op is None: # pragma: no cover pieces = self.reshape(self.backend.stack(pieces), nqubits * (2, )) return self.reshape(self.transpose(pieces, order), state.shape) else: from qibo.config import get_threads return self.op.transpose_state(pieces, state, nqubits, order, get_threads())
def test_custom_op_toy_callback(gate, compile): """Check calculating ``callbacks`` using intermediate state values.""" import functools state = utils.random_tensorflow_complex((2 ** 2,), dtype=tf.float64) mask = utils.random_tensorflow_complex((2 ** 2,), dtype=tf.float64) matrices = {"h": np.array([[1, 1], [1, -1]]) / np.sqrt(2), "x": np.array([[0, 1], [1, 0]]), "z": np.array([[1, 0], [0, -1]])} for k, v in matrices.items(): matrices[k] = np.kron(v, np.eye(2)) matrices["swap"] = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]) target_state = state.numpy() target_c1 = mask.numpy().dot(target_state) target_state = matrices[gate].dot(target_state) target_c2 = mask.numpy().dot(target_state) assert target_c1 != target_c2 target_callback = [target_c1, target_c2] htf = tf.cast(np.array([[1, 1], [1, -1]]) / np.sqrt(2), dtype=state.dtype) qubits_t1 = qubits_tensor(2, [0]) qubits_t2 = qubits_tensor(2, [0, 1]) apply_gate = {"h": functools.partial(op.apply_gate, gate=htf, qubits=qubits_t1, nqubits=2, target=0, omp_num_threads=get_threads()), "x": functools.partial(op.apply_x, qubits=qubits_t1, nqubits=2, target=0, omp_num_threads=get_threads()), "z": functools.partial(op.apply_z, qubits=qubits_t1, nqubits=2, target=0, omp_num_threads=get_threads()), "swap": functools.partial(op.apply_swap, qubits=qubits_t2, nqubits=2, target1=0, target2=1, omp_num_threads=get_threads())} def apply_operator(state): c1 = tf.reduce_sum(mask * state) state0 = apply_gate[gate](state) c2 = tf.reduce_sum(mask * state0) return state0, tf.stack([c1, c2]) if compile: # pragma: no cover # case not tested because it fails apply_operator = tf.function(apply_operator) state, callback = apply_operator(state) np.testing.assert_allclose(target_state, state.numpy()) np.testing.assert_allclose(target_callback, callback.numpy())
def _swap(self, state, global_qubit: int, local_qubit: int): m = self.queues.qubits.reduced_global[global_qubit] m = self.nglobal - m - 1 t = 1 << m for g in range(self.ndevices // 2): i = ((g >> m) << (m + 1)) + (g & (t - 1)) local_eff = self.queues.qubits.reduced_local[local_qubit] with K.device(self.memory_device): K.op.swap_pieces(state.pieces[i], state.pieces[i + t], local_eff, self.nlocal, get_threads())
def test_transpose_state(nqubits, ndevices): for _ in range(10): # Generate global qubits randomly all_qubits = np.arange(nqubits) np.random.shuffle(all_qubits) qubit_order = list(all_qubits) state = utils.random_tensorflow_complex((2 ** nqubits,), dtype=tf.float64) state_tensor = state.numpy().reshape(nqubits * (2,)) target_state = np.transpose(state_tensor, qubit_order).ravel() new_state = tf.zeros_like(state) shape = (ndevices, int(state.shape[0]) // ndevices) state = tf.reshape(state, shape) pieces = [state[i] for i in range(ndevices)] if tf.config.list_physical_devices("GPU"): # pragma: no cover # case not tested by GitHub workflows because it requires GPU check_unimplemented_error(op.transpose_state, pieces, new_state, nqubits, qubit_order, get_threads()) else: new_state = op.transpose_state(pieces, new_state, nqubits, qubit_order, get_threads()) np.testing.assert_allclose(target_state, new_state.numpy())
def test_swap_pieces_zero_global(nqubits): state = utils.random_tensorflow_complex((2 ** nqubits,), dtype=tf.float64) target_state = tf.cast(np.copy(state.numpy()), dtype=state.dtype) shape = (2, int(state.shape[0]) // 2) state = tf.reshape(state, shape) for _ in range(10): local = np.random.randint(1, nqubits) qubits_t = qubits_tensor(nqubits, [0, local]) target_state = op.apply_swap(target_state, qubits_t, nqubits, 0, local, get_threads()) target_state = tf.reshape(target_state, shape) piece0, piece1 = state[0], state[1] if tf.config.list_physical_devices("GPU"): # pragma: no cover # case not tested by GitHub workflows because it requires GPU check_unimplemented_error(op.swap_pieces, piece0, piece1, local - 1, nqubits - 1, get_threads()) else: op.swap_pieces(piece0, piece1, local - 1, nqubits - 1, get_threads()) np.testing.assert_allclose(target_state[0], piece0.numpy()) np.testing.assert_allclose(target_state[1], piece1.numpy())
def test_vqe(method, options, compile, filename): """Performs a VQE circuit minimization test.""" import qibo original_backend = qibo.get_backend() if method == "sgd" or compile: qibo.set_backend("matmuleinsum") else: qibo.set_backend("custom") original_threads = get_threads() if method == 'parallel_L-BFGS-B': if 'GPU' in qibo.get_device(): # pragma: no cover pytest.skip("unsupported configuration") qibo.set_threads(1) nqubits = 6 layers = 4 circuit = Circuit(nqubits) for l in range(layers): for q in range(nqubits): circuit.add(gates.RY(q, theta=1.0)) for q in range(0, nqubits - 1, 2): circuit.add(gates.CZ(q, q + 1)) for q in range(nqubits): circuit.add(gates.RY(q, theta=1.0)) for q in range(1, nqubits - 2, 2): circuit.add(gates.CZ(q, q + 1)) circuit.add(gates.CZ(0, nqubits - 1)) for q in range(nqubits): circuit.add(gates.RY(q, theta=1.0)) hamiltonian = XXZ(nqubits=nqubits) np.random.seed(0) initial_parameters = np.random.uniform(0, 2 * np.pi, 2 * nqubits * layers + nqubits) v = VQE(circuit, hamiltonian) best, params = v.minimize(initial_parameters, method=method, options=options, compile=compile) if method == "cma": # remove `outcmaes` folder import shutil shutil.rmtree("outcmaes") if filename is not None: utils.assert_regression_fixture(params, filename) qibo.set_backend(original_backend) qibo.set_threads(original_threads)
def initial_state(self, nqubits, is_matrix=False): if self.op is None: # pragma: no cover dim = 1 + is_matrix shape = dim * (2**nqubits, ) idx = self.backend.constant([dim * [0]], dtype=self.dtypes('DTYPEINT')) state = self.backend.zeros(shape, dtype=self.dtypes('DTYPECPX')) update = self.backend.constant([1], dtype=self.dtypes('DTYPECPX')) state = self.backend.tensor_scatter_nd_update(state, idx, update) return state else: from qibo.config import get_threads return self.op.initial_state(nqubits, self.dtypes('DTYPECPX'), is_matrix=is_matrix, omp_num_threads=get_threads())
def assign_vector(self, full_state: tf.Tensor): """Splits a full state vector and assigns it to the ``tf.Variable`` pieces. Args: full_state (tf.Tensor): Full state vector as a tensor of shape ``(2 ** nqubits)``. """ with tf.device(self.device): full_state = tf.reshape(full_state, self.shapes["device"]) pieces = [full_state[i] for i in range(self.ndevices)] new_state = tf.zeros(self.shapes["device"], dtype=self.dtype) new_state = op.transpose_state(pieces, new_state, self.nqubits, self.qubits.transpose_order, get_threads()) for i in range(self.ndevices): self.pieces[i].assign(new_state[i])
def test_apply_pauli_gate(nqubits, target, gate, compile): """Check ``apply_x``, ``apply_y`` and ``apply_z`` kernels.""" matrices = {"x": np.array([[0, 1], [1, 0]], dtype=np.complex128), "y": np.array([[0, -1j], [1j, 0]], dtype=np.complex128), "z": np.array([[1, 0], [0, -1]], dtype=np.complex128)} state = utils.random_tensorflow_complex((2 ** nqubits,), dtype=tf.float64) target_state = tf.cast(state.numpy(), dtype=state.dtype) qubits = qubits_tensor(nqubits, [target]) target_state = op.apply_gate(state, matrices[gate], qubits, nqubits, target, get_threads()) def apply_operator(state): qubits = qubits_tensor(nqubits, [target]) return getattr(op, "apply_{}".format(gate))(state, qubits, nqubits, target, get_threads()) if compile: apply_operator = tf.function(apply_operator) state = apply_operator(state) np.testing.assert_allclose(target_state.numpy(), state.numpy())
def vector(self) -> tf.Tensor: """Returns the full state vector as a ``tf.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 tf.device(self.device): state = tf.concat([x[tf.newaxis] for x in self.pieces], axis=0) state = tf.reshape(state, self.shapes["full"]) elif self.qubits.list == list(range(self.nlocal, self.nqubits)): with tf.device(self.device): state = tf.concat([x[:, tf.newaxis] for x in self.pieces], axis=1) state = tf.reshape(state, self.shapes["full"]) else: # fall back to the transpose op with tf.device(self.device): state = tf.zeros(self.shapes["full"], dtype=self.dtype) state = op.transpose_state(self.pieces, state, self.nqubits, self.qubits.reverse_transpose_order, get_threads()) return state
def density_matrix_call(self, state: tf.Tensor) -> tf.Tensor: return self.gate_op(state, self.matrix, self.qubits_tensor, 2 * self.nqubits, *self.target_qubits_dm, get_threads())
def apply_operator(state): qubits = qubits_tensor(nqubits, [nqubits - 1], controls) return op.apply_gate(state, xgate, qubits, nqubits, nqubits - 1, get_threads())
def state_vector_call(self, state: tf.Tensor) -> tf.Tensor: return self.gate_op(state, self.qubits_tensor, self.result_tensor, self.nqubits, self.normalize, get_threads())
def density_matrix_call(self, state: tf.Tensor) -> tf.Tensor: state = self.gate_op(state, self.qubits_tensor_dm, self.result_tensor, 2 * self.nqubits, False, get_threads()) state = self.gate_op(state, self.qubits_tensor, self.result_tensor, 2 * self.nqubits, False, get_threads()) return state / tf.linalg.trace(state)
def apply_operator(state, gate): qubits = qubits_tensor(nqubits, [target]) return op.apply_gate(state, gate, qubits, nqubits, target, get_threads())
def state_vector_call(self, state: tf.Tensor) -> tf.Tensor: return self.gate_op(state, self.matrix, self.qubits_tensor, self.nqubits, *self.target_qubits, get_threads())
def apply_operator(state): qubits = qubits_tensor(nqubits, [target]) return getattr(op, "apply_{}".format(gate))(state, qubits, nqubits, target, get_threads())
def apply_operator(state): qubits = qubits_tensor(nqubits, [target], controls) return op.apply_z_pow(state, phase, qubits, nqubits, target, get_threads())
def apply_operator(state): qubits = qubits_tensor(nqubits, targets, controls) return op.apply_swap(state, qubits, nqubits, *targets, get_threads())
def apply_operator(state): qubits = qubits_tensor(2, [0, 1]) return op.apply_swap(state, qubits, 2, 0, 1, get_threads())