Skip to content

Commit

Permalink
enable partially entangling ms gates
Browse files Browse the repository at this point in the history
  • Loading branch information
splch committed Apr 16, 2024
1 parent e200ad2 commit 003f9f4
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 27 deletions.
22 changes: 17 additions & 5 deletions pennylane_ionq/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class IonQDevice(QubitDevice):
distribution has peaks. See `IonQ Debiasing and Sharpening
<https://ionq.com/resources/debiasing-and-sharpening>`_ for details.
"""

# pylint: disable=too-many-instance-attributes
name = "IonQ PennyLane plugin"
short_name = "ionq"
Expand Down Expand Up @@ -114,7 +115,9 @@ def __init__(
sharpen=False,
):
if shots is None:
raise ValueError("The ionq device does not support analytic expectation values.")
raise ValueError(
"The ionq device does not support analytic expectation values."
)

super().__init__(wires=wires, shots=shots)
self.target = target
Expand Down Expand Up @@ -164,7 +167,9 @@ def apply(self, operations, **kwargs):
rotations = kwargs.pop("rotations", [])

if len(operations) == 0 and len(rotations) == 0:
warnings.warn("Circuit is empty. Empty circuits return failures. Submitting anyway.")
warnings.warn(
"Circuit is empty. Empty circuits return failures. Submitting anyway."
)

for i, operation in enumerate(operations):
if i > 0 and operation.name in {
Expand Down Expand Up @@ -201,7 +206,9 @@ def _apply_operation(self, operation):

if self.gateset == "native":
if len(par) > 1:
gate["phases"] = [float(v) for v in par]
gate["phases"] = [float(v) for v in par[:2]]
if len(par) > 2:
gate["rotation"] = float(par[2])
else:
gate["phase"] = float(par[0])
elif par:
Expand Down Expand Up @@ -246,7 +253,8 @@ def prob(self):
# Here, we rearrange the states to match the big-endian ordering
# expected by PennyLane.
basis_states = (
int(bin(int(k))[2:].rjust(self.num_wires, "0")[::-1], 2) for k in self.histogram
int(bin(int(k))[2:].rjust(self.num_wires, "0")[::-1], 2)
for k in self.histogram
)
idx = np.fromiter(basis_states, dtype=int)

Expand All @@ -266,7 +274,9 @@ def probability(self, wires=None, shot_range=None, bin_size=None):
if shot_range is None and bin_size is None:
return self.marginal_prob(self.prob, wires)

return self.estimate_probability(wires=wires, shot_range=shot_range, bin_size=bin_size)
return self.estimate_probability(
wires=wires, shot_range=shot_range, bin_size=bin_size
)


class SimulatorDevice(IonQDevice):
Expand All @@ -285,6 +295,7 @@ class SimulatorDevice(IonQDevice):
api_key (str): The IonQ API key. If not provided, the environment
variable ``IONQ_API_KEY`` is used.
"""

name = "IonQ Simulator PennyLane plugin"
short_name = "ionq.simulator"

Expand Down Expand Up @@ -329,6 +340,7 @@ class QPUDevice(IonQDevice):
your expected output distribution has peaks. See `IonQ Debiasing and Sharpening
<https://ionq.com/resources/debiasing-and-sharpening>`_ for details.
"""

name = "IonQ QPU PennyLane plugin"
short_name = "ionq.qpu"

Expand Down
32 changes: 22 additions & 10 deletions pennylane_ionq/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class GPI(Operation): # pylint: disable=too-few-public-methods
phi (float): phase :math:`\phi`
wires (Sequence[int]): the subsystems the operation acts on
"""

