Skip to content

Commit

Permalink
Merge branch 'master' into internal_modes
Browse files Browse the repository at this point in the history
  • Loading branch information
nquesada authored Feb 13, 2024
2 parents e6de063 + 8759363 commit 127a1a7
Show file tree
Hide file tree
Showing 10 changed files with 613 additions and 125 deletions.
2 changes: 2 additions & 0 deletions .github/ACKNOWLEDGMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,5 @@
* [Martin Houde](https://github.com/MHoude2) (Polytechnique Montréal) - 🙃 Minister of amplification

* Will McCutcheon (Heriot-Watt University) - 🧅 Gaussian Onion Merchant

* [Yanic Cardin](https://github.com/yaniccd) (Polytechnique Montréal) - 🦜 Pirate of the permutations
42 changes: 33 additions & 9 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,53 @@
# Release 0.21.0-dev
# Release 0.22.0-dev

### New features

* Adds the Takagi decomposition [(#363)](https://github.com/XanaduAI/thewalrus/pull/338)

### Breaking changes

### Improvements

* Further simplifies the implementation of `decompositions.williamson` and corrects its docstring [(#380)](https://github.com/XanaduAI/thewalrus/pull/380).

* Tighten power-trace bound of odd loop Hafnian. [(#362)](https://github.com/XanaduAI/thewalrus/pull/362)

* Simplifies the internal working of Bloch-Messiah decomposition [(#363)](https://github.com/XanaduAI/thewalrus/pull/338).
* Further simplifies the implementation of `decompositions.blochmessiah` [(#381)](https://github.com/XanaduAI/thewalrus/pull/381).

* Simplifies the internal working of Williamson decomposition [(#366)](https://github.com/XanaduAI/thewalrus/pull/338).

### Bug fixes

### Documentation

### Contributors

This release contains contributions from (in alphabetical order):
This release contains contributions from (in alphabetical order):

Nicolas Quesada

---

# Release 0.21.0

### New features

* Adds the Takagi decomposition [(#363)](https://github.com/XanaduAI/thewalrus/pull/363)

* Adds the Montrealer and Loop Montrealer functions [(#363)](https://github.com/XanaduAI/thewalrus/pull/374).

### Improvements

* Tighten power-trace bound of odd loop Hafnian. [(#362)](https://github.com/XanaduAI/thewalrus/pull/362)

* Simplifies the internal working of Bloch-Messiah decomposition [(#363)](https://github.com/XanaduAI/thewalrus/pull/338).

* Simplifies the internal working of Williamson decomposition [(#366)](https://github.com/XanaduAI/thewalrus/pull/338).

* Improves the handling of an edge case in Takagi [(#373)](https://github.com/XanaduAI/thewalrus/pull/373).

* Adds extra tests for the Takagi decomposition [(#377)](https://github.com/XanaduAI/thewalrus/pull/377)

### Contributors

This release contains contributions from (in alphabetical order):

Gregory Morse, Nicolas Quesada
Yanic Cardin, Gregory Morse, Nicolas Quesada

---

Expand Down
8 changes: 8 additions & 0 deletions thewalrus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@
rec_torontonian,
rec_ltorontonian,
)

from ._montrealer import (
mtl,
lmtl,
)

from ._version import __version__


Expand All @@ -152,6 +158,8 @@
"reduction",
"hermite_multidimensional",
"grad_hermite_multidimensional",
"mtl",
"lmtl",
"version",
]

Expand Down
134 changes: 134 additions & 0 deletions thewalrus/_montrealer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""
Montrealer Python interface
* Yanic Cardin and Nicolás Quesada. "Photon-number moments and cumulants of Gaussian states"
`arxiv:12212.06067 (2023) <https://arxiv.org/abs/2212.06067>`_
"""
import numpy as np
import numba
from thewalrus.quantum.conversions import Xmat
from thewalrus.charpoly import powertrace
from ._hafnian import nb_ix
from ._torontonian import tor_input_checks


@numba.jit(nopython=True, cache=True)
def dec2bin(num, n): # pragma: no cover
"""Helper function to convert an integer into an element of the power-set of ``n`` objects.
Args:
num (int): label to convert
n (int): number of elements in the set
Returns:
(array): array containing the labels of the elements to be selected
"""
digits = np.zeros((n), dtype=type(num))
nn = num
counter = -1
while nn >= 1:
digits[counter] = nn % 2
counter -= 1
nn //= 2
return np.nonzero(digits)[0]


@numba.jit(nopython=True)
def montrealer(Sigma): # pragma: no cover
"""Calculates the loop-montrealer of the zero-displacement Gaussian state with the given complex covariance matrix.
Args:
Sigma (array): adjacency matrix of the Gaussian state
Returns:
(np.complex128): the montrealer of ``Sigma``
"""
n = len(Sigma) // 2
tot_num = 2**n
val = np.complex128(0)
for p in numba.prange(tot_num):
pos = dec2bin(p, n)
lenpos = len(pos)
pos = np.concatenate((pos, n + pos))
submat = nb_ix(Sigma, pos, pos)
sign = (-1) ** (lenpos + 1)
val += (sign) * powertrace(submat, n + 1)[-1]
return (-1) ** (n + 1) * val / (2 * n)


@numba.jit(nopython=True)
def power_loop(Sigma, zeta, n): # pragma: no cover
"""Auxiliary function to calculate the product ``np.conj(zeta) @ Sigma^{n-1} @ zeta``.
Args:
Sigma (array): square complex matrix
zeta (array): complex vector
n (int): sought after power
Returns:
(np.complex128 or np.float64): the product np.conj(zeta) @ Sigma^{n-1} @ zeta
"""
vec = zeta
for _ in range(n - 1):
vec = Sigma @ vec
return np.conj(zeta) @ vec


@numba.jit(nopython=True, cache=True)
def lmontrealer(Sigma, zeta): # pragma: no cover
"""Calculates the loop-montrealer of the displaced Gaussian state with the given complex covariance matrix and vector of displacements.
Args:
Sigma (array): complex Glauber covariance matrix of the Gaussian state
zeta (array): vector of displacements
Returns:
(np.complex128): the montrealer of ``Sigma``
"""
n = len(Sigma) // 2
tot_num = 2**n
val = np.complex128(0)
val_loops = np.complex128(0)
for p in numba.prange(tot_num):
pos = dec2bin(p, n)
lenpos = len(pos)
pos = np.concatenate((pos, n + pos))
subvec = zeta[pos]
submat = nb_ix(Sigma, pos, pos)
sign = (-1) ** (lenpos + 1)
val_loops += sign * power_loop(submat, subvec, n)
val += sign * powertrace(submat, n + 1)[-1]
return (-1) ** (n + 1) * (val / (2 * n) + val_loops / 2)


def lmtl(A, zeta):
"""Returns the montrealer of an NxN matrix and an N-length vector.
Args:
A (array): an NxN array of even dimensions
zeta (array): an N-length vector of even dimensions
Returns:
np.float64 or np.complex128: the loop montrealer of matrix A, vector zeta
"""

tor_input_checks(A, zeta)
n = len(A) // 2
Sigma = Xmat(n) @ A
return lmontrealer(Sigma, zeta)


def mtl(A):
"""Returns the montrealer of an NxN matrix.
Args:
A (array): an NxN array of even dimensions.
Returns:
np.float64 or np.complex128: the montrealer of matrix ``A``
"""

tor_input_checks(A)
n = len(A) // 2
Sigma = Xmat(n) @ A
return montrealer(Sigma)
49 changes: 24 additions & 25 deletions thewalrus/_torontonian.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,41 @@
from ._hafnian import reduction, find_kept_edges, nb_ix


def tor(A, recursive=True):
"""Returns the Torontonian of a matrix.
def tor_input_checks(A, loops=None):
"""Checks the correctness of the inputs for the torontonian/montrealer.
Args:
A (array): a square array of even dimensions.
recursive: use the faster recursive implementation.
Returns:
np.float64 or np.complex128: the torontonian of matrix A.
A (array): an NxN array of even dimensions
loops (array): optional argument, an N-length vector of even dimensions
"""
if not isinstance(A, np.ndarray):
raise TypeError("Input matrix must be a NumPy array.")

matshape = A.shape

if matshape[0] != matshape[1]:
raise ValueError("Input matrix must be square.")

if matshape[0] % 2 != 0:
raise ValueError("matrix dimension must be even")

if loops is not None:
if not isinstance(loops, np.ndarray):
raise TypeError("Input matrix must be a NumPy array.")
if matshape[0] != len(loops):
raise ValueError("gamma must be a vector matching the dimension of A")


def tor(A, recursive=True):
"""Returns the Torontonian of a matrix.
Args:
A (array): a square array of even dimensions.
recursive: use the faster recursive implementation.
Returns:
np.float64 or np.complex128: the torontonian of matrix A.
"""
tor_input_checks(A)
return rec_torontonian(A) if recursive else numba_tor(A)


Expand All @@ -54,23 +69,7 @@ def ltor(A, gamma, recursive=True):
Returns:
np.float64 or np.complex128: the loop torontonian of matrix A, vector gamma
"""

if not isinstance(A, np.ndarray):
raise TypeError("Input matrix must be a NumPy array.")

if not isinstance(gamma, np.ndarray):
raise TypeError("Input matrix must be a NumPy array.")

matshape = A.shape

if matshape[0] != matshape[1]:
raise ValueError("Input matrix must be square.")

if matshape[0] != len(gamma):
raise ValueError("gamma must be a vector matching the dimension of A")

if matshape[0] % 2 != 0:
raise ValueError("matrix dimension must be even")
tor_input_checks(A, gamma)

return rec_ltorontonian(A, gamma) if recursive else numba_ltor(A, gamma)

Expand Down
2 changes: 1 addition & 1 deletion thewalrus/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
Version number (major.minor.patch[-label])
"""

__version__ = "0.21.0-dev"
__version__ = "0.22.0-dev"
Loading

0 comments on commit 127a1a7

Please sign in to comment.