Skip to content

Commit

Permalink
Merge pull request #208 from Budapest-Quantum-Computing-Group/kz-back…
Browse files Browse the repository at this point in the history
…port

FIX(gaussian): Fix fidelity calculation on 2.0
  • Loading branch information
Kolarovszki authored Nov 3, 2022
2 parents 2c091d6 + 85de9df commit a59adf5
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 44 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Changelog


## [2.0.1] - 2022-11-03

### Fixed

- `GaussianState.fidelity` gave incorrect results for multiple modes and it
needed to be corrected.


## [2.0.0] - 2022-10-30

### Added
Expand Down
2 changes: 1 addition & 1 deletion piquasso/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,4 @@
"LossyInterferometer",
]

__version__ = "2.0.0"
__version__ = "2.0.1"
114 changes: 73 additions & 41 deletions piquasso/_backends/gaussian/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from typing import Tuple, List

import numpy as np
from scipy.linalg import sqrtm

from piquasso.api.config import Config
from piquasso.api.exceptions import InvalidState, InvalidParameter, PiquassoException
Expand All @@ -28,7 +27,7 @@
is_symmetric,
is_positive_semidefinite,
)
from piquasso._math.symplectic import symplectic_form, xp_symplectic_form
from piquasso._math.symplectic import symplectic_form
from piquasso._math.combinatorics import get_occupation_numbers
from piquasso._math.transformations import from_xxpp_to_xpxp_transformation_matrix

Expand Down Expand Up @@ -621,66 +620,99 @@ def fidelity(self, state: "GaussianState") -> float:
:math:`\rho_1, \rho_2` is given by:
.. math::
\operatorname{F}(\rho_1, \rho_2) = \operatorname{Tr}(\sqrt{\sqrt{\rho_1}
\rho_2\sqrt{\rho_1}})^2
\operatorname{F}(\rho_1, \rho_2) = \left [
\operatorname{Tr} \sqrt{\sqrt{\rho_1}
\rho_2 \sqrt{\rho_1} }
\right ]
A gaussian state can be represented by its Covariance matrix and the vector of
Means. Hence, the above equation can be rewritten as:
A Gaussian state can be represented by its covariance matrix and displacement
vector.
Let :math:`\mu_1, \mu_2` be the displacement vectors and
:math:`\sigma_1, \sigma_2` be the covariance matrices of the
:math:`\rho_1, \rho_2` Gaussian states, respectively. Define
:math:`\hat{\sigma} = \frac{\sigma_1 + \sigma_2}{2}` and
:math:`\Delta\mu = \mu_2 - \mu_1`.
The fidelity can be written as
.. math::
\operatorname{F} = \operatorname{F_0}(V_1, V_2) \exp(
-\frac{1}{2} \delta_u^T(V_1 + V_2)^{-1}\delta_u^T)
\operatorname{F}(\rho_1, \rho_2) = \operatorname{F_0}(\sigma_1, \sigma_2)
\exp \left(
-\frac{1}{4} \Delta\mu^T (\hat{\sigma})^{-1} \Delta\mu
\right),
where :math:`V` is the :attr:`xpxp_covariance_matrix` of the gaussian state,
:math:`\delta_u` is the difference between mean vectors of the two gaussian
states represented by :attr:`xpxp_mean_vector`, and :math:`F_0` is given by:
where :math:`F_0` can be written as
.. math::
\operatorname{F_0} = \sqrt{\det{[2(\sqrt{I +
\frac{(V_{aux}\Omega)^-2}{4}} + I)V_{aux}]} \det{[(V_1 + V_2)^{-1}]}}
\operatorname{F_0} = \frac{
\prod_{i=1}^d \left [w_i + \sqrt{w_i^2 - 1} \right]
}{
\sqrt{\det \hat{\sigma}}
},
where :math:`V_{aux}` is given by
where :math:`w_i \geq 1` and :math:`\pm w_i` are the eigenvalues of the matrix
.. math::
\Omega^T (V_1 + V_2)^{-1} (\frac{\Omega}{4} V_2 \Omega V_1)
W := - \frac{i}{2} \Omega^T \hat{\sigma}^{-1} \left(
I - \sigma_2 \Omega \sigma_1 \Omega
\right)
and :math:`\Omega` is a symplectic matrix of shape :math:`2*d \times 2*d`.
For more details please check:
https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.115.260501.
and
.. math::
\Omega = \begin{bmatrix}
0 & 1 \\-1 & 0
\end{bmatrix} \otimes I_{2d \times 2d}.
References:
- `Quantum fidelity for arbitrary Gaussian states <https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.115.260501>`_.
Note:
In this notation :math:`\sqrt{\hbar} \mu_i` is equivalent to
:attr:`xpxp_mean_vector` and :math:`\hbar \sigma_i` is equivalent to
:attr:`xpxp_covariance_matrix`.
Args:
state: A gaussian state
:class:`~piquasso._backends.gaussian.state.GaussianState` that can be
used to calculate the fidelity aganist it.
state: Another :class:`~piquasso._backends.gaussian.state.GaussianState`
instance.
Returns:
float: The calculated fidelity.
"""
mean_1, cov_1 = (
self.xpxp_mean_vector,
self.xpxp_covariance_matrix / (2 * self._config.hbar),
)
mean_2, cov_2 = (
state.xpxp_mean_vector,
state.xpxp_covariance_matrix / (2 * state._config.hbar),
""" # noqa: E501
hbar = self._config.hbar

