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

enable partially entangling ms gates #101

Merged
merged 15 commits into from
Apr 30, 2024
4 changes: 3 additions & 1 deletion pennylane_ionq/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,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["angle"] = float(par[2])
else:
gate["phase"] = float(par[0])
elif par:
Expand Down
26 changes: 16 additions & 10 deletions pennylane_ionq/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,28 +61,34 @@ class GPI2(Operation): # pylint: disable=too-few-public-methods


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 \in [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"
splch marked this conversation as resolved.
Show resolved Hide resolved
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 Down
47 changes: 37 additions & 10 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 @@ -305,14 +324,15 @@ 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)

assert dev.job["input"]["format"] == "ionq.circuit.v0"
assert dev.job["input"]["gateset"] == "native"
assert dev.job["input"]["qubits"] == 3

assert len(dev.job["input"]["circuit"]) == 3
assert len(dev.job["input"]["circuit"]) == 4
assert dev.job["input"]["circuit"][0] == {
"gate": "gpi",
"target": 0,
Expand All @@ -327,4 +347,11 @@ def mock_submit_job(*args):
"gate": "ms",
"targets": [1, 2],
"phases": [0.2, 0.3],
"angle": 0.25,
}
assert dev.job["input"]["circuit"][3] == {
"gate": "ms",
"targets": [1, 2],
"phases": [0.4, 0.5],
"angle": 0.1,
}
Loading