Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed differentiation for all interfaces, added generator. Also, adde… #16

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion ionizer/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@
"""
Native gates for IonQ hardware as PennyLane operations.
"""
import functools
import inspect
import numpy as np

import pennylane as qml
from pennylane.operation import Operation


stack_last = functools.partial(qml.math.stack, axis=-1)


class GPI(Operation):
r"""
The single-qubit GPI rotation
Expand All @@ -43,6 +48,30 @@ class GPI(Operation):
num_wires = 1
num_params = 1
ndim_params = (0,)
grad_method = "A"
parameter_frequencies = [(1,)]

grad_recipe = ([[1, 1, np.pi / 4], [-1, 1, -np.pi / 4]],)

def generator(self):
r"""The Generator for GPI gate.

.. math::
\hat(G) = \frac{\pi}{2}\begin{bmatrix}
1 & e^{i\phi} \\
e^{-i\phi} & 1
\end{bmatrix} = -\frac{\pi}{2}(I-\cos(\phi)X-\sin(\phi)Y).
"""
phi = self.data[0]
c = (np.pi / 2) / phi
coeffs = np.array([c, -c * qml.math.cos(phi), -c * qml.math.sin(phi)])
observables = [
qml.Identity(wires=self.wires),
qml.PauliX(wires=self.wires),
qml.PauliY(wires=self.wires),
]

return qml.Hamiltonian(coeffs, observables)

# Note: disable pylint complaint about redefined built-in, since the id
# value itself is coming from the class definition of Operators in PennyLane proper.
Expand All @@ -65,7 +94,14 @@ def compute_matrix(phi): # pylint: disable=arguments-differ
array([[0. +0.j , 0.95533649-0.29552021j],
[0.95533649+0.29552021j, 0. +0.j ]])
"""
return qml.math.stack([[0, qml.math.exp(-1j * phi)], [qml.math.exp(1j * phi), 0]])
if qml.math.get_interface(phi) == "tensorflow":
phi = qml.math.cast_like(phi, 1j)

a = 0 + 0j
b = qml.math.exp((0 - 1j) * phi)
return qml.math.stack(
qml.math.stack([stack_last([a, b]), stack_last([qml.math.conj(b), a])])
)

def adjoint(self):
# The GPI gate is its own adjoint.
Expand Down
63 changes: 63 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
try:
import tensorflow as tf
except (ImportError, ModuleNotFoundError) as e:
tf_available = False
else:
tf_available = True

try:
import torch
from torch.autograd import Variable

torch_available = True
except ImportError as e:
torch_available = False

try:
import jax
import jax.numpy as jnp

jax_available = True
except ImportError as e:
jax_available = False


# pylint: disable=unused-argument
def pytest_generate_tests(metafunc):
if jax_available:
jax.config.update("jax_enable_x64", True)


def pytest_runtest_setup(item):
"""Automatically skip tests if interfaces are not installed"""
# Autograd is assumed to be installed
interfaces = {"tf", "torch", "jax"}
available_interfaces = {
"tf": tf_available,
"torch": torch_available,
"jax": jax_available,
}

allowed_interfaces = [
allowed_interface
for allowed_interface in interfaces
if available_interfaces[allowed_interface] is True
]

# load the marker specifying what the interface is
all_interfaces = {"tf", "torch", "jax", "all_interfaces"}
marks = {mark.name for mark in item.iter_markers() if mark.name in all_interfaces}

for b in marks:
if b == "all_interfaces":
required_interfaces = {"tf", "torch", "jax"}
for interface in required_interfaces:
if interface not in allowed_interfaces:
pytest.skip(
f"\nTest {item.nodeid} only runs with {allowed_interfaces} interfaces(s) but {b} interface provided",
)
else:
if b not in allowed_interfaces:
pytest.skip(
f"\nTest {item.nodeid} only runs with {allowed_interfaces} interfaces(s) but {b} interface provided",
)
180 changes: 180 additions & 0 deletions tests/test_ops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"""
Unit tests for GPI2 operation and its utility functions.
"""

import pytest
import torch
import jax

import pennylane as qml
from pennylane import numpy as np
import pennylane.math as math
import tensorflow as tf
from ionizer.ops import GPI

jax.config.update("jax_enable_x64", True)

interfaces_and_array_methods = [
["autograd", qml.numpy.array],
["torch", torch.tensor],
["tf", tf.Variable],
["jax", jax.numpy.array],
]
diff_methods = ["backprop", "adjoint", "parameter-shift", "finite-diff"]
two_pi = 2 * np.pi


def get_GPI_matrix(phi):
"""TODO"""
return np.array([[0, math.exp(-1j * phi)], [math.exp(1j * phi), 0]])


class State:
@staticmethod
def set_state():
"""TODO"""
qml.RX(0.2, wires=0)
qml.RY(1.1, wires=0)
qml.RX(0.3, wires=0)

@classmethod
@qml.qnode(qml.device("default.qubit", wires=1))
def get_state(cls):
"""TODO"""
cls.set_state()
return qml.state()

