From 0deaca421b2ca3f72dea7d27bf981c0a9f0592db Mon Sep 17 00:00:00 2001 From: Nicolas Quesada Date: Tue, 20 Oct 2020 19:48:21 -0400 Subject: [PATCH 01/11] First working version with tests --- thewalrus/random.py | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/thewalrus/random.py b/thewalrus/random.py index c3fbdeeed..75db1d894 100644 --- a/thewalrus/random.py +++ b/thewalrus/random.py @@ -117,3 +117,55 @@ def random_interferometer(N, real=False): ph = d / np.abs(d) U = np.multiply(q, ph, q) return U + + +def random_block_interferometer(N, top_one=True, real=False): + r"""Random interferometer with blocks of at most size 2. + + Args: + N (int): number of modes + top_one (bool): if true places a `1\times1` interferometer in the top-left most block + real (bool): return a random real orthogonal matrix + + Returns: + array: random :math:`N\times N` unitary with the specified block structure + """ + if N % 2 == 0: + if top_one is True: + u2s = [random_interferometer(2, real=real) for i in range(((N // 2) - 1))] + u0 = random_interferometer(1, real=real) + u1 = random_interferometer(1, real=real) + return sp.linalg.block_diag(u0, *u2s, u1) + else: + u2s = [random_interferometer(2, real=real) for i in range(N // 2)] + return sp.linalg.block_diag(*u2s) + + else: + u2s = [random_interferometer(2, real=real) for i in range((N - 1) // 2)] + u0 = random_interferometer(1, real=real) + if top_one is True: + return sp.linalg.block_diag(u0, *u2s) + else: + return sp.linalg.block_diag(*u2s, u0) + +def random_banded_interferometer(N, w, top_one_init=True, real=False): + r"""Generates a banded unitary matrix + + Args: + N (int): number of modes + w (int): bandwidth + top_one_init (bool): if true places a `1\times1` interferometer in the top-left-most block of the first matrix in the product + real (bool): return a random real orthogonal matrix + + Returns: + array: random :math:`N\times N` unitary with the specified block structure + """ + if N < w + 1: + raise ValueError("The bandwidth can be at most one minus the size of the matrix.") + if N == w + 1: + return random_interferometer(N, real=real) + U = sp.linalg.block_diag(*[random_interferometer(1, real=real) for _ in range(N)]) + for i in range(w): + U = U @ random_block_interferometer(N, top_one=top_one_init, real=real) + top_one_init = not top_one_init + return U From c5a2f0dedfa5b7c6177a188c9162640ef986a981 Mon Sep 17 00:00:00 2001 From: Nicolas Quesada Date: Tue, 20 Oct 2020 19:57:42 -0400 Subject: [PATCH 02/11] Adds extra test --- thewalrus/tests/test_random.py | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 thewalrus/tests/test_random.py diff --git a/thewalrus/tests/test_random.py b/thewalrus/tests/test_random.py new file mode 100644 index 000000000..407047475 --- /dev/null +++ b/thewalrus/tests/test_random.py @@ -0,0 +1,65 @@ +# Copyright 2019 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Random tests""" +import pytest + +import numpy as np +from thewalrus.random import random_block_interferometer, random_banded_interferometer + + +def bandwidth(A): + """Calculates the upper bandwidth of the matrix A + + Args: + A (array): input matrix + + Returns: + (int): bandwidth of matrix + """ + n, _ = A.shape + for i in range(n): + vali = np.diag(A, i) + if np.allclose(vali, 0): + return i - 1 + return n - 1 + + +@pytest.mark.parametrize("n", [5, 7, 8, 9]) +@pytest.mark.parametrize("top_one", [True, False]) +@pytest.mark.parametrize("real", [True, False]) +def test_random_block(n, top_one, real): + """Test that random_block_interferometer produces a unitary with the right structure""" + U = random_block_interferometer(n, top_one=top_one, real=real) + assert np.allclose(U @ U.T.conj(), np.identity(n)) + if top_one: + assert np.allclose(U[0, 1], 0) + assert np.allclose(U[1, 0], 0) + + +@pytest.mark.parametrize("n", [5, 7, 8, 9]) +@pytest.mark.parametrize("top_one_init", [True, False]) +@pytest.mark.parametrize("real", [True, False]) +def test_random_banded(n, top_one_init, real): + """Test that random_banded_interferometer produces a unitary with the right structure""" + for w in range(n): + U = random_banded_interferometer(n, w, top_one_init=top_one_init, real=real) + assert np.allclose(U @ U.T.conj(), np.identity(n)) + assert w == bandwidth(U) + +def test_wrong_bandwidth(): + """Test tha the correct error is raised if w > n-1""" + n = 10 + w = 10 + with pytest.raises(ValueError, match="The bandwidth can be at most one minus the size of the matrix."): + random_banded_interferometer(n, w) From 454dffa2862e1c0099c82c4d020840e25f331749 Mon Sep 17 00:00:00 2001 From: Nicolas Quesada Date: Wed, 21 Oct 2020 09:39:03 -0400 Subject: [PATCH 03/11] Fixes linting --- thewalrus/random.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/thewalrus/random.py b/thewalrus/random.py index 75db1d894..0fc7a3648 100644 --- a/thewalrus/random.py +++ b/thewalrus/random.py @@ -136,17 +136,16 @@ def random_block_interferometer(N, top_one=True, real=False): u0 = random_interferometer(1, real=real) u1 = random_interferometer(1, real=real) return sp.linalg.block_diag(u0, *u2s, u1) - else: - u2s = [random_interferometer(2, real=real) for i in range(N // 2)] - return sp.linalg.block_diag(*u2s) + + u2s = [random_interferometer(2, real=real) for i in range(N // 2)] + return sp.linalg.block_diag(*u2s) else: u2s = [random_interferometer(2, real=real) for i in range((N - 1) // 2)] u0 = random_interferometer(1, real=real) if top_one is True: return sp.linalg.block_diag(u0, *u2s) - else: - return sp.linalg.block_diag(*u2s, u0) + return sp.linalg.block_diag(*u2s, u0) def random_banded_interferometer(N, w, top_one_init=True, real=False): r"""Generates a banded unitary matrix @@ -165,7 +164,7 @@ def random_banded_interferometer(N, w, top_one_init=True, real=False): if N == w + 1: return random_interferometer(N, real=real) U = sp.linalg.block_diag(*[random_interferometer(1, real=real) for _ in range(N)]) - for i in range(w): + for _ in range(w): U = U @ random_block_interferometer(N, top_one=top_one_init, real=real) top_one_init = not top_one_init return U From 8d3faf62154df2353a656a1df7a4ade5f64da538 Mon Sep 17 00:00:00 2001 From: Nicolas Quesada Date: Wed, 21 Oct 2020 09:40:33 -0400 Subject: [PATCH 04/11] removes unnecesary if --- thewalrus/random.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/thewalrus/random.py b/thewalrus/random.py index 0fc7a3648..8e77f3e55 100644 --- a/thewalrus/random.py +++ b/thewalrus/random.py @@ -140,12 +140,11 @@ def random_block_interferometer(N, top_one=True, real=False): u2s = [random_interferometer(2, real=real) for i in range(N // 2)] return sp.linalg.block_diag(*u2s) - else: - u2s = [random_interferometer(2, real=real) for i in range((N - 1) // 2)] - u0 = random_interferometer(1, real=real) - if top_one is True: - return sp.linalg.block_diag(u0, *u2s) - return sp.linalg.block_diag(*u2s, u0) + u2s = [random_interferometer(2, real=real) for i in range((N - 1) // 2)] + u0 = random_interferometer(1, real=real) + if top_one is True: + return sp.linalg.block_diag(u0, *u2s) + return sp.linalg.block_diag(*u2s, u0) def random_banded_interferometer(N, w, top_one_init=True, real=False): r"""Generates a banded unitary matrix From fe0dc17d1285d2347ff9fde0c2ea7de2fa39f20f Mon Sep 17 00:00:00 2001 From: Nicolas Quesada Date: Wed, 21 Oct 2020 15:30:49 -0400 Subject: [PATCH 05/11] Updates CHANGELOG --- .github/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 4d1bbfb51..1f674286b 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,6 +2,8 @@ ### New features +* Adds the functions `random_banded_interferometer` to generate unitary matrices with a given bandwidth. [#208](https://github.com/XanaduAI/thewalrus/pull/208) + ### Improvements ### Bug fixes From fef393207fb34d74c76597569b8deeaa9d6b31b9 Mon Sep 17 00:00:00 2001 From: Nicolas Quesada Date: Wed, 21 Oct 2020 15:31:29 -0400 Subject: [PATCH 06/11] Correct typo --- .github/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 1f674286b..a548cfbc1 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,7 +2,7 @@ ### New features -* Adds the functions `random_banded_interferometer` to generate unitary matrices with a given bandwidth. [#208](https://github.com/XanaduAI/thewalrus/pull/208) +* Adds the function `random_banded_interferometer` to generate unitary matrices with a given bandwidth. [#208](https://github.com/XanaduAI/thewalrus/pull/208) ### Improvements From aa4cf7d03af80010d80d051b68dfbeaaea876cc1 Mon Sep 17 00:00:00 2001 From: Nicolas Quesada Date: Wed, 21 Oct 2020 15:31:59 -0400 Subject: [PATCH 07/11] Adds name --- .github/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index a548cfbc1..4a6c98086 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -14,6 +14,8 @@ This release contains contributions from (in alphabetical order): +Nicolas Quesada + --- # Version 0.14.0 From 4eb57461e8aec21730005cf1132d4bae4fd706b9 Mon Sep 17 00:00:00 2001 From: Nicolas Quesada Date: Wed, 21 Oct 2020 15:46:48 -0400 Subject: [PATCH 08/11] Update thewalrus/tests/test_random.py --- thewalrus/tests/test_random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thewalrus/tests/test_random.py b/thewalrus/tests/test_random.py index 407047475..005951a5b 100644 --- a/thewalrus/tests/test_random.py +++ b/thewalrus/tests/test_random.py @@ -58,7 +58,7 @@ def test_random_banded(n, top_one_init, real): assert w == bandwidth(U) def test_wrong_bandwidth(): - """Test tha the correct error is raised if w > n-1""" + """Test that the correct error is raised if w > n-1""" n = 10 w = 10 with pytest.raises(ValueError, match="The bandwidth can be at most one minus the size of the matrix."): From 87f4bcff2f1396551435356599a216aa25f75012 Mon Sep 17 00:00:00 2001 From: Nicolas Quesada Date: Wed, 21 Oct 2020 21:52:27 -0400 Subject: [PATCH 09/11] Update thewalrus/tests/test_random.py --- thewalrus/tests/test_random.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/thewalrus/tests/test_random.py b/thewalrus/tests/test_random.py index 005951a5b..2141e413c 100644 --- a/thewalrus/tests/test_random.py +++ b/thewalrus/tests/test_random.py @@ -56,6 +56,9 @@ def test_random_banded(n, top_one_init, real): U = random_banded_interferometer(n, w, top_one_init=top_one_init, real=real) assert np.allclose(U @ U.T.conj(), np.identity(n)) assert w == bandwidth(U) +```suggestion + + def test_wrong_bandwidth(): """Test that the correct error is raised if w > n-1""" From 2a425b2a4ee978fe89197c20d009be775ae70f06 Mon Sep 17 00:00:00 2001 From: Nicolas Quesada Date: Fri, 23 Oct 2020 11:38:08 -0400 Subject: [PATCH 10/11] corrects typo --- thewalrus/tests/test_random.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/thewalrus/tests/test_random.py b/thewalrus/tests/test_random.py index 2141e413c..005951a5b 100644 --- a/thewalrus/tests/test_random.py +++ b/thewalrus/tests/test_random.py @@ -56,9 +56,6 @@ def test_random_banded(n, top_one_init, real): U = random_banded_interferometer(n, w, top_one_init=top_one_init, real=real) assert np.allclose(U @ U.T.conj(), np.identity(n)) assert w == bandwidth(U) -```suggestion - - def test_wrong_bandwidth(): """Test that the correct error is raised if w > n-1""" From 62ae1bb76db9194b00934913e292f291b093a957 Mon Sep 17 00:00:00 2001 From: Nicolas Quesada Date: Fri, 23 Oct 2020 15:49:53 -0400 Subject: [PATCH 11/11] Apply suggestions from code review Co-authored-by: Olivia Di Matteo <2068515+glassnotes@users.noreply.github.com> --- thewalrus/random.py | 12 ++++++------ thewalrus/tests/test_random.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/thewalrus/random.py b/thewalrus/random.py index 8e77f3e55..38ad01f5d 100644 --- a/thewalrus/random.py +++ b/thewalrus/random.py @@ -120,18 +120,18 @@ def random_interferometer(N, real=False): def random_block_interferometer(N, top_one=True, real=False): - r"""Random interferometer with blocks of at most size 2. + r"""Generates a random interferometer with blocks of at most size 2. Args: N (int): number of modes - top_one (bool): if true places a `1\times1` interferometer in the top-left most block + top_one (bool): if True places a `1\times1` interferometer in the top-left most block real (bool): return a random real orthogonal matrix Returns: array: random :math:`N\times N` unitary with the specified block structure """ if N % 2 == 0: - if top_one is True: + if top_one: u2s = [random_interferometer(2, real=real) for i in range(((N // 2) - 1))] u0 = random_interferometer(1, real=real) u1 = random_interferometer(1, real=real) @@ -142,17 +142,17 @@ def random_block_interferometer(N, top_one=True, real=False): u2s = [random_interferometer(2, real=real) for i in range((N - 1) // 2)] u0 = random_interferometer(1, real=real) - if top_one is True: + if top_one: return sp.linalg.block_diag(u0, *u2s) return sp.linalg.block_diag(*u2s, u0) def random_banded_interferometer(N, w, top_one_init=True, real=False): - r"""Generates a banded unitary matrix + r"""Generates a banded unitary matrix. Args: N (int): number of modes w (int): bandwidth - top_one_init (bool): if true places a `1\times1` interferometer in the top-left-most block of the first matrix in the product + top_one_init (bool): if True places a `1\times1` interferometer in the top-left-most block of the first matrix in the product real (bool): return a random real orthogonal matrix Returns: diff --git a/thewalrus/tests/test_random.py b/thewalrus/tests/test_random.py index 005951a5b..6a7393c7f 100644 --- a/thewalrus/tests/test_random.py +++ b/thewalrus/tests/test_random.py @@ -19,7 +19,7 @@ def bandwidth(A): - """Calculates the upper bandwidth of the matrix A + """Calculates the upper bandwidth of the matrix A. Args: A (array): input matrix @@ -39,7 +39,7 @@ def bandwidth(A): @pytest.mark.parametrize("top_one", [True, False]) @pytest.mark.parametrize("real", [True, False]) def test_random_block(n, top_one, real): - """Test that random_block_interferometer produces a unitary with the right structure""" + """Test that random_block_interferometer produces a unitary with the right structure.""" U = random_block_interferometer(n, top_one=top_one, real=real) assert np.allclose(U @ U.T.conj(), np.identity(n)) if top_one: @@ -51,14 +51,14 @@ def test_random_block(n, top_one, real): @pytest.mark.parametrize("top_one_init", [True, False]) @pytest.mark.parametrize("real", [True, False]) def test_random_banded(n, top_one_init, real): - """Test that random_banded_interferometer produces a unitary with the right structure""" + """Test that random_banded_interferometer produces a unitary with the right structure.""" for w in range(n): U = random_banded_interferometer(n, w, top_one_init=top_one_init, real=real) assert np.allclose(U @ U.T.conj(), np.identity(n)) - assert w == bandwidth(U) + assert bandwidth(U) == w def test_wrong_bandwidth(): - """Test that the correct error is raised if w > n-1""" + """Test that the correct error is raised if w > n-1.""" n = 10 w = 10 with pytest.raises(ValueError, match="The bandwidth can be at most one minus the size of the matrix."):