diff --git a/.gitignore b/.gitignore index 56fb6c78d2..1027fc4ff3 100644 --- a/.gitignore +++ b/.gitignore @@ -134,6 +134,7 @@ dmypy.json # tmp files tmp/ +tmp.npy # Mac .DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d641a95aa..38f39908e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: black - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.1 hooks: - id: isort args: ["--profile", "black"] diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index 8ecc66a1fd..82f82f32f4 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -1287,7 +1287,7 @@ types: - the **heuristics** optimizers: an evolutionary strategy (Covariance matrix adaptation evolution strategy (CMA-ES)), for which we rely on the `cmaes`_ packages and a Basin-Hopping algorithm implementation provided by Scipy as `scipy.optimize.basinhopping`_. These methods are global and, as in the case of many meta-heuristic optimizers they can be as versatile as they are computationally intensive; -- the **gradient based** optimizers built on top of `Tensorflow`_ implementation. This `TensorflowSGD` optimization routine has to be used activating the `tensorflow` backend. +- the **gradient based** optimizers built on top of `Tensorflow`_ implementation. This `TensorflowSGD` optimization routine has to be used activating the `tensorflow` backend. .. _`scipy.optimize.minimize`: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html .. _`scipy.optimize.basinhopping`: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.basinhopping.html @@ -1546,6 +1546,12 @@ Expressibility of parameterized quantum circuits .. autofunction:: qibo.quantum_info.expressibility +Frame Potential +""""""""""""""" + +.. autofunction:: qibo.quantum_info.frame_potential + + Random Ensembles ^^^^^^^^^^^^^^^^ diff --git a/src/qibo/gates/channels.py b/src/qibo/gates/channels.py index 07c4932e5d..d63ad9a354 100644 --- a/src/qibo/gates/channels.py +++ b/src/qibo/gates/channels.py @@ -179,6 +179,15 @@ def to_pauli_liouville( return super_op + def matrix(self, backend=None): + """""" + raise_error( + NotImplementedError, + "`matrix` method not defined for Channels. " + + "Please use one of the following methods: " + + "`to_choi` or `to_liouville` or `to_pauli_liouville`.", + ) + class KrausChannel(Channel): """General channel defined by arbitrary Kraus operators. diff --git a/src/qibo/quantum_info/metrics.py b/src/qibo/quantum_info/metrics.py index 4656f20692..dd162462d3 100644 --- a/src/qibo/quantum_info/metrics.py +++ b/src/qibo/quantum_info/metrics.py @@ -1116,7 +1116,7 @@ def expressibility( Defaults to ``None``. Returns: - float: Entangling capability. + float: Expressibility of parametrized circuit. """ if isinstance(power_t, int) is False: @@ -1146,6 +1146,82 @@ def expressibility( return fid +def frame_potential( + circuit, + power_t: int, + samples: int = None, + backend=None, +): + """Returns the frame potential of a parametrized circuit under uniform sampling of the parameters. + + For :math:`n` qubits and moment :math:`t`, the frame potential + :math:`\\mathcal{F}_{\\mathcal{U}}^{(t)}` if given by [1] + + .. math:: + \\mathcal{F}_{\\mathcal{U}}^{(t)} = \\int_{U,V \\in \\mathcal{U}} \\, + \\text{d}U \\, \\text{d}V \\, \\text{abs}\\bigl[\\text{tr}(U^{\\dagger} \\, V)\\bigr]^{2t} \\, , + + where :math:`\\mathcal{U}` is the group of unitaries defined by the parametrized circuit. + The frame potential is approximated by the average + + .. math:: + \\mathcal{F}_{\\mathcal{U}}^{(t)} \\approx \\frac{1}{N} \\, + \\sum_{k=1}^{N} \\, \\text{abs}\\bigl[ \\text{tr}(U_{k}^{\\dagger} \\, V_{k})\\bigr]^{2t} \\, , + + where :math:`N` is the number of ``samples``. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): Parametrized circuit. + power_t (int): power that defines the :math:`t`-design. + samples (int): number of samples to estimate the integral. + backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used + in the execution. If ``None``, it uses :class:`qibo.backends.GlobalBackend`. + Defaults to ``None``. + + Returns: + float: Frame potential of the parametrized circuit. + + References: + 1. M. Liu *et al.*, *Estimating the randomness of quantum circuit ensembles up to 50 qubits*. + `arXiv:2205.09900 [quant-ph] `_. + + """ + if not isinstance(power_t, int): + raise_error( + TypeError, f"power_t must be type int, but it is type {type(power_t)}." + ) + + if not isinstance(samples, int): + raise_error( + TypeError, f"samples must be type int, but it is type {type(samples)}." + ) + + if backend is None: # pragma: no cover + backend = GlobalBackend() + + nqubits = circuit.nqubits + dim = 2**nqubits + + potential = 0 + for _ in range(samples): + unitary_1 = circuit.copy() + params_1 = np.random.uniform(-np.pi, np.pi, circuit.trainable_gates.nparams) + unitary_1.set_parameters(params_1) + unitary_1 = unitary_1.unitary(backend) / np.sqrt(dim) + + for _ in range(samples): + unitary_2 = circuit.copy() + params_2 = np.random.uniform(-np.pi, np.pi, circuit.trainable_gates.nparams) + unitary_2.set_parameters(params_2) + unitary_2 = unitary_2.unitary(backend) / np.sqrt(dim) + + potential += np.abs( + np.trace(np.transpose(np.conj(unitary_1)) @ unitary_2) + ) ** (2 * power_t) + + return potential / samples**2 + + def _check_hermitian_or_not_gpu(matrix, backend=None): """Checks if a given matrix is Hermitian and whether the backend is neither :class:`qibojit.backends.CupyBackend` diff --git a/src/qibo/result.py b/src/qibo/result.py index 007fb2ea36..39babb81f3 100644 --- a/src/qibo/result.py +++ b/src/qibo/result.py @@ -68,7 +68,7 @@ def state(self, numpy: bool = False): """State's tensor representation as a backend tensor. Args: - numpy (bool, optional): If ``True`` the returned tensor will be a numpy array, + numpy (bool, optional): If ``True`` the returned tensor will be a ``numpy`` array, otherwise it will follow the backend tensor type. Defaults to ``False``. @@ -76,7 +76,8 @@ def state(self, numpy: bool = False): The state in the computational basis. """ if numpy: - return np.array(self._state) + return np.array(self._state.tolist()) + return self._state def probabilities(self, qubits: Optional[Union[list, set]] = None): diff --git a/tests/test_gates_channels.py b/tests/test_gates_channels.py index d6511f83a5..fb3aa0deb5 100644 --- a/tests/test_gates_channels.py +++ b/tests/test_gates_channels.py @@ -45,6 +45,8 @@ def test_general_channel(backend): with pytest.raises(NotImplementedError): state = random_statevector(2**2, backend=backend) channel1.apply(backend, state, 2) + with pytest.raises(NotImplementedError): + channel1.matrix(backend) def test_controlled_by_channel_error(): diff --git a/tests/test_quantum_info_metrics.py b/tests/test_quantum_info_metrics.py index 7cb1a15909..06efa6c61f 100644 --- a/tests/test_quantum_info_metrics.py +++ b/tests/test_quantum_info_metrics.py @@ -16,6 +16,7 @@ entropy, expressibility, fidelity, + frame_potential, gate_error, hilbert_schmidt_distance, impurity, @@ -649,3 +650,30 @@ def test_expressibility(backend): expr_3 = expressibility(c3, t, samples, backend=backend) backend.assert_allclose(expr_1 < expr_2 < expr_3, True) + + +@pytest.mark.parametrize("samples", [int(1e2)]) +@pytest.mark.parametrize("power_t", [2]) +@pytest.mark.parametrize("nqubits", [2, 3, 4]) +def test_frame_potential(backend, nqubits, power_t, samples): + depth = int(np.ceil(nqubits * power_t)) + + circuit = Circuit(nqubits) + circuit.add(gates.U3(q, 0.0, 0.0, 0.0) for q in range(nqubits)) + for _ in range(depth): + circuit.add(gates.CNOT(q, q + 1) for q in range(nqubits - 1)) + circuit.add(gates.U3(q, 0.0, 0.0, 0.0) for q in range(nqubits)) + + with pytest.raises(TypeError): + frame_potential(circuit, power_t="2", backend=backend) + with pytest.raises(TypeError): + frame_potential(circuit, 2, samples="1000", backend=backend) + + dim = 2**nqubits + potential_haar = 2 / dim**4 + + potential = frame_potential( + circuit, power_t=power_t, samples=samples, backend=backend + ) + + backend.assert_allclose(potential, potential_haar, rtol=1e-2, atol=1e-2)