Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add (state) Schmidt decomposition to quantum_info.linalg_operations #1471

Merged
merged 33 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
035836a
backup
renatomello Sep 20, 2024
9326027
Merge branch 'master' into schmidt
renatomello Sep 20, 2024
19ccd7f
Merge branch 'master' into schmidt
renatomello Sep 24, 2024
e6d4002
Merge branch 'master' into schmidt
renatomello Oct 2, 2024
ffabfe6
Merge branch 'master' into schmidt
renatomello Oct 4, 2024
858ed92
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 4, 2024
bb290e5
backup
renatomello Oct 4, 2024
3469c9c
point to svd
renatomello Oct 7, 2024
e126e60
Merge branch 'svd' into schmidt
renatomello Oct 7, 2024
248b908
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 7, 2024
0248926
stash
renatomello Oct 7, 2024
8be4f15
api ref
renatomello Oct 7, 2024
c924ad0
docstring
renatomello Oct 7, 2024
9ae4daf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 7, 2024
3d8f956
Merge branch 'svd' into schmidt
renatomello Oct 7, 2024
ec93331
Merge branch 'schmidt' of github.com:qiboteam/qibo into schmidt
renatomello Oct 7, 2024
b93529c
comprehensive testing
renatomello Oct 7, 2024
7c18032
fix bug + docstring
renatomello Oct 7, 2024
142dbdc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 7, 2024
d67fb06
coverage
renatomello Oct 7, 2024
51123a0
replace transpiler function
renatomello Oct 7, 2024
8474a15
fix test
renatomello Oct 7, 2024
8f2fdfe
Merge branch 'master' into schmidt
renatomello Oct 7, 2024
55b4114
Merge branch 'master' into schmidt
renatomello Oct 7, 2024
34fa053
Merge branch 'master' into schmidt
renatomello Oct 7, 2024
29fd726
Merge branch 'master' into schmidt
renatomello Oct 8, 2024
df41fd7
Merge branch 'master' into schmidt
renatomello Oct 8, 2024
4f2103f
Merge branch 'master' into schmidt
renatomello Oct 9, 2024
6f81efd
Merge branch 'master' into schmidt
renatomello Oct 9, 2024
438f7f7
Merge branch 'master' into schmidt
renatomello Oct 11, 2024
b7f0de3
Merge branch 'master' into schmidt
renatomello Oct 14, 2024
0c8db5e
Merge branch 'master' into schmidt
renatomello Oct 16, 2024
dcee04a
Update src/qibo/quantum_info/linalg_operations.py
renatomello Oct 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2005,6 +2005,12 @@ Singular value decomposition
.. autofunction:: qibo.quantum_info.singular_value_decomposition


Schmidt decomposition
"""""""""""""""""""""

.. autofunction:: qibo.quantum_info.schmidt_decomposition


Quantum Networks
^^^^^^^^^^^^^^^^

Expand Down
57 changes: 57 additions & 0 deletions src/qibo/quantum_info/linalg_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,60 @@ def singular_value_decomposition(matrix, backend=None):
backend = _check_backend(backend)

return backend.calculate_singular_value_decomposition(matrix)


def schmidt_decomposition(
state, partition: Union[List[int], Tuple[int, ...]], backend=None
):
"""Return the Schmidt decomposition of a :math:`n`-qubit bipartite pure quantum ``state``.

Given a bipartite pure state :math:`\\ket{\\psi}\\in\\mathcal{H}_{A}\\otimes\\mathcal{H}_{B}`,
its Schmidt decomposition is given by

.. math::
\\ket{\\psi} = \\sum_{k = 1}^{\\min\\{a, \\, b\\}} \\, c_{k} \\,
\\ket{\\phi_{k}} \\otimes \\ket{\\nu_{k}} \\, ,

with :math:`a` and :math:`b` being the respective cardinalities of :math:`\\mathcal{H}_{A}`
and :math:`\\mathcal{H}_{B}`, and :math:`\\{\\phi_{k}\\}_{k\\in[\\min\\{a, \\, b\\}]}
\\subset \\mathcal{H}_{A}` and :math:`\\{\\nu_{k}\\}_{k\\in[\\min\\{a, \\, b\\}]}
\\subset \\mathcal{H}_{B}` being orthonormal sets. The coefficients
:math:`\\{c_{k}\\}_{k\\in[\\min\\{a, \\, b\\}]}` are real, non-negative, and unique
up to re-ordering.

The decomposition is calculated using :func:`qibo.quantum_info.singular_value_decomposition`,
resulting in

.. math::
\\ketbra{\\psi}{\\psi} = U \\, S \\, V^{\\dagger} \\, ,

where :math:`U` is an :math:`a \\times a` unitary matrix, :math:`V` is an :math:`b \\times b`
unitary matrix, and :math:`S` is an :math:`a \\times b` positive semidefinite diagonal matrix
that contains the singular values of :math:`\\ketbra{\\psi}{\\psi}`.

