Skip to content

Commit

Permalink
Add noise options to ACES (#699)
Browse files Browse the repository at this point in the history
Adds the ability to pass noise models to the ACES endpoints to allow for
more interesting simulations.

(related: https://github.com/Infleqtion/server-superstaq/pull/2634)

---------

Co-authored-by: richrines1 <85512171+richrines1@users.noreply.github.com>
Co-authored-by: Victory Omole <vtomole2@gmail.com>
  • Loading branch information
3 people authored Aug 31, 2023
1 parent 6f03849 commit 7158072
Show file tree
Hide file tree
Showing 12 changed files with 378 additions and 38 deletions.
16 changes: 16 additions & 0 deletions cirq-superstaq/cirq_superstaq/daily_integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,22 @@ def test_dfe(service: css.Service) -> None:
assert isinstance(result, float)


def test_aces(service: css.Service) -> None:
noise_model = cirq.NoiseModel.from_noise_model_like(cirq.depolarize(0.1))
job_id = service.submit_aces(
target="ss_unconstrained_simulator",
qubits=[0],
shots=100,
num_circuits=10,
mirror_depth=5,
extra_depth=7,
method="noise-sim",
noise=noise_model,
)
result = service.process_aces(job_id)
assert len(result) == 18


def test_job(service: css.Service) -> None:
circuit = cirq.Circuit(cirq.measure(cirq.q(0)))
job = service.create_job(circuit, target="ibmq_qasm_simulator", repetitions=10)
Expand Down
82 changes: 82 additions & 0 deletions cirq-superstaq/cirq_superstaq/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# limitations under the License.
"""Service to access Superstaqs API."""

import numbers
import warnings
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, Union

Expand Down Expand Up @@ -804,6 +805,87 @@ def process_dfe(self, ids: List[str]) -> float:
"""
return self._client.process_dfe(ids)

def submit_aces(
self,
target: str,
qubits: Sequence[int],
shots: int,
num_circuits: int,
mirror_depth: int,
extra_depth: int,
method: Optional[str] = None,
noise: Optional[Union[str, cirq.NoiseModel]] = None,
error_prob: Optional[Union[float, Tuple[float, float, float]]] = None,
tag: Optional[str] = None,
lifespan: Optional[int] = None,
) -> str:
"""Submits the jobs to characterize `target` through the ACES protocol.
The following gate eigenvalues are eestimated. For each qubit in the device, we consider
six Clifford gates. These are given by the XZ maps: XZ, ZX, -YZ, -XY, ZY, YX. For each of
these gates, three eigenvalues are returned (X, Y, Z, in that order). Then, the two-qubit
gate considered here is the CZ in linear connectivity (each qubit n with n + 1). For this
gate, 15 eigenvalues are considered: XX, XY, XZ, XI, YX, YY, YZ, YI, ZX, ZY, ZZ, ZI, IX, IY
IZ, in that order.
If n qubits are characterized, the first 18 * n entries of the list returned by
`process_aces` will contain the single-qubit eigenvalues for each gate in the order above.
After all the single-qubit eigenvalues, the next 15 * (n - 1) entries will contain for the
CZ connections, in ascending order.
The protocol in detail can be found in: https://arxiv.org/abs/2108.05803.
Args:
target: The device target to characterize.
qubits: A list with the qubit indices to characterize.
shots: How many shots to use per circuit submitted.
num_circuits: How many random circuits to use in the protocol.
mirror_depth: The half-depth of the mirror portion of the random circuits.
extra_depth: The depth of the fully random portion of the random circuits.
method: Which type of method to execute the circuits with.
noise: Noise model to simulate the protocol with. It can be either a string or a
`cirq.NoiseModel`. Valid strings are "symmetric_depolarize", "phase_flip",
"bit_flip" and "asymmetric_depolarize".
error_prob: The error probabilities if a string was passed to `noise`.
* For "asymmetric_depolarize", `error_prob` will be a three-tuple with the error
rates for the X, Y, Z gates in that order. So, a valid argument would be
`error_prob = (0.1, 0.1, 0.1)`. Notice that these values must add up to less than
or equal to 1.
* For the other channels, `error_prob` is one number less than or equal to 1, e.g.,
`error_prob = 0.1`.
tag: Tag for all jobs submitted for this protocol.
lifespan: How long to store the jobs submitted for in days (only works with right
permissions).
Returns:
A string with the job id for the ACES job created.
Raises:
ValueError: If the target or noise model is not valid.
SuperstaqServerException: If the request fails.
"""
noise_dict: Dict[str, object] = {}
if isinstance(noise, str):
noise_dict["type"] = noise
noise_dict["params"] = (
(error_prob,) if isinstance(error_prob, numbers.Number) else error_prob
)
elif isinstance(noise, cirq.NoiseModel):
noise_dict["cirq_noise_model"] = cirq.to_json(noise)

return self._client.submit_aces(
target=target,
qubits=qubits,
shots=shots,
num_circuits=num_circuits,
mirror_depth=mirror_depth,
extra_depth=extra_depth,
method=method,
noise=noise_dict,
tag=tag,
lifespan=lifespan,
)

def target_info(self, target: str) -> Dict[str, Any]:
"""Returns information about device specified by `target`.
Expand Down
37 changes: 37 additions & 0 deletions cirq-superstaq/cirq_superstaq/service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,43 @@ def test_service_dfe(mock_post: mock.MagicMock) -> None:
assert service.process_dfe(["1", "2"]) == 1


@mock.patch("requests.post")
def test_aces(
mock_post: mock.MagicMock,
) -> None:
service = css.Service(api_key="key", remote_host="http://example.com")
mock_post.return_value.json = lambda: "id1"
assert (
service.submit_aces(
target="ss_unconstrained_simulator",
qubits=[0, 1],
shots=100,
num_circuits=10,
mirror_depth=5,
extra_depth=5,
noise=cirq.NoiseModel.from_noise_model_like(cirq.depolarize(0.1)),
)
== "id1"
)

assert (
service.submit_aces(
target="ss_unconstrained_simulator",
qubits=[0, 1],
shots=100,
num_circuits=10,
mirror_depth=5,
extra_depth=5,
noise="asymmetric_depolarize",
error_prob=(0.1, 0.1, 0.1),
)
== "id1"
)

mock_post.return_value.json = lambda: [1] * 51
assert service.process_aces("id1") == [1] * 51


@mock.patch("requests.post")
def test_service_target_info(mock_post: mock.MagicMock) -> None:
fake_data = {"target_info": {"backend_name": "ss_example_qpu", "max_experiments": 1234}}
Expand Down
61 changes: 49 additions & 12 deletions general-superstaq/general_superstaq/service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import numbers
import os
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union

import qubovert as qv

Expand Down Expand Up @@ -297,14 +298,32 @@ def aqt_download_configs(
def submit_aces(
self,
target: str,
qubits: List[int],
qubits: Sequence[int],
shots: int,
num_circuits: int,
mirror_depth: int,
extra_depth: int,
**kwargs: Any,
method: Optional[str] = None,
noise: Optional[str] = None,
error_prob: Optional[Union[float, Tuple[float, float, float]]] = None,
tag: Optional[str] = None,
lifespan: Optional[int] = None,
) -> str:
"""Performs a POST request on the `/aces` endpoint.
"""Submits the jobs to characterize `target` through the ACES protocol.
The following gate eigenvalues are eestimated. For each qubit in the device, we consider
six Clifford gates. These are given by the XZ maps: XZ, ZX, -YZ, -XY, ZY, YX. For each of
these gates, three eigenvalues are returned (X, Y, Z, in that order). Then, the two-qubit
gate considered here is the CZ in linear connectivity (each qubit n with n + 1). For this
gate, 15 eigenvalues are considered: XX, XY, XZ, XI, YX, YY, YZ, YI, ZX, ZY, ZZ, ZI, IX, IY
IZ, in that order.
If n qubits are characterized, the first 18 * n entries of the list returned by
`process_aces` will contain the single-qubit eigenvalues for each gate in the order above.
After all the single-qubit eigenvalues, the next 15 * (n - 1) entries will contain for the
CZ connections, in ascending order.
The protocol in detail can be found in: https://arxiv.org/abs/2108.05803.
Args:
target: The device target to characterize.
Expand All @@ -313,31 +332,49 @@ def submit_aces(
num_circuits: How many random circuits to use in the protocol.
mirror_depth: The half-depth of the mirror portion of the random circuits.
extra_depth: The depth of the fully random portion of the random circuits.
kwargs: Other execution parameters.
- tag: Tag for all jobs submitted for this protocol.
- lifespan: How long to store the jobs submitted for in days (only works with right
method: Which type of method to execute the circuits with.
noise: Noise model to simulate the protocol with. Valid strings are
"symmetric_depolarize", "phase_flip", "bit_flip" and "asymmetric_depolarize".
error_prob: The error probabilities if a string was passed to `noise`.
* For "asymmetric_depolarize", `error_prob` will be a three-tuple with the error
rates for the X, Y, Z gates in that order. So, a valid argument would be
`error_prob = (0.1, 0.1, 0.1)`. Notice that these values must add up to less than
or equal to 1.
* For the other channels, `error_prob` is one number less than or equal to 1, e.g.,
`error_prob = 0.1`.
tag: Tag for all jobs submitted for this protocol.
lifespan: How long to store the jobs submitted for in days (only works with right
permissions).
- method: Which type of method to execute the circuits with.
Returns:
A string with the job id for the ACES job created.
Raises:
ValueError: If any the target passed are not valid.
SuperstaqServerException: if the request fails.
ValueError: If the target or noise model is not valid.
SuperstaqServerException: If the request fails.
"""
noise_dict: Dict[str, object] = {}
if noise:
noise_dict["type"] = noise
noise_dict["params"] = (
(error_prob,) if isinstance(error_prob, numbers.Number) else error_prob
)

return self._client.submit_aces(
target=target,
qubits=qubits,
shots=shots,
num_circuits=num_circuits,
mirror_depth=mirror_depth,
extra_depth=extra_depth,
**kwargs,
method=method,
noise=noise_dict,
tag=tag,
lifespan=lifespan,
)

def process_aces(self, job_id: str) -> List[float]:
"""Makes a POST request to the "/aces_fetch" endpoint.
"""Process a job submitted through `submit_aces`.
Args:
job_id: The job id returned by `submit_aces`.
Expand Down
2 changes: 2 additions & 0 deletions general-superstaq/general_superstaq/service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ def test_aces(
num_circuits=10,
mirror_depth=5,
extra_depth=5,
noise="bit_flip",
error_prob=0.1,
)
== "id1"
)
Expand Down
34 changes: 23 additions & 11 deletions general-superstaq/general_superstaq/superstaq_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import textwrap
import time
import urllib
from typing import Any, Callable, Dict, List, Optional, Union
from typing import Any, Callable, Dict, List, Optional, Sequence, Union

import qubovert as qv
import requests
Expand Down Expand Up @@ -462,12 +462,15 @@ def process_dfe(self, job_ids: List[str]) -> float:
def submit_aces(
self,
target: str,
qubits: List[int],
qubits: Sequence[int],
shots: int,
num_circuits: int,
mirror_depth: int,
extra_depth: int,
**kwargs: Any,
method: Optional[str] = None,
noise: Optional[Dict[str, object]] = None,
tag: Optional[str] = None,
lifespan: Optional[int] = None,
) -> str:
"""Performs a POST request on the `/aces` endpoint.
Expand All @@ -478,18 +481,18 @@ def submit_aces(
num_circuits: How many random circuits to use in the protocol.
mirror_depth: The half-depth of the mirror portion of the random circuits.
extra_depth: The depth of the fully random portion of the random circuits.
kwargs: Other execution parameters.
- tag: Tag for all jobs submitted for this protocol.
- lifespan: How long to store the jobs submitted for in days (only works with right
method: Which type of method to execute the circuits with.
noise: A dictionary describing a noise model to simulate the run with.
tag: Tag for all jobs submitted for this protocol.
lifespan: How long to store the jobs submitted for in days (only works with right
permissions).
- method: Which type of method to execute the circuits with.
Returns:
A string with the job id for the ACES job created.
Raises:
ValueError: If any the target passed are not valid.
SuperstaqServerException: if the request fails.
ValueError: If the target or noise model is not valid.
SuperstaqServerException: If the request fails.
"""
gss.validation.validate_target(target)

Expand All @@ -502,8 +505,17 @@ def submit_aces(
"extra_depth": extra_depth,
}

if kwargs:
json_dict["options"] = json.dumps(kwargs)
if method:
json_dict["method"] = method
if noise:
if "type" in noise.keys():
gss.validation.validate_noise_type(noise, len(qubits))
json_dict["noise"] = noise
if tag:
json_dict["tag"] = tag
if lifespan:
json_dict["lifespan"] = lifespan

return self.post_request("/aces", json_dict)

def process_aces(self, job_id: str) -> List[float]:
Expand Down
8 changes: 7 additions & 1 deletion general-superstaq/general_superstaq/superstaq_client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,9 @@ def test_superstaq_client_aces(mock_post: mock.MagicMock) -> None:
mirror_depth=6,
extra_depth=4,
method="dry-run",
tag="test-tag",
lifespan=10,
noise={"type": "symmetric_depolarize", "params": (0.01,)},
)

expected_json = {
Expand All @@ -627,7 +630,10 @@ def test_superstaq_client_aces(mock_post: mock.MagicMock) -> None:
"num_circuits": 10,
"mirror_depth": 6,
"extra_depth": 4,
"options": json.dumps({"method": "dry-run"}),
"method": "dry-run",
"noise": {"type": "symmetric_depolarize", "params": (0.01,)},
"tag": "test-tag",
"lifespan": 10,
}
mock_post.assert_called_with(
f"http://example.com/{API_VERSION}/aces",
Expand Down
Loading

0 comments on commit 7158072

Please sign in to comment.