Repository: PennyLaneAI/pennylane
Pull request #1135 (2021-03-11 22:22:48)

Added adjoints for some CV gates
**Context:**
Added adjoint definitions for some CV ops.

**Benefits:**
Users can now use `qml.adjoint` when using CVOperations.

**Possible Drawbacks:**
None

**Related GitHub Issues:**
None.


github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [.github/CHANGELOG.md](/PennyLaneAI/pennylane/blob/master/.github/CHANGELOG.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135?src=pr&el=h1) Report
> Merging [#1135](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135?src=pr&el=desc) (d5a8d56) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/47b3243a0f795c03730f79f91b9251aa310c3fc1?el=desc) (47b3243) will **decrease** coverage by `5.63%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/graphs/tree.svg?width=650&height=150&src=pr&token=9S3YzlUgGk)](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1135      +/-   ##
==========================================
- Coverage   97.77%   92.13%   -5.64%     
==========================================
  Files         156      156              
  Lines       11870    11890      +20     
==========================================
- Hits        11606    10955     -651     
- Misses        264      935     +671     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pennylane/ops/cv.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/diff?src=pr&el=tree#diff-cGVubnlsYW5lL29wcy9jdi5weQ==) | `100.00% <100.00%> (ø)` | |
| [pennylane/math/jax\_box.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/diff?src=pr&el=tree#diff-cGVubnlsYW5lL21hdGgvamF4X2JveC5weQ==) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pennylane/math/torch\_box.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/diff?src=pr&el=tree#diff-cGVubnlsYW5lL21hdGgvdG9yY2hfYm94LnB5) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pennylane/tape/interfaces/torch.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3RhcGUvaW50ZXJmYWNlcy90b3JjaC5weQ==) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pennylane/devices/jax\_ops.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/diff?src=pr&el=tree#diff-cGVubnlsYW5lL2RldmljZXMvamF4X29wcy5weQ==) | `0.00% <0.00%> (-95.13%)` | :arrow_down: |
| [pennylane/devices/default\_qubit\_jax.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/diff?src=pr&el=tree#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9xdWJpdF9qYXgucHk=) | `0.00% <0.00%> (-94.03%)` | :arrow_down: |
| [pennylane/interfaces/torch.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/diff?src=pr&el=tree#diff-cGVubnlsYW5lL2ludGVyZmFjZXMvdG9yY2gucHk=) | `0.00% <0.00%> (-93.07%)` | :arrow_down: |
| [pennylane/tape/interfaces/jax.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3RhcGUvaW50ZXJmYWNlcy9qYXgucHk=) | `0.00% <0.00%> (-89.75%)` | :arrow_down: |
| [pennylane/qnn/torch.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3Fubi90b3JjaC5weQ==) | `26.59% <0.00%> (-71.28%)` | :arrow_down: |
| [pennylane/qnn/cost.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/diff?src=pr&el=tree#diff-cGVubnlsYW5lL3Fubi9jb3N0LnB5) | `42.85% <0.00%> (-57.15%)` | :arrow_down: |
| ... and [16 more](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135?src=pr&el=footer). Last update [47b3243...5423fb1](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1135?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

co9olguy: There are still a few gates missing (they're all easy):

- ControlledAddition: s -> -s
- ControlledPhase: s -> -s
- CrossKerr: kappa -> -kappa
- CubicPhase: gamma -> -gamma
- Kerr: kappa -> -kappa
Thenerdstation: > There are still a few gates missing (they're all easy):

Added
Thenerdstation: Should we rename `adjoint` to `invert`? Since the CV ops aren't unitary, it doesn't make sense anymore calling them ajoints.
ixfoduap: > Should we rename `adjoint` to `invert`? Since the CV ops aren't unitary, it doesn't make sense anymore calling them ajoints.

What do you mean, of course they are unitary! They are unitary on the entire Hilbert space
co9olguy: Approved!
co9olguy: Yep the CV ops are all unitary, it's just a different Hilbert space they operate on than qubits, but they're still "gates"

Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index 2968d25a6..c7df79660 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 * A new adjoint transform has been added. 
   [(#1111)](https://github.com/PennyLaneAI/pennylane/pull/1111)
+  [(#1135)](https://github.com/PennyLaneAI/pennylane/pull/1135)
 
   This new method allows users to apply the adjoint of an arbitrary sequence of operations.
 
diff --git a/pennylane/ops/cv.py b/pennylane/ops/cv.py
index ea45ff514..650689e03 100644
--- a/pennylane/ops/cv.py
+++ b/pennylane/ops/cv.py
@@ -102,6 +102,9 @@ class Rotation(CVOperation):
     def _heisenberg_rep(p):
         return _rotation(p[0])
 
+    def adjoint(self, do_queue=False):
+        return Rotation(-self.parameters[0], wires=self.wires, do_queue=do_queue)
+
 
 class Squeezing(CVOperation):
     r"""pennylane.Squeezing(r, phi, wires)
@@ -147,6 +150,11 @@ class Squeezing(CVOperation):
         R = _rotation(p[1] / 2)
         return R @ np.diag([1, math.exp(-p[0]), math.exp(p[0])]) @ R.T
 
+    def adjoint(self, do_queue=False):
+        r, phi = self.parameters
+        new_phi = (phi + np.pi) % (2 * np.pi)
+        return Squeezing(r, new_phi, wires=self.wires, do_queue=do_queue)
+
 
 class Displacement(CVOperation):
     r"""pennylane.Displacement(a, phi, wires)
@@ -193,6 +201,11 @@ class Displacement(CVOperation):
         scale = 2  # sqrt(2 * hbar)
         return np.array([[1, 0, 0], [scale * c * p[0], 1, 0], [scale * s * p[0], 0, 1]])
 
+    def adjoint(self, do_queue=False):
+        a, phi = self.parameters
+        new_phi = (phi + np.pi) % (2 * np.pi)
+        return Displacement(a, new_phi, wires=self.wires, do_queue=do_queue)
+
 
 class Beamsplitter(CVOperation):
     r"""pennylane.Beamsplitter(theta, phi, wires)
@@ -243,6 +256,10 @@ class Beamsplitter(CVOperation):
         U[3:5, 1:3] = s * R
         return U
 
+    def adjoint(self, do_queue=False):
+        theta, phi = self.parameters
+        return Beamsplitter(-theta, phi, wires=self.wires, do_queue=do_queue)
+
 
 class TwoModeSqueezing(CVOperation):
     r"""pennylane.TwoModeSqueezing(r, phi, wires)
@@ -300,6 +317,11 @@ class TwoModeSqueezing(CVOperation):
         U[3:5, 1:3] = S @ R.T
         return U
 
+    def adjoint(self, do_queue=False):
+        r, phi = self.parameters
+        new_phi = (phi + np.pi) % (2 * np.pi)
+        return TwoModeSqueezing(r, new_phi, wires=self.wires, do_queue=do_queue)
+
 
 class QuadraticPhase(CVOperation):
     r"""pennylane.QuadraticPhase(s, wires)
@@ -394,6 +416,9 @@ class ControlledAddition(CVOperation):
         U[3, 1] = p[0]
         return U
 
+    def adjoint(self, do_queue=False):
+        return ControlledAddition(-self.parameters[0], wires=self.wires, do_queue=do_queue)
+
 
 class ControlledPhase(CVOperation):
     r"""pennylane.ControlledPhase(s, wires)
@@ -443,6 +468,9 @@ class ControlledPhase(CVOperation):
         U[4, 1] = p[0]
         return U
 
+    def adjoint(self, do_queue=False):
+        return ControlledPhase(-self.parameters[0], wires=self.wires, do_queue=do_queue)
+
 
 class Kerr(CVOperation):
     r"""pennylane.Kerr(kappa, wires)
@@ -466,6 +494,9 @@ class Kerr(CVOperation):
     par_domain = "R"
     grad_method = "F"
 
+    def adjoint(self, do_queue=False):
+        return Kerr(-self.parameters[0], wires=self.wires, do_queue=do_queue)
+
 
 class CrossKerr(CVOperation):
     r"""pennylane.CrossKerr(kappa, wires)
@@ -489,6 +520,9 @@ class CrossKerr(CVOperation):
     par_domain = "R"
     grad_method = "F"
 
+    def adjoint(self, do_queue=False):
+        return CrossKerr(-self.parameters[0], wires=self.wires, do_queue=do_queue)
+
 
 class CubicPhase(CVOperation):
     r"""pennylane.CubicPhase(gamma, wires)
@@ -512,6 +546,9 @@ class CubicPhase(CVOperation):
     par_domain = "R"
     grad_method = "F"
 
+    def adjoint(self, do_queue=False):
+        return CubicPhase(-self.parameters[0], wires=self.wires, do_queue=do_queue)
+
 
 class Interferometer(CVOperation):
     r"""pennylane.Interferometer(U, wires)
@@ -566,6 +603,10 @@ class Interferometer(CVOperation):
         M[1 : 2 * N + 1, 1 : 2 * N + 1] = S
         return M
 
+    def adjoint(self, do_queue=False):
+        U = self.parameters[0]
+        return Interferometer(U.conj().T, wires=self.wires, do_queue=do_queue)
+
 
 # =============================================================================
 # State preparation


Test patch:
diff --git a/tests/ops/test_cv_ops.py b/tests/ops/test_cv_ops.py
index 0be596962..e737fb87e 100644
--- a/tests/ops/test_cv_ops.py
+++ b/tests/ops/test_cv_ops.py
@@ -21,6 +21,7 @@ import pytest
 
 import pennylane
 from pennylane import numpy as np
+import numpy.testing as np_testing
 from pennylane.ops import cv
 from pennylane.wires import Wires
 
@@ -40,6 +41,41 @@ class TestCV:
         )
         assert np.allclose(matrix, true_matrix)
 
+    @pytest.mark.parametrize("op,size", [
+            (cv.Squeezing(0.123, -0.456, wires=1), 3),
+            (cv.Squeezing(0.668, 10.0, wires=0), 3), # phi > 2pi
+            (cv.Squeezing(1.992, -9.782, wires=0), 3), # phi < -2pi
+            (cv.Rotation(2.005, wires=1), 3),
+            (cv.Rotation(-1.365, wires=1), 3),
+            (cv.Displacement(2.841, 0.456, wires=0), 3),
+            (cv.Displacement(3.142, -7.221, wires=0), 3), # phi < -2pi
+            (cv.Displacement(2.004, 8.673, wires=0), 3), # phi > 2pi
+            (cv.Beamsplitter(0.456, -0.789, wires=[0, 2]), 5),
+            (cv.TwoModeSqueezing(2.532, 1.778, wires=[1, 2]), 5),
+            (cv.Interferometer(
+                np.array([[1, 1], [1, -1]]) * -1.j / np.sqrt(2.0),
+                wires=1), 5),
+            (cv.ControlledAddition(2.551, wires=[0, 2]), 5),
+            (cv.ControlledPhase(2.189, wires=[3, 1]), 5),
+        ])
+    def test_adjoint_cv_ops(self, op, size, tol):
+        op_d = op.adjoint()
+        op_heis = op._heisenberg_rep(op.parameters)
+        op_d_heis = op_d._heisenberg_rep(op_d.parameters)
+        res1 = np.dot(op_heis, op_d_heis)
+        res2 = np.dot(op_d_heis, op_heis)
+        np_testing.assert_allclose(res1, np.eye(size), atol=tol)
+        np_testing.assert_allclose(res2, np.eye(size), atol=tol)
+        assert op.wires == op_d.wires
+
+    @pytest.mark.parametrize("op", [
+            cv.CrossKerr(-1.724, wires=[2, 0]),
+            cv.CubicPhase(0.997, wires=2),
+            cv.Kerr(2.568, wires=2),
+        ])
+    def test_adjoint_no_heisenberg_rep_defined(self, op, tol):
+        op_d = op.adjoint()
+        assert op.parameters[0] == -op_d.parameters[0]
 
     @pytest.mark.parametrize("phi", phis)
     @pytest.mark.parametrize("mag", mags)