sigma_1 = self.xpxp_covariance_matrix / hbar
sigma_2 = state.xpxp_covariance_matrix / hbar

sigma_mean = (sigma_1 + sigma_2) / 2

Omega = symplectic_form(self.d)

Id = np.identity(2 * self.d)

W_aux = (
-1j
/ 2
* Omega.T
@ np.linalg.inv(sigma_mean)
@ (Id - sigma_2 @ Omega @ sigma_1 @ Omega)
)

W = xp_symplectic_form(self.d)
ident = np.identity(self.d * 2, dtype=complex)
eigenvalues = np.linalg.eigvals(W_aux)
positive_eigenvalues = eigenvalues[eigenvalues >= 0]

sum_cov_inv = np.linalg.inv(cov_1 + cov_2)
V_aux = W.T @ sum_cov_inv @ (0.25 * W + cov_2 @ W @ cov_1)
delta_mu = (mean_1 - mean_2) / np.sqrt(self._config.hbar)
F_0 = np.prod(
[w + np.sqrt(w**2 - 1) for w in positive_eigenvalues]
) / np.sqrt(np.linalg.det(sigma_mean))

f1 = np.exp(-0.5 * delta_mu @ sum_cov_inv @ delta_mu)
f_total = (
2
* (sqrtm(ident + 0.25 * np.linalg.inv(V_aux @ W @ V_aux @ W)) + ident)
@ V_aux
mu_1 = self.xpxp_mean_vector / np.sqrt(hbar)
mu_2 = state.xpxp_mean_vector / np.sqrt(hbar)
delta_mu = mu_2 - mu_1
displaced_contribition = np.exp(
-1 / 2 * delta_mu @ np.linalg.inv(sigma_mean) @ delta_mu
)
f_total = np.sqrt(np.linalg.det(f_total) * np.linalg.det(sum_cov_inv))

return float((f_total * f1).real)
return np.real(displaced_contribition * F_0)

def quadratic_polynomial_expectation(
self, A: np.ndarray, b: np.ndarray, c: float = 0.0, phi: float = 0.0
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

setup(
name="piquasso",
version="2.0.0",
version="2.0.1",
packages=find_packages(exclude=["tests.*", "tests", "scripts", "scripts.*"]),
maintainer="Budapest Quantum Computing Group",
maintainer_email="[email protected]",
Expand Down
136 changes: 135 additions & 1 deletion tests/backends/test_backend_equivalence.py
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ def test_wigner_function_equivalence():
pq.FockSimulator,
),
)
def test_fidelity(SimulatorClass):
def test_fidelity_for_1_mode(SimulatorClass):
with pq.Program() as program_1:
pq.Q() | pq.Vacuum()
pq.Q(0) | pq.Squeezing(r=0.2, phi=-np.pi / 3)
Expand All @@ -974,6 +974,140 @@ def test_fidelity(SimulatorClass):
assert np.isclose(fidelity, state_2.fidelity(state_1))


@pytest.mark.parametrize(
"SimulatorClass",
(
pq.GaussianSimulator,
pq.PureFockSimulator,
pq.FockSimulator,
),
)
def test_fidelity_for_nondisplaced_states_on_2_modes(SimulatorClass):
with pq.Program() as program_1:
pq.Q() | pq.Vacuum()

pq.Q(all) | pq.Squeezing([0.1, 0.2])
pq.Q(all) | pq.Beamsplitter(theta=np.pi / 3, phi=np.pi / 7)

with pq.Program() as program_2:
pq.Q() | pq.Vacuum()

pq.Q(all) | pq.Squeezing([0.3, 0.1])
pq.Q(all) | pq.Beamsplitter(theta=np.pi / 4, phi=np.pi / 9)

simulator = SimulatorClass(d=2, config=pq.Config(cutoff=9))

state_1 = simulator.execute(program_1).state
state_2 = simulator.execute(program_2).state
fidelity = state_1.fidelity(state_2)

assert np.isclose(fidelity, 0.9735085877314046)
assert np.isclose(fidelity, state_2.fidelity(state_1))


@pytest.mark.parametrize(
"SimulatorClass",
(
pq.GaussianSimulator,
pq.PureFockSimulator,
pq.FockSimulator,
),
)
def test_fidelity_for_displaced_states_on_2_modes(SimulatorClass):
with pq.Program() as program_1:
pq.Q() | pq.Vacuum()

pq.Q(all) | pq.Displacement(alpha=[0.4, 0.5])
pq.Q(all) | pq.Squeezing([0.1, 0.2])
pq.Q(all) | pq.Beamsplitter(theta=np.pi / 3, phi=np.pi / 7)

with pq.Program() as program_2:
pq.Q() | pq.Vacuum()

pq.Q(all) | pq.Displacement(alpha=[0.5, 0.4])
pq.Q(all) | pq.Squeezing([0.3, 0.1])
pq.Q(all) | pq.Beamsplitter(theta=np.pi / 4, phi=np.pi / 9)

simulator = SimulatorClass(d=2, config=pq.Config(cutoff=10))

state_1 = simulator.execute(program_1).state
state_2 = simulator.execute(program_2).state
fidelity = state_1.fidelity(state_2)

assert np.isclose(fidelity, 0.9346675071279842)
assert np.isclose(fidelity, state_2.fidelity(state_1))


@pytest.mark.parametrize(
"SimulatorClass",
(
pq.GaussianSimulator,
pq.PureFockSimulator,
pq.FockSimulator,
),
)
def test_fidelity_for_nondisplaced_pure_states_on_3_modes(SimulatorClass):
with pq.Program() as program_1:
pq.Q() | pq.Vacuum()

pq.Q(all) | pq.Displacement(alpha=[0.04, 0.05, 0.1])
pq.Q(all) | pq.Squeezing([0.01, 0.02, 0.03])
pq.Q(0, 1) | pq.Beamsplitter(theta=np.pi / 3, phi=np.pi / 7)
pq.Q(1, 2) | pq.Beamsplitter(theta=np.pi / 4, phi=np.pi / 9)

with pq.Program() as program_2:
pq.Q() | pq.Vacuum()

pq.Q(all) | pq.Displacement(alpha=[0.05, 0.04, 0.02])
pq.Q(all) | pq.Squeezing([0.03, 0.01, 0.02])
pq.Q(0, 1) | pq.Beamsplitter(theta=np.pi / 2, phi=np.pi / 9)
pq.Q(1, 2) | pq.Beamsplitter(theta=np.pi / 5, phi=np.pi / 3)

simulator = SimulatorClass(d=3, config=pq.Config(cutoff=7))

state_1 = simulator.execute(program_1).state
state_2 = simulator.execute(program_2).state
fidelity = state_1.fidelity(state_2)

assert np.isclose(fidelity, 0.9889124929545777)
assert np.isclose(fidelity, state_2.fidelity(state_1))


@pytest.mark.parametrize(
"SimulatorClass",
(
pq.GaussianSimulator,
pq.PureFockSimulator,
pq.FockSimulator,
),
)
def test_fidelity_for_nondisplaced_mixed_states_on_3_modes(SimulatorClass):
with pq.Program() as program_1:
pq.Q() | pq.Vacuum()

pq.Q(all) | pq.Displacement(alpha=[0.04, 0.05, 0.1])
pq.Q(all) | pq.Squeezing([0.01, 0.02, 0.03])
pq.Q(0, 1) | pq.Beamsplitter(theta=np.pi / 3, phi=np.pi / 7)
pq.Q(1, 2) | pq.Beamsplitter(theta=np.pi / 4, phi=np.pi / 9)

with pq.Program() as program_2:
pq.Q() | pq.Vacuum()

pq.Q(all) | pq.Displacement(alpha=[0.05, 0.04, 0.02])
pq.Q(all) | pq.Squeezing([0.03, 0.01, 0.02])
pq.Q(0, 1) | pq.Beamsplitter(theta=np.pi / 2, phi=np.pi / 9)
pq.Q(1, 2) | pq.Beamsplitter(theta=np.pi / 5, phi=np.pi / 3)

simulator = SimulatorClass(d=3, config=pq.Config(cutoff=7))

state_1 = simulator.execute(program_1).state.reduced(modes=(0, 1))
state_2 = simulator.execute(program_2).state.reduced(modes=(0, 1))
fidelity = state_1.fidelity(state_2)

assert np.isclose(fidelity, 0.9959871270027937)
assert np.isclose(fidelity, state_2.fidelity(state_1))


@pytest.mark.parametrize(
"SimulatorClass",
(
Expand Down

0 comments on commit a59adf5

Please sign in to comment.