num_params = 1
num_wires = 1
grad_method = None
Expand All @@ -53,33 +54,41 @@ class GPI2(Operation): # pylint: disable=too-few-public-methods
phi (float): phase :math:`\phi`
wires (Sequence[int]): the subsystems the operation acts on
"""

num_params = 1
num_wires = 1
grad_method = None


class MS(Operation): # pylint: disable=too-few-public-methods
r"""MS(phi0, phi1, wires)
2-qubit entanlging MS gate.
r"""MS(phi0, phi1, theta=0.25, wires)
2-qubit entangling MS gate.
.. math::
MS(\phi_{0}, \phi_{1}) =
MS(\phi_{0}, \phi_{1}, \theta) =
\frac{1}{\sqrt{2}}\begin{pmatrix}
1 & 0 & 0 & -i e^{-2 \pi i(\phi_{0}+\phi_{1})} \\
0 & 1 & -i e^{-2 \pi i (\phi_{0}-\phi_{1})} & 0 \\
0 & -i e^{2 \pi i(\phi_{0}-\phi_{1})} & 1 & 0 \\
-i e^{2 \pi i(\phi_{0}+\phi_{1})} & 0 & 0 & 1
\cos(\theta / 2) & 0 & 0 & -i e^{-2 \pi i(\phi_{0}+\phi_{1})} \\
0 & \cos(\theta / 2) & -i e^{-2 \pi i (\phi_{0}-\phi_{1})} & 0 \\
0 & -i e^{2 \pi i(\phi_{0}-\phi_{1})} & \cos(\theta / 2) & 0 \\
-i e^{2 \pi i(\phi_{0}+\phi_{1})} & 0 & 0 & \cos(\theta / 2)
\end{pmatrix}
Args:
phi0 (float): phase of the first qubit :math:`\phi`
phi1 (float): phase of the second qubit :math:`\phi`
phi0 (float): phase of the first qubit :math:`\phi_0`
phi1 (float): phase of the second qubit :math:`\phi_1`
theta (float): entanglement ratio of the qubits :math:`\theta` [0, 0.25], defaults to 0.25
wires (Sequence[int]): the subsystems the operation acts on
"""
num_params = 2

num_params = 3
num_wires = 2
par_domain = "R"
grad_method = None

def __init__(self, phi0, phi1, theta=0.25, wires=None):
super().__init__(phi0, phi1, theta, wires=wires)


# Custom operations for the QIS Gateset below

Expand All @@ -101,6 +110,7 @@ class XX(Operation):
phi (float): rotation angle :math:`\phi`
wires (Sequence[int]): the subsystems the operation acts on
"""

num_params = 1
num_wires = 2
grad_method = "A"
Expand All @@ -123,6 +133,7 @@ class YY(Operation):
phi (float): rotation angle :math:`\phi`
wires (Sequence[int]): the subsystems the operation acts on
"""

