diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index 1f4c99fe95..87d985c5cc 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -1524,6 +1524,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/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/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)