def __init__(self):
"""TODO"""
self.state = self.get_state()
self.a_conj_b = self.state[0] * np.conj(self.state[1])
self.b_conj_a = self.state[1] * np.conj(self.state[0])


@pytest.mark.parametrize("phi", [0, 0.37 * two_pi, 1.23 * two_pi, two_pi])
class TestGPI:
@staticmethod
def circuit(phi):
"""TODO"""
State.set_state()
GPI(phi, wires=0)
return qml.expval(qml.PauliY(wires=0))

@pytest.fixture(autouse=True)
def state(self):
"""TODO"""
self._state = State()

def test_GPI_compute_matrix(self, phi):
"""TODO"""
gpi_matrix = GPI.compute_matrix(phi)
check_matrix = get_GPI_matrix(phi_value)

assert math.allclose(gpi_matrix, check_matrix)

@pytest.mark.parametrize("interface, array_method", interfaces_and_array_methods)
def test_GPI_circuit(self, interface, array_method, phi):
"""TODO"""
phi_GPI = array_method(phi)
dev = qml.device("default.qubit", wires=1)

qnode_GPI = qml.QNode(self.circuit, dev, interface=interface)
val_GPI = qnode_GPI(phi_GPI)

expected_inner_product_1 = 1j * self._state.b_conj_a * np.exp(-2j * phi)
expected_inner_product_2 = -1j * self._state.a_conj_b * np.exp(2j * phi)
expected_val = expected_inner_product_1 + expected_inner_product_2

assert np.isclose(
val_GPI, expected_val, atol=1e-07
), f"Given val: {val_GPI}; Expected val: {expected_val}"

def get_circuit_grad(self, phi):
"""TODO"""
expected_inner_product_1 = 1j * self._state.b_conj_a * np.exp(-2j * phi) * (-2j)
expected_inner_product_2 = -1j * self._state.a_conj_b * np.exp(2j * phi) * 2j
return np.real(expected_inner_product_1 + expected_inner_product_2)

@pytest.mark.parametrize("diff_method", diff_methods)
def test_GPI_grad_qnode(self, diff_method, phi):
"""TODO"""

phi_GPI = np.array(phi)
dev = qml.device("default.qubit", wires=1)

qnode_GPI = qml.QNode(self.circuit, dev, diff_method=diff_method)
grad_GPI = qml.grad(qnode_GPI, argnum=0)(phi_GPI)

expected_grad = self.get_circuit_grad(phi)
assert np.isclose(
grad_GPI, expected_grad
), f"Given grad: {grad_GPI}; Expected grad: {expected_grad}"

@pytest.mark.autograd
def test_GPI_grad_autograd(self, phi):
"""TODO"""
phi_GPI = qml.numpy.array(phi, requires_grad=True)
dev = qml.device("default.qubit", wires=1)

qnode_GPI = qml.QNode(self.circuit, dev, interface=interface, diff_method=diff_method)
grad_GPI = qml.grad(qnode_GPI, argnum=0)(phi_GPI)

expected_grad = self.get_circuit_grad(phi)
assert np.isclose(
grad_GPI, expected_grad
), f"Given grad: {grad_GPI}; Expected grad: {expected_grad}"

@pytest.mark.jax
def test_GPI_grad_jax(self, phi):
"""TODO"""
import jax

phi_GPI = jax.numpy.array(phi, dtype=jax.numpy.complex128)
dev = qml.device("default.qubit", wires=1)

qnode_GPI = qml.QNode(self.circuit, dev, interface=interface, diff_method=diff_method)
grad_GPI = jax.grad(qnode_GPI)(phi_GPI)

expected_grad = self.get_circuit_grad(phi)
assert np.isclose(
grad_GPI, expected_grad
), f"Given grad: {grad_GPI}; Expected grad: {expected_grad}"

@pytest.mark.torch
def test_GPI_grad_torch(self, phi):
"""TODO"""
import torch

phi_GPI = torch.tensor(phi, requires_grad=True)
dev = qml.device("default.qubit", wires=1)

qnode_GPI = qml.QNode(self.circuit, dev, interface=interface, diff_method=diff_method)

phi_GPI.requires_grad = True
result = qnode_GPI(phi_GPI)
result.backward()
grad_GPI = phi_GPI.grad

expected_grad = self.get_circuit_grad(phi)
assert np.isclose(
grad_GPI, expected_grad
), f"Given grad: {grad_GPI}; Expected grad: {expected_grad}"

@pytest.mark.tf
def test_GPI_grad_tensorflow(self, phi):
"""TODO"""
import tensorflow as tf

phi_GPI = tf.Variable(phi)
dev = qml.device("default.qubit", wires=1)

qnode_GPI = qml.QNode(self.circuit, dev, interface=interface, diff_method=diff_method)

with tf.GradientTape() as tape:
loss = qnode_GPI(phi_GPI)
grad_GPI = tape.gradient(loss, phi_GPI)

expected_grad = self.get_circuit_grad(phi)
assert np.isclose(
grad_GPI, expected_grad
), f"Given grad: {grad_GPI}; Expected grad: {expected_grad}"
Loading