num_params = 1
num_wires = 2
grad_method = "A"
Expand All @@ -145,6 +156,7 @@ class ZZ(Operation):
phi (float): rotation angle :math:`\phi`
wires (Sequence[int]): the subsystems the operation acts on
"""

num_params = 1
num_wires = 2
grad_method = "A"
54 changes: 42 additions & 12 deletions tests/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,15 @@ def test_generate_samples_qpu_device(self, wires, histogram):

unique_outcomes1 = np.unique(sample1, axis=0)
unique_outcomes2 = np.unique(sample2, axis=0)
assert np.all(unique_outcomes1 == unique_outcomes2) # possible outcomes are the same
assert np.all(
unique_outcomes1 == unique_outcomes2
) # possible outcomes are the same

sorted_outcomes1 = np.sort(sample1, axis=0)
sorted_outcomes2 = np.sort(sample2, axis=0)
assert np.all(sorted_outcomes1 == sorted_outcomes2) # set of outcomes is the same
assert np.all(
sorted_outcomes1 == sorted_outcomes2
) # set of outcomes is the same


class TestDeviceIntegration:
Expand Down Expand Up @@ -96,7 +100,9 @@ def test_failedcircuit(self, monkeypatch):
monkeypatch.setattr(
requests, "post", lambda url, timeout, data, headers: (url, data, headers)
)
monkeypatch.setattr(ResourceManager, "handle_response", lambda self, response: None)
monkeypatch.setattr(
ResourceManager, "handle_response", lambda self, response: None
)
monkeypatch.setattr(Job, "is_complete", False)
monkeypatch.setattr(Job, "is_failed", True)

Expand All @@ -111,13 +117,17 @@ def test_shots(self, shots, monkeypatch, mocker, tol):
monkeypatch.setattr(
requests, "post", lambda url, timeout, data, headers: (url, data, headers)
)
monkeypatch.setattr(ResourceManager, "handle_response", lambda self, response: None)
monkeypatch.setattr(
ResourceManager, "handle_response", lambda self, response: None
)
monkeypatch.setattr(Job, "is_complete", True)

def fake_response(self, resource_id=None, params=None):
"""Return fake response data"""
fake_json = {"0": 1}
setattr(self.resource, "data", type("data", tuple(), {"value": fake_json})())
setattr(
self.resource, "data", type("data", tuple(), {"value": fake_json})()
)

monkeypatch.setattr(ResourceManager, "get", fake_response)

Expand All @@ -133,20 +143,26 @@ def circuit():
circuit()
assert json.loads(spy.call_args[1]["data"])["shots"] == shots

@pytest.mark.parametrize("error_mitigation", [None, {"debias": True}, {"debias": False}])
@pytest.mark.parametrize(
"error_mitigation", [None, {"debias": True}, {"debias": False}]
)
def test_error_mitigation(self, error_mitigation, monkeypatch, mocker):
"""Test that shots are correctly specified when submitting a job to the API."""

monkeypatch.setattr(
requests, "post", lambda url, timeout, data, headers: (url, data, headers)
)
monkeypatch.setattr(ResourceManager, "handle_response", lambda self, response: None)
monkeypatch.setattr(
ResourceManager, "handle_response", lambda self, response: None
)
monkeypatch.setattr(Job, "is_complete", True)

def fake_response(self, resource_id=None, params=None):
"""Return fake response data"""
fake_json = {"0": 1}
setattr(self.resource, "data", type("data", tuple(), {"value": fake_json})())
setattr(
self.resource, "data", type("data", tuple(), {"value": fake_json})()
)

monkeypatch.setattr(ResourceManager, "get", fake_response)

Expand All @@ -167,7 +183,10 @@ def circuit():
spy = mocker.spy(requests, "post")
circuit()
if error_mitigation is not None:
assert json.loads(spy.call_args[1]["data"])["error_mitigation"] == error_mitigation
assert (
json.loads(spy.call_args[1]["data"])["error_mitigation"]
== error_mitigation
)
else:
with pytest.raises(KeyError, match="error_mitigation"):
json.loads(spy.call_args[1]["data"])["error_mitigation"]
Expand Down Expand Up @@ -218,12 +237,15 @@ def test_probability(self):
assert np.array_equal(dev.probability(shot_range=(0, 2)), [0, 0, 0, 1])

uniform_prob = [0.25] * 4
with patch("pennylane_ionq.device.SimulatorDevice.prob", new_callable=PropertyMock) as mock_prob:
with patch(
"pennylane_ionq.device.SimulatorDevice.prob", new_callable=PropertyMock
) as mock_prob:
mock_prob.return_value = uniform_prob
assert np.array_equal(dev.probability(), uniform_prob)


@pytest.mark.parametrize("backend", ["harmony", "aria-1", "aria-2", "forte-1", None])
@pytest.mark.parametrize(
"backend", ["harmony", "aria-1", "aria-2", "forte-1", None]
)
def test_backend_initialization(self, backend):
"""Test that the device initializes with the correct backend."""
dev = qml.device(
Expand Down Expand Up @@ -304,6 +326,7 @@ def mock_submit_job(*args):
GPI(0.1, wires=[0])
GPI2(0.2, wires=[1])
MS(0.2, 0.3, wires=[1, 2])
MS(0.4, 0.5, 0.1, wires=[1, 2])

dev.apply(tape.operations)

Expand All @@ -326,4 +349,11 @@ def mock_submit_job(*args):
"gate": "ms",
"targets": [1, 2],
"phases": [0.2, 0.3],
"rotation": 0.25,
}
assert dev.job["input"]["circuit"][3] == {
"gate": "ms",
"targets": [1, 2],
"phases": [0.4, 0.5],
"rotation": 0.1,
}

0 comments on commit 003f9f4

Please sign in to comment.