Args:
state (ndarray): stevector or density matrix.
partition (Union[List[int], Tuple[int, ...]]): indices of qubits in one of the two
partitions. The other partition is inferred as the remaining qubits.
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:
ndarray, ndarray, ndarray: Respectively, the matrices :math:`U`, :math:`S`,
and :math:`V^{\\dagger}`.
"""
backend = _check_backend(backend)

nqubits = math.log2(state.shape[-1])
if not nqubits.is_integer():
raise_error(ValueError, f"dimensions of ``state`` must be a power of 2.")

nqubits = int(nqubits)
partition_2 = partition.__class__(set(list(range(nqubits))) ^ set(partition))

tensor = backend.np.reshape(state, [2] * nqubits)
tensor = backend.np.transpose(tensor, partition + partition_2)
tensor = backend.np.reshape(tensor, (2 ** len(partition), -1))

return singular_value_decomposition(tensor, backend=backend)
32 changes: 6 additions & 26 deletions src/qibo/transpiler/unitary_decompositions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from qibo import gates, matrices
from qibo.config import raise_error
from qibo.quantum_info.linalg_operations import schmidt_decomposition

magic_basis = np.array(
[[1, -1j, 0, 0], [0, 0, 1, -1j], [0, 0, -1, -1j], [1, 1j, 0, 0]]
Expand Down Expand Up @@ -83,30 +84,7 @@ def calculate_psi(unitary, backend, magic_basis=magic_basis):
return psi, eigvals


def schmidt_decompose(state, backend):
"""Decomposes a two-qubit product state to its single-qubit parts.

Args:
state (ndarray): product state to be decomposed.

Returns:
(ndarray, ndarray): decomposition

"""
# tf.linalg.svd has a different behaviour
if backend.__class__.__name__ == "TensorflowBackend":
u, d, v = np.linalg.svd(backend.np.reshape(state, (2, 2)))
else:
u, d, v = backend.np.linalg.svd(backend.np.reshape(state, (2, 2)))
if not np.allclose(backend.to_numpy(d), [1, 0]): # pragma: no cover
raise_error(
ValueError,
f"Unexpected singular values: {d}\nCan only decompose product states.",
)
return u[:, 0], v[0]


def calculate_single_qubit_unitaries(psi, backend):
def calculate_single_qubit_unitaries(psi, backend=None):
"""Calculates local unitaries that maps a maximally entangled basis to the magic basis.

See Lemma 1 of Appendix A in arXiv:quant-ph/0011050.
Expand All @@ -130,8 +108,10 @@ def calculate_single_qubit_unitaries(psi, backend):
# find e and f by inverting (A3), (A4)
ef = (psi_bar[0] + 1j * psi_bar[1]) / np.sqrt(2)
e_f_ = (psi_bar[0] - 1j * psi_bar[1]) / np.sqrt(2)
e, f = schmidt_decompose(ef, backend=backend)
e_, f_ = schmidt_decompose(e_f_, backend=backend)
e, _, f = schmidt_decomposition(ef, [0], backend=backend)
e, f = e[:, 0], f[0]
e_, _, f_ = schmidt_decomposition(e_f_, [0], backend=backend)
e_, f_ = e_[:, 0], f_[0]
# find exp(1j * delta) using (A5a)
ef_ = backend.np.kron(e, f_)
phase = (
Expand Down
29 changes: 29 additions & 0 deletions tests/test_quantum_info_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
matrix_power,
partial_trace,
partial_transpose,
schmidt_decomposition,
singular_value_decomposition,
)
from qibo.quantum_info.metrics import purity
Expand Down Expand Up @@ -263,3 +264,31 @@ def test_singular_value_decomposition(backend):
S_sorted, coeffs_sorted = S_sorted[0], coeffs_sorted[0]

backend.assert_allclose(S_sorted, coeffs_sorted)


def test_schmidt_decomposition(backend):
with pytest.raises(ValueError):
test = random_statevector(3, backend=backend)
test = schmidt_decomposition(test, [0], backend=backend)

state_A = random_statevector(4, seed=10, backend=backend)
state_B = random_statevector(4, seed=11, backend=backend)
state = backend.np.kron(state_A, state_B)

U, S, Vh = schmidt_decomposition(state, [0, 1], backend=backend)

# recovering original state
recovered = np.zeros_like(state.shape, dtype=complex)
recovered = backend.cast(recovered, dtype=recovered.dtype)
for coeff, u, vh in zip(S, U.T, Vh):
if abs(coeff) > 1e-10:
recovered = recovered + coeff * backend.np.kron(u, vh)

backend.assert_allclose(recovered, state)

# entropy test
coeffs = backend.np.abs(S) ** 2
entropy = backend.np.where(backend.np.abs(S) < 1e-10, 0.0, backend.np.log(coeffs))
entropy = -backend.np.sum(coeffs * entropy)

backend.assert_allclose(entropy, 0.0, atol=1e-14)