From 6338ed4fa9f08700d14c17be1b48eb6e3f41c916 Mon Sep 17 00:00:00 2001 From: elib20 <53090166+elib20@users.noreply.github.com> Date: Fri, 17 Dec 2021 18:06:46 -0500 Subject: [PATCH 1/9] Adds rectangular GKP states - Adds a parameter, alpha, to specify peak spacing of the GKP state - Modifies both fock and bosonic backends --- .../backends/bosonicbackend/backend.py | 26 +++++++-- .../backends/fockbackend/backend.py | 16 ++++-- .../backends/fockbackend/circuit.py | 8 ++- strawberryfields/backends/fockbackend/ops.py | 30 +++++----- strawberryfields/ops.py | 11 +++- tests/backend/test_bosonic_backend.py | 57 +++++++++++++++++++ untitled0.py | 43 ++++++++++++++ 7 files changed, 163 insertions(+), 28 deletions(-) create mode 100644 untitled0.py diff --git a/strawberryfields/backends/bosonicbackend/backend.py b/strawberryfields/backends/bosonicbackend/backend.py index e72da5769..fa44c1e33 100644 --- a/strawberryfields/backends/bosonicbackend/backend.py +++ b/strawberryfields/backends/bosonicbackend/backend.py @@ -535,7 +535,9 @@ def prepare_cat_real_rep(self, alpha, phi, ampl_cutoff, D): return weights, means, cov - def prepare_gkp(self, state, epsilon, ampl_cutoff, representation="real", shape="square"): + def prepare_gkp( + self, state, epsilon, ampl_cutoff, representation="real", shape="square", alpha=1 + ): r"""Prepares the arrays of weights, means and covs for a finite energy GKP state. GKP states are qubits, with the qubit state defined by: @@ -550,6 +552,7 @@ def prepare_gkp(self, state, epsilon, ampl_cutoff, representation="real", shape= ampl_cutoff (float): this determines how many terms to keep representation (str): ``'real'`` or ``'complex'`` reprsentation shape (str): shape of the lattice; default 'square' + alpha (float): peak spacing in q is given by sqrt(alpha * pi) Returns: tuple: arrays of the weights, means and covariances for the state @@ -561,12 +564,19 @@ def prepare_gkp(self, state, epsilon, ampl_cutoff, representation="real", shape= if representation == "complex": raise NotImplementedError("The complex description of GKP is not implemented") - if shape != "square": - raise NotImplementedError("Only square GKP are implemented for now") + if shape not in ["square", "rectangular"]: + raise NotImplementedError("Only square and rectangular GKP are implemented.") + + if shape == "square": + if alpha != 1: + raise ValueError( + "For square GKPs, alpha must be 1. For alpha not equal to " + + "1, use shape='rectangular'." + ) theta, phi = state[0], state[1] - def coeff(peak_loc): + def coeff(peak_loc, alpha): """Returns the value of the weight for a given peak. Args: @@ -605,7 +615,7 @@ def coeff(peak_loc): prefactor = np.exp( -np.pi * 0.25 - * (l ** 2 + m ** 2) + * ((l * alpha) ** 2 + (m / alpha) ** 2) * (1 - np.exp(-2 * epsilon)) / (1 + np.exp(-2 * epsilon)) ) @@ -644,7 +654,7 @@ def coeff(peak_loc): ) # Calculate the weights for each peak - weights = coeff(means) + weights = coeff(means, np.sqrt(alpha)) filt = abs(weights) > ampl_cutoff weights = weights[filt] @@ -652,7 +662,11 @@ def coeff(peak_loc): # Apply finite energy effect to means means = means[filt] + means[:, 0] *= np.sqrt(alpha) + means[:, 1] /= np.sqrt(alpha) + means *= 0.5 * damping * np.sqrt(np.pi * self.circuit.hbar) + # Covariances all the same covs = ( 0.5 diff --git a/strawberryfields/backends/fockbackend/backend.py b/strawberryfields/backends/fockbackend/backend.py index 80ba64580..b01427c8b 100644 --- a/strawberryfields/backends/fockbackend/backend.py +++ b/strawberryfields/backends/fockbackend/backend.py @@ -292,7 +292,7 @@ def measure_fock(self, modes, shots=1, select=None, **kwargs): return self.circuit.measure_fock(self._remap_modes(modes), select=select) def prepare_gkp( - self, state, epsilon, ampl_cutoff, representation="real", shape="square", mode=None + self, state, epsilon, ampl_cutoff, representation="real", shape="square", alpha=1, mode=None ): r"""Prepares the Fock representation of a finite energy GKP state. @@ -308,6 +308,7 @@ def prepare_gkp( amplcutoff (float): this determines how many terms to keep representation (str): ``'real'`` or ``'complex'`` reprsentation shape (str): shape of the lattice; default 'square' + alpha (float): peak spacing in q is given by sqrt(alpha * pi) Returns: tuple: arrays of the weights, means and covariances for the state @@ -319,8 +320,15 @@ def prepare_gkp( if representation == "complex": raise NotImplementedError("The complex description of GKP is not implemented") - if shape != "square": - raise NotImplementedError("Only square GKP are implemented for now") + if shape not in ["square", "rectangular"]: + raise NotImplementedError("Only square and rectangular GKP are implemented.") + + if shape == "square": + if alpha != 1: + raise ValueError( + "For square GKPs, alpha must be 1. For alpha not equal to " + + "1, use shape='rectangular'." + ) theta, phi = state[0], state[1] - self.circuit.prepare_gkp(theta, phi, epsilon, ampl_cutoff, self._remap_modes(mode)) + self.circuit.prepare_gkp(theta, phi, epsilon, ampl_cutoff, alpha, self._remap_modes(mode)) diff --git a/strawberryfields/backends/fockbackend/circuit.py b/strawberryfields/backends/fockbackend/circuit.py index 699dd219f..525de9efe 100644 --- a/strawberryfields/backends/fockbackend/circuit.py +++ b/strawberryfields/backends/fockbackend/circuit.py @@ -793,13 +793,15 @@ def measure_homodyne(self, phi, mode, select=None, **kwargs): # `homodyne_sample` will always be a single value since multiple shots is not supported return np.array([[homodyne_sample]]) - def prepare_gkp(self, theta, phi, epsilon, ampl_cutoff, mode): + def prepare_gkp(self, theta, phi, epsilon, alpha, ampl_cutoff, mode): """ Prepares a mode in a GKP state. """ if self._pure: - self.prepare(ops.square_gkp_state(theta, phi, epsilon, ampl_cutoff, self._trunc), mode) + self.prepare( + ops.rect_gkp_state(theta, phi, epsilon, alpha, ampl_cutoff, self._trunc), mode + ) else: - st = ops.square_gkp_state(theta, phi, epsilon, ampl_cutoff, self._trunc) + st = ops.rect_gkp_state(theta, phi, epsilon, alpha, ampl_cutoff, self._trunc) self.prepare(np.outer(st, st.conjugate()), mode) diff --git a/strawberryfields/backends/fockbackend/ops.py b/strawberryfields/backends/fockbackend/ops.py index 372af7c23..801cc6e9b 100644 --- a/strawberryfields/backends/fockbackend/ops.py +++ b/strawberryfields/backends/fockbackend/ops.py @@ -500,7 +500,7 @@ def hermiteVals(q_mag, num_bins, m_omega_over_hbar, trunc): return q_tensor, Hvals -def gkp_displacements(t, k, epsilon): +def gkp_displacements(t, k, epsilon, alpha): """ Helper function to generate the displacements parameters associated with the teeth of GKP computational basis state k. @@ -509,14 +509,15 @@ def gkp_displacements(t, k, epsilon): t (array): the teeth of GKP computational basis k (int): a computational basis state label, can be either 0 or 1 epsilon (float): finite energy parameter of the state + alpha (float): peak spacing in q is given by sqrt(alpha * pi) Returns: array: the displacements """ - return np.sqrt(0.5 * np.pi) * (2 * t + k) / np.cosh(epsilon) + return np.sqrt(0.5 * np.pi * alpha) * (2 * t + k) / np.cosh(epsilon) -def gkp_coeffs(t, k, epsilon): +def gkp_coeffs(t, k, epsilon, alpha): """ Helper function to generate the coefficient parameters associated with the teeth of GKP computational basis state k. @@ -525,22 +526,24 @@ def gkp_coeffs(t, k, epsilon): t (array): the teeth of GKP computational basis k (int): a computational basis state label, can be either 0 or 1 epsilon (float): finite energy parameter of the state + alpha (float): peak spacing in q is given by sqrt(alpha * pi) Returns: array: the coefficients """ - return np.exp(-0.5 * np.pi * np.tanh(epsilon) * (k + 2 * t) ** 2) + return np.exp(-0.5 * np.pi * alpha * np.tanh(epsilon) * (k + 2 * t) ** 2) @functools.lru_cache() -def square_gkp_basis_state(i, epsilon, ampl_cutoff, cutoff): +def rect_gkp_basis_state(i, epsilon, ampl_cutoff, alpha, cutoff): """ - Generate the Fock expansion of a (subnormalized) computational GKP basis state. Normalization occurs in the ``square_gkp_state`` method. + Generate the Fock expansion of a (subnormalized) computational GKP basis state. Normalization occurs in the ``rect_gkp_state`` method. Args: i (int): a computational basis state label, can be either 0 or 1 epsilon (float): finite energy parameter of the state ampl_cutoff (float): this determines how many terms to keep in the Hilbert space expansion + alpha (float): peak spacing in q is given by sqrt(alpha * pi) cutoff (int): Fock space truncation Returns @@ -548,16 +551,16 @@ def square_gkp_basis_state(i, epsilon, ampl_cutoff, cutoff): """ z_max = int(np.ceil(np.sqrt(-0.25 / np.pi * np.log(ampl_cutoff) / np.tanh(epsilon)))) - coeffs = [gkp_coeffs(t, i, epsilon) for t in range(-z_max, z_max + 1)] + coeffs = [gkp_coeffs(t, i, epsilon, alpha) for t in range(-z_max, z_max + 1)] r = -0.5 * np.log(np.tanh(epsilon)) - alphas = [gkp_displacements(t, i, epsilon) for t in range(-z_max, z_max + 1)] - num_kets = len(alphas) - ket = [coeffs[j] * displacedSqueezed(alphas[j], 0, r, 0, cutoff) for j in range(num_kets)] + disps = [gkp_displacements(t, i, epsilon, alpha) for t in range(-z_max, z_max + 1)] + num_kets = len(disps) + ket = [coeffs[j] * displacedSqueezed(disps[j], 0, r, 0, cutoff) for j in range(num_kets)] return sum(ket) @functools.lru_cache() -def square_gkp_state(theta, phi, epsilon, ampl_cutoff, cutoff): +def rect_gkp_state(theta, phi, epsilon, ampl_cutoff, alpha, cutoff): r""" Generate the Fock expansion of an abitrary GKP state parametrized as :math:`|\psi\rangle = \cos{\tfrac{\theta}{2}} \vert 0 \rangle_{\rm gkp} + e^{-i \phi} \sin{\tfrac{\theta}{2}} \vert 1 \rangle_{\rm gkp}`. @@ -567,6 +570,7 @@ def square_gkp_state(theta, phi, epsilon, ampl_cutoff, cutoff): phi (float): the longitude with respect to the x-axis in the Bloch sphere epsilon (float): finite energy parameter of the state ampl_cutoff (float): this determines how many terms to keep + alpha (float): peak spacing in q is given by sqrt(alpha * pi) cutoff (int): Fock space truncation Returns: @@ -574,8 +578,8 @@ def square_gkp_state(theta, phi, epsilon, ampl_cutoff, cutoff): """ qubit_coeff0 = np.cos(theta / 2) qubit_coeff1 = np.sin(theta / 2) * np.exp(-1j * phi) - ket0 = square_gkp_basis_state(0, epsilon, ampl_cutoff, cutoff) - ket1 = square_gkp_basis_state(1, epsilon, ampl_cutoff, cutoff) + ket0 = rect_gkp_basis_state(0, epsilon, ampl_cutoff, alpha, cutoff) + ket1 = rect_gkp_basis_state(1, epsilon, ampl_cutoff, alpha, cutoff) ket = qubit_coeff0 * ket0 + qubit_coeff1 * ket1 ket /= np.linalg.norm(ket) return ket diff --git a/strawberryfields/ops.py b/strawberryfields/ops.py index 9fa8f44b5..0706c464d 100644 --- a/strawberryfields/ops.py +++ b/strawberryfields/ops.py @@ -912,14 +912,21 @@ class GKP(Preparation): cutoff (float): this determines how many terms to keep representation (str): ``'real'`` or ``'complex'`` reprsentation shape (str): shape of the lattice; default ``'square'`` + alpha (float): peak spacing in q is given by sqrt(alpha * pi) """ def __init__( - self, state=None, epsilon=0.2, ampl_cutoff=1e-12, representation="real", shape="square" + self, + state=None, + epsilon=0.2, + ampl_cutoff=1e-12, + representation="real", + shape="square", + alpha=1, ): if state is None: state = [0, 0] - super().__init__([state, epsilon, ampl_cutoff, representation, shape]) + super().__init__([state, epsilon, ampl_cutoff, representation, shape, alpha]) def _apply(self, reg, backend, **kwargs): backend.prepare_gkp(*self.p, mode=reg[0]) diff --git a/tests/backend/test_bosonic_backend.py b/tests/backend/test_bosonic_backend.py index 179d9b2db..d0919a8a4 100644 --- a/tests/backend/test_bosonic_backend.py +++ b/tests/backend/test_bosonic_backend.py @@ -508,6 +508,35 @@ def test_gkp_complex(self): backend = bosonic.BosonicBackend() with pytest.raises(NotImplementedError): backend.run_prog(prog) + + @pytest.mark.parametrize("eps", EPS_VALS) + def test_sensor_gkp(self, eps): + r"""Checks that the GKP sensor state is invariant under Fourier.""" + x = np.linspace(-3 * np.sqrt(np.pi), 3 * np.sqrt(np.pi), 40) + p = np.copy(x) + + # Prepare GKP 0 and apply Hadamard + prog_sensor = sf.Program(1) + with prog_sensor.context as q: + sf.ops.GKP(state=[np.pi/2,0], epsilon=eps, shape="rectangular", alpha=2) | q[0] + + backend_sensor = bosonic.BosonicBackend() + backend_sensor.run_prog(prog_sensor) + state_sensor = backend_sensor.state() + wigner_sensor = state_sensor.wigner(0, x, p) + + # Prepare GKP + + prog_sensor_F = sf.Program(1) + with prog_sensor_F.context as qF: + sf.ops.GKP(state=[np.pi/2,0], epsilon=eps, shape="rectangular", alpha=2) | qF[0] + sf.ops.Rgate(np.pi / 2) | qF[0] + + backend_sensor_F = bosonic.BosonicBackend() + backend_sensor_F.run_prog(prog_sensor_F) + state_sensor_F = backend_sensor_F.state() + wigner_sensor_F = state_sensor_F.wigner(0, x, p) + + assert np.allclose(wigner_sensor, wigner_sensor_F) @pytest.mark.parametrize("fock_eps", [0.1,0.2,0.3]) def test_gkp_wigner_compare_backends(self, fock_eps): @@ -535,6 +564,34 @@ def test_gkp_wigner_compare_backends(self, fock_eps): gkp_bosonic = eng_bosonic.run(prog_bosonic).state W_bosonic = gkp_bosonic.wigner(0, xvec, xvec) assert np.allclose(W_fock, W_bosonic) + + @pytest.mark.parametrize("fock_eps", [0.1,0.2,0.3]) + @pytest.mark.parametrize("rect_ratio", [0.8,1,1.5]) + def test_rect_gkp_wigner_compare_backends(self, fock_eps, rect_ratio): + """Test correct Wigner function are generated by comparing fock and bosonic outputs. + Since the fock backend cannot simulate very small epsilon, we restrict to fock_eps>0.1.""" + theta = np.pi / 8 + phi = np.pi / 6 + ket = [theta, phi] + xvec = np.linspace(-1, 1, 200) + prog_fock = sf.Program(1) + with prog_fock.context as q: + sf.ops.GKP(epsilon=fock_eps, state=ket, shape="rectangular", alpha=rect_ratio) | q + cutoff = 100 + hbar = 2 + eng_fock = sf.Engine("fock", backend_options={"cutoff_dim": cutoff, "hbar": hbar}) + gkp_fock = eng_fock.run(prog_fock).state + W_fock = gkp_fock.wigner(0, xvec, xvec) + + prog_bosonic = sf.Program(1) + + with prog_bosonic.context as q: + sf.ops.GKP(epsilon=fock_eps, state=ket, shape="rectangular", alpha=rect_ratio) | q + hbar = 2 + eng_bosonic = sf.Engine("bosonic", backend_options={"hbar": hbar}) + gkp_bosonic = eng_bosonic.run(prog_bosonic).state + W_bosonic = gkp_bosonic.wigner(0, xvec, xvec) + assert np.allclose(W_fock, W_bosonic) class TestBosonicUserSpecifiedState: diff --git a/untitled0.py b/untitled0.py new file mode 100644 index 000000000..b089544f6 --- /dev/null +++ b/untitled0.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Dec 17 15:18:59 2021 + +@author: Xanadu +""" + +import strawberryfields as sf +import numpy as np +import pylab as plt + +fock_eps = 0.2 +alpha = 1.2 +xvec = np.linspace(-5, 5, 200) + +prog_fock = sf.Program(1) +with prog_fock.context as q: + sf.ops.GKP(epsilon=fock_eps, state=[np.pi/2,0], shape="rectangular", alpha=alpha) | q +cutoff = 40 +sf.hbar = 1 +eng_fock = sf.Engine("fock", backend_options={"cutoff_dim": cutoff}) +gkp_fock = eng_fock.run(prog_fock).state +W_fock = gkp_fock.wigner(0, xvec, xvec) + +prog_bosonic = sf.Program(1) + +with prog_bosonic.context as q: + sf.ops.GKP(epsilon=fock_eps, state = [np.pi/2,0], shape="rectangular", alpha=alpha) | q +sf.hbar = 1 +eng_bosonic = sf.Engine("bosonic") +gkp_bosonic = eng_bosonic.run(prog_bosonic).state +W_bosonic = gkp_bosonic.wigner(0, xvec, xvec) + + +plt.contourf(xvec/np.sqrt(np.pi*sf.hbar*2),xvec/np.sqrt(np.pi*sf.hbar*2),W_fock,levels=50) +plt.show() + +plt.contourf(xvec/np.sqrt(np.pi*sf.hbar*2),xvec/np.sqrt(np.pi*sf.hbar*2),W_bosonic,levels=50) +plt.show() + +plt.contourf(xvec/np.sqrt(np.pi*sf.hbar*2),xvec/np.sqrt(np.pi*sf.hbar*2),np.log(abs(W_fock-W_bosonic)),levels=50) +plt.colorbar() +plt.show() \ No newline at end of file From 7c70407795e198fbe2af8c29506186ac04411f04 Mon Sep 17 00:00:00 2001 From: elib20 <53090166+elib20@users.noreply.github.com> Date: Fri, 17 Dec 2021 18:21:44 -0500 Subject: [PATCH 2/9] Delete untitled0.py --- untitled0.py | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 untitled0.py diff --git a/untitled0.py b/untitled0.py deleted file mode 100644 index b089544f6..000000000 --- a/untitled0.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Dec 17 15:18:59 2021 - -@author: Xanadu -""" - -import strawberryfields as sf -import numpy as np -import pylab as plt - -fock_eps = 0.2 -alpha = 1.2 -xvec = np.linspace(-5, 5, 200) - -prog_fock = sf.Program(1) -with prog_fock.context as q: - sf.ops.GKP(epsilon=fock_eps, state=[np.pi/2,0], shape="rectangular", alpha=alpha) | q -cutoff = 40 -sf.hbar = 1 -eng_fock = sf.Engine("fock", backend_options={"cutoff_dim": cutoff}) -gkp_fock = eng_fock.run(prog_fock).state -W_fock = gkp_fock.wigner(0, xvec, xvec) - -prog_bosonic = sf.Program(1) - -with prog_bosonic.context as q: - sf.ops.GKP(epsilon=fock_eps, state = [np.pi/2,0], shape="rectangular", alpha=alpha) | q -sf.hbar = 1 -eng_bosonic = sf.Engine("bosonic") -gkp_bosonic = eng_bosonic.run(prog_bosonic).state -W_bosonic = gkp_bosonic.wigner(0, xvec, xvec) - - -plt.contourf(xvec/np.sqrt(np.pi*sf.hbar*2),xvec/np.sqrt(np.pi*sf.hbar*2),W_fock,levels=50) -plt.show() - -plt.contourf(xvec/np.sqrt(np.pi*sf.hbar*2),xvec/np.sqrt(np.pi*sf.hbar*2),W_bosonic,levels=50) -plt.show() - -plt.contourf(xvec/np.sqrt(np.pi*sf.hbar*2),xvec/np.sqrt(np.pi*sf.hbar*2),np.log(abs(W_fock-W_bosonic)),levels=50) -plt.colorbar() -plt.show() \ No newline at end of file From 26c5b88b8a1c2dff4a37e71ed70f670b16b69457 Mon Sep 17 00:00:00 2001 From: elib20 <53090166+elib20@users.noreply.github.com> Date: Fri, 17 Dec 2021 18:28:11 -0500 Subject: [PATCH 3/9] Ran black + CHANGELOG --- .github/CHANGELOG.md | 14 ++++++++++++++ tests/backend/test_bosonic_backend.py | 12 ++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 0025978cc..13b2bc4f6 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,6 +2,18 @@

New features since last release

+* Rectangular GKP states are now supported. + [(#668)](https://github.com/XanaduAI/strawberryfields/pull/668) + + For example, + + ```python + prog = sf.Program(1) + + with prog.context as q: + sf.ops.GKP(shape="rectangular", alpha=2) | q[0] + ``` +

Breaking Changes

Bug fixes

@@ -12,6 +24,8 @@ This release contains contributions from (in alphabetical order): +J. Eli Bourassa + # Release 0.21.0 (current release)

New features since last release

diff --git a/tests/backend/test_bosonic_backend.py b/tests/backend/test_bosonic_backend.py index 3031ffe64..1071e24a5 100644 --- a/tests/backend/test_bosonic_backend.py +++ b/tests/backend/test_bosonic_backend.py @@ -473,7 +473,7 @@ def test_gkp_complex(self): backend = bosonic.BosonicBackend() with pytest.raises(NotImplementedError): backend.run_prog(prog) - + @pytest.mark.parametrize("eps", EPS_VALS) def test_sensor_gkp(self, eps): r"""Checks that the GKP sensor state is invariant under Fourier.""" @@ -483,7 +483,7 @@ def test_sensor_gkp(self, eps): # Prepare GKP 0 and apply Hadamard prog_sensor = sf.Program(1) with prog_sensor.context as q: - sf.ops.GKP(state=[np.pi/2,0], epsilon=eps, shape="rectangular", alpha=2) | q[0] + sf.ops.GKP(state=[np.pi / 2, 0], epsilon=eps, shape="rectangular", alpha=2) | q[0] backend_sensor = bosonic.BosonicBackend() backend_sensor.run_prog(prog_sensor) @@ -493,7 +493,7 @@ def test_sensor_gkp(self, eps): # Prepare GKP + prog_sensor_F = sf.Program(1) with prog_sensor_F.context as qF: - sf.ops.GKP(state=[np.pi/2,0], epsilon=eps, shape="rectangular", alpha=2) | qF[0] + sf.ops.GKP(state=[np.pi / 2, 0], epsilon=eps, shape="rectangular", alpha=2) | qF[0] sf.ops.Rgate(np.pi / 2) | qF[0] backend_sensor_F = bosonic.BosonicBackend() @@ -529,9 +529,9 @@ def test_gkp_wigner_compare_backends(self, fock_eps): gkp_bosonic = eng_bosonic.run(prog_bosonic).state W_bosonic = gkp_bosonic.wigner(0, xvec, xvec) assert np.allclose(W_fock, W_bosonic) - - @pytest.mark.parametrize("fock_eps", [0.1,0.2,0.3]) - @pytest.mark.parametrize("rect_ratio", [0.8,1,1.5]) + + @pytest.mark.parametrize("fock_eps", [0.1, 0.2, 0.3]) + @pytest.mark.parametrize("rect_ratio", [0.8, 1, 1.5]) def test_rect_gkp_wigner_compare_backends(self, fock_eps, rect_ratio): """Test correct Wigner function are generated by comparing fock and bosonic outputs. Since the fock backend cannot simulate very small epsilon, we restrict to fock_eps>0.1.""" From 7d7ee100d270fdfe4eb96642d62a200f4c87eea4 Mon Sep 17 00:00:00 2001 From: elib20 <53090166+elib20@users.noreply.github.com> Date: Fri, 17 Dec 2021 18:47:52 -0500 Subject: [PATCH 4/9] Update test_ops_gate.py --- tests/frontend/test_ops_gate.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/frontend/test_ops_gate.py b/tests/frontend/test_ops_gate.py index 422e8fd9b..3b2b5b1ee 100644 --- a/tests/frontend/test_ops_gate.py +++ b/tests/frontend/test_ops_gate.py @@ -174,12 +174,13 @@ class TestGKPBasics: @pytest.mark.parametrize("ampl", [0, 0.001]) @pytest.mark.parametrize("eps", [0, 0.001]) @pytest.mark.parametrize("r", ["real", "complex"]) - @pytest.mark.parametrize("s", ["square"]) - def test_gkp_str_representation(self, state, ampl, eps, r, s): + @pytest.mark.parametrize("s", ["square", "rectangular"]) + @pytest.mark.parametrize("alph", [0.8,1.1]) + def test_gkp_str_representation(self, state, ampl, eps, r, s, alph): """Test the string representation of the GKP operation""" assert ( - str(ops.GKP(state=state, ampl_cutoff=ampl, epsilon=eps, representation=r, shape=s)) - == f"GKP({str(state)}, {str(eps)}, {str(ampl)}, {r}, {s})" + str(ops.GKP(state=state, ampl_cutoff=ampl, epsilon=eps, representation=r, shape=s, alpha=alph)) + == f"GKP({str(state)}, {str(eps)}, {str(ampl)}, {r}, {s}, {str(alph)})" ) From 4ffa167700a03b1a148f21c303287ea6edfa0675 Mon Sep 17 00:00:00 2001 From: elib20 <53090166+elib20@users.noreply.github.com> Date: Fri, 17 Dec 2021 18:50:57 -0500 Subject: [PATCH 5/9] Ran black --- tests/frontend/test_ops_gate.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/frontend/test_ops_gate.py b/tests/frontend/test_ops_gate.py index 3b2b5b1ee..99a4872dd 100644 --- a/tests/frontend/test_ops_gate.py +++ b/tests/frontend/test_ops_gate.py @@ -175,11 +175,20 @@ class TestGKPBasics: @pytest.mark.parametrize("eps", [0, 0.001]) @pytest.mark.parametrize("r", ["real", "complex"]) @pytest.mark.parametrize("s", ["square", "rectangular"]) - @pytest.mark.parametrize("alph", [0.8,1.1]) + @pytest.mark.parametrize("alph", [0.8, 1.1]) def test_gkp_str_representation(self, state, ampl, eps, r, s, alph): """Test the string representation of the GKP operation""" assert ( - str(ops.GKP(state=state, ampl_cutoff=ampl, epsilon=eps, representation=r, shape=s, alpha=alph)) + str( + ops.GKP( + state=state, + ampl_cutoff=ampl, + epsilon=eps, + representation=r, + shape=s, + alpha=alph, + ) + ) == f"GKP({str(state)}, {str(eps)}, {str(ampl)}, {r}, {s}, {str(alph)})" ) From 831e2811676a35f2aaa27f9e99c21b8f317ecc56 Mon Sep 17 00:00:00 2001 From: elib20 <53090166+elib20@users.noreply.github.com> Date: Tue, 21 Dec 2021 16:45:07 -0500 Subject: [PATCH 6/9] Minor changes based on Ilan's review Co-Authored-By: ilan-tz <57886357+ilan-tz@users.noreply.github.com> --- strawberryfields/backends/bosonicbackend/backend.py | 7 ++++--- strawberryfields/backends/fockbackend/backend.py | 2 +- strawberryfields/backends/fockbackend/circuit.py | 6 +++--- strawberryfields/backends/fockbackend/ops.py | 8 ++++---- strawberryfields/ops.py | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/strawberryfields/backends/bosonicbackend/backend.py b/strawberryfields/backends/bosonicbackend/backend.py index ebea78ee8..f5a20983c 100644 --- a/strawberryfields/backends/bosonicbackend/backend.py +++ b/strawberryfields/backends/bosonicbackend/backend.py @@ -557,7 +557,7 @@ def prepare_gkp( ampl_cutoff (float): this determines how many terms to keep representation (str): ``'real'`` or ``'complex'`` reprsentation shape (str): shape of the lattice; default 'square' - alpha (float): peak spacing in q is given by sqrt(alpha * pi) + alpha (float): peak spacing in q is given by sqrt(alpha * pi * hbar) Returns: tuple: arrays of the weights, means and covariances for the state @@ -586,6 +586,7 @@ def coeff(peak_loc, alpha): Args: peak_loc (array): location of the ideal peak in phase space + alpha (float): peak spacing in q is given by sqrt(alpha * pi * hbar) Returns: float: weight of the peak @@ -620,7 +621,7 @@ def coeff(peak_loc, alpha): prefactor = np.exp( -np.pi * 0.25 - * ((l * alpha) ** 2 + (m / alpha) ** 2) + * ((l * np.sqrt(alpha)) ** 2 + (m / np.sqrt(alpha)) ** 2) * (1 - np.exp(-2 * epsilon)) / (1 + np.exp(-2 * epsilon)) ) @@ -659,7 +660,7 @@ def coeff(peak_loc, alpha): ) # Calculate the weights for each peak - weights = coeff(means, np.sqrt(alpha)) + weights = coeff(means, alpha) filt = abs(weights) > ampl_cutoff weights = weights[filt] diff --git a/strawberryfields/backends/fockbackend/backend.py b/strawberryfields/backends/fockbackend/backend.py index bb3d3dc0b..71d64da72 100644 --- a/strawberryfields/backends/fockbackend/backend.py +++ b/strawberryfields/backends/fockbackend/backend.py @@ -311,7 +311,7 @@ def prepare_gkp( amplcutoff (float): this determines how many terms to keep representation (str): ``'real'`` or ``'complex'`` reprsentation shape (str): shape of the lattice; default 'square' - alpha (float): peak spacing in q is given by sqrt(alpha * pi) + alpha (float): peak spacing in q is given by sqrt(alpha * pi * hbar) Returns: tuple: arrays of the weights, means and covariances for the state diff --git a/strawberryfields/backends/fockbackend/circuit.py b/strawberryfields/backends/fockbackend/circuit.py index e27c33e9e..b5962b5ae 100644 --- a/strawberryfields/backends/fockbackend/circuit.py +++ b/strawberryfields/backends/fockbackend/circuit.py @@ -799,15 +799,15 @@ def measure_homodyne(self, phi, mode, select=None, **kwargs): # `homodyne_sample` will always be a single value since multiple shots is not supported return np.array([[homodyne_sample]]) - def prepare_gkp(self, theta, phi, epsilon, alpha, ampl_cutoff, mode): + def prepare_gkp(self, theta, phi, epsilon, ampl_cutoff, alpha, mode): """ Prepares a mode in a GKP state. """ if self._pure: self.prepare( - ops.rect_gkp_state(theta, phi, epsilon, alpha, ampl_cutoff, self._trunc), mode + ops.rect_gkp_state(theta, phi, epsilon, ampl_cutoff, alpha, self._trunc), mode ) else: - st = ops.rect_gkp_state(theta, phi, epsilon, alpha, ampl_cutoff, self._trunc) + st = ops.rect_gkp_state(theta, phi, epsilon, ampl_cutoff, alpha, self._trunc) self.prepare(np.outer(st, st.conjugate()), mode) diff --git a/strawberryfields/backends/fockbackend/ops.py b/strawberryfields/backends/fockbackend/ops.py index 0d20853d8..9d004bb52 100644 --- a/strawberryfields/backends/fockbackend/ops.py +++ b/strawberryfields/backends/fockbackend/ops.py @@ -524,7 +524,7 @@ def gkp_displacements(t, k, epsilon, alpha): t (array): the teeth of GKP computational basis k (int): a computational basis state label, can be either 0 or 1 epsilon (float): finite energy parameter of the state - alpha (float): peak spacing in q is given by sqrt(alpha * pi) + alpha (float): peak spacing in q is given by sqrt(alpha * pi * hbar) Returns: array: the displacements @@ -541,7 +541,7 @@ def gkp_coeffs(t, k, epsilon, alpha): t (array): the teeth of GKP computational basis k (int): a computational basis state label, can be either 0 or 1 epsilon (float): finite energy parameter of the state - alpha (float): peak spacing in q is given by sqrt(alpha * pi) + alpha (float): peak spacing in q is given by sqrt(alpha * pi * hbar) Returns: array: the coefficients @@ -558,7 +558,7 @@ def rect_gkp_basis_state(i, epsilon, ampl_cutoff, alpha, cutoff): i (int): a computational basis state label, can be either 0 or 1 epsilon (float): finite energy parameter of the state ampl_cutoff (float): this determines how many terms to keep in the Hilbert space expansion - alpha (float): peak spacing in q is given by sqrt(alpha * pi) + alpha (float): peak spacing in q is given by sqrt(alpha * pi * hbar) cutoff (int): Fock space truncation Returns @@ -585,7 +585,7 @@ def rect_gkp_state(theta, phi, epsilon, ampl_cutoff, alpha, cutoff): phi (float): the longitude with respect to the x-axis in the Bloch sphere epsilon (float): finite energy parameter of the state ampl_cutoff (float): this determines how many terms to keep - alpha (float): peak spacing in q is given by sqrt(alpha * pi) + alpha (float): peak spacing in q is given by sqrt(alpha * pi * hbar) cutoff (int): Fock space truncation Returns: diff --git a/strawberryfields/ops.py b/strawberryfields/ops.py index 95a8d7824..8d8389cab 100644 --- a/strawberryfields/ops.py +++ b/strawberryfields/ops.py @@ -949,7 +949,7 @@ class GKP(Preparation): cutoff (float): this determines how many terms to keep representation (str): ``'real'`` or ``'complex'`` reprsentation shape (str): shape of the lattice; default ``'square'`` - alpha (float): peak spacing in q is given by sqrt(alpha * pi) + alpha (float): peak spacing in q is given by sqrt(alpha * pi * hbar) """ def __init__( From ce277807f3957e2d6b61eb95d4a069ff9c287425 Mon Sep 17 00:00:00 2001 From: elib20 <53090166+elib20@users.noreply.github.com> Date: Tue, 21 Dec 2021 16:46:31 -0500 Subject: [PATCH 7/9] Apply suggestions from code review --- strawberryfields/backends/bosonicbackend/backend.py | 2 +- strawberryfields/backends/fockbackend/backend.py | 2 +- tests/backend/test_bosonic_backend.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/strawberryfields/backends/bosonicbackend/backend.py b/strawberryfields/backends/bosonicbackend/backend.py index f5a20983c..d4392896f 100644 --- a/strawberryfields/backends/bosonicbackend/backend.py +++ b/strawberryfields/backends/bosonicbackend/backend.py @@ -576,7 +576,7 @@ def prepare_gkp( if alpha != 1: raise ValueError( "For square GKPs, alpha must be 1. For alpha not equal to " - + "1, use shape='rectangular'." + + "1, use shape=\"rectangular\"." ) theta, phi = state[0], state[1] diff --git a/strawberryfields/backends/fockbackend/backend.py b/strawberryfields/backends/fockbackend/backend.py index 71d64da72..7df10aa9d 100644 --- a/strawberryfields/backends/fockbackend/backend.py +++ b/strawberryfields/backends/fockbackend/backend.py @@ -330,7 +330,7 @@ def prepare_gkp( if alpha != 1: raise ValueError( "For square GKPs, alpha must be 1. For alpha not equal to " - + "1, use shape='rectangular'." + + "1, use shape=\"rectangular\"." ) theta, phi = state[0], state[1] diff --git a/tests/backend/test_bosonic_backend.py b/tests/backend/test_bosonic_backend.py index 1071e24a5..b46abc716 100644 --- a/tests/backend/test_bosonic_backend.py +++ b/tests/backend/test_bosonic_backend.py @@ -476,7 +476,7 @@ def test_gkp_complex(self): @pytest.mark.parametrize("eps", EPS_VALS) def test_sensor_gkp(self, eps): - r"""Checks that the GKP sensor state is invariant under Fourier.""" + r"""Checks that the GKP sensor state is invariant under Fourier gate.""" x = np.linspace(-3 * np.sqrt(np.pi), 3 * np.sqrt(np.pi), 40) p = np.copy(x) @@ -533,8 +533,8 @@ def test_gkp_wigner_compare_backends(self, fock_eps): @pytest.mark.parametrize("fock_eps", [0.1, 0.2, 0.3]) @pytest.mark.parametrize("rect_ratio", [0.8, 1, 1.5]) def test_rect_gkp_wigner_compare_backends(self, fock_eps, rect_ratio): - """Test correct Wigner function are generated by comparing fock and bosonic outputs. - Since the fock backend cannot simulate very small epsilon, we restrict to fock_eps>0.1.""" + """Test that correct Wigner function is generated by comparing fock and bosonic outputs. + Since the fock backend cannot simulate very small epsilon, we restrict to fock_eps > 0.1.""" theta = np.pi / 8 phi = np.pi / 6 ket = [theta, phi] From 60a88803e95a7b0243c0011d3793c0ebf124efd2 Mon Sep 17 00:00:00 2001 From: elib20 <53090166+elib20@users.noreply.github.com> Date: Tue, 21 Dec 2021 16:51:13 -0500 Subject: [PATCH 8/9] Ran black --- strawberryfields/backends/bosonicbackend/backend.py | 2 +- strawberryfields/backends/fockbackend/backend.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/strawberryfields/backends/bosonicbackend/backend.py b/strawberryfields/backends/bosonicbackend/backend.py index d4392896f..77b7c412d 100644 --- a/strawberryfields/backends/bosonicbackend/backend.py +++ b/strawberryfields/backends/bosonicbackend/backend.py @@ -576,7 +576,7 @@ def prepare_gkp( if alpha != 1: raise ValueError( "For square GKPs, alpha must be 1. For alpha not equal to " - + "1, use shape=\"rectangular\"." + + '1, use shape="rectangular".' ) theta, phi = state[0], state[1] diff --git a/strawberryfields/backends/fockbackend/backend.py b/strawberryfields/backends/fockbackend/backend.py index 7df10aa9d..4a99f6861 100644 --- a/strawberryfields/backends/fockbackend/backend.py +++ b/strawberryfields/backends/fockbackend/backend.py @@ -330,7 +330,7 @@ def prepare_gkp( if alpha != 1: raise ValueError( "For square GKPs, alpha must be 1. For alpha not equal to " - + "1, use shape=\"rectangular\"." + + '1, use shape="rectangular".' ) theta, phi = state[0], state[1] From 0d6c810e40000784c5d82d057165cdfdac6a6936 Mon Sep 17 00:00:00 2001 From: elib20 <53090166+elib20@users.noreply.github.com> Date: Wed, 22 Dec 2021 08:53:09 -0500 Subject: [PATCH 9/9] Apply suggestions from code review --- strawberryfields/backends/bosonicbackend/backend.py | 2 +- strawberryfields/backends/fockbackend/backend.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/strawberryfields/backends/bosonicbackend/backend.py b/strawberryfields/backends/bosonicbackend/backend.py index 77b7c412d..f5a20983c 100644 --- a/strawberryfields/backends/bosonicbackend/backend.py +++ b/strawberryfields/backends/bosonicbackend/backend.py @@ -576,7 +576,7 @@ def prepare_gkp( if alpha != 1: raise ValueError( "For square GKPs, alpha must be 1. For alpha not equal to " - + '1, use shape="rectangular".' + + "1, use shape='rectangular'." ) theta, phi = state[0], state[1] diff --git a/strawberryfields/backends/fockbackend/backend.py b/strawberryfields/backends/fockbackend/backend.py index 4a99f6861..71d64da72 100644 --- a/strawberryfields/backends/fockbackend/backend.py +++ b/strawberryfields/backends/fockbackend/backend.py @@ -330,7 +330,7 @@ def prepare_gkp( if alpha != 1: raise ValueError( "For square GKPs, alpha must be 1. For alpha not equal to " - + '1, use shape="rectangular".' + + "1, use shape='rectangular'." ) theta, phi = state[0], state[1]