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

rename debiasing + backend #124

Merged
merged 6 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export QISKIT_IONQ_API_TOKEN="token"
Then invoke instantiate the provider without any arguments:

```python
from qiskit_ionq import IonQProvider
from qiskit_ionq import IonQProvider, ErrorMitigation

provider = IonQProvider()
```
Expand All @@ -74,23 +74,22 @@ For example, running a Bell State:

```python
from qiskit import QuantumCircuit
from qiskit_ionq.constants import AggregationType

# Create a basic Bell State circuit:
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])

# Run the circuit on IonQ's platform:
job = simulator_backend.run(qc)
# Run the circuit on IonQ's platform with error mitigation:
job = simulator_backend.run(qc, error_mitigation=ErrorMitigation.DEBIASING)

# Print the results.
print(job.result().get_counts())

# Get results with a different aggregation method when symmetrization
# Get results with a different aggregation method when debiasing
# is applied as an error mitigation strategy
print(job.result(AggregationType.AVERAGE).get_counts())
print(job.result(sharpen=True).get_counts())

# The simulator specifically provides the the ideal probabilities and creates
# counts by sampling from these probabilities. The raw probabilities are also accessible:
Expand Down
2 changes: 1 addition & 1 deletion qiskit_ionq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@
from .ionq_provider import IonQProvider
from .version import __version__
from .ionq_gates import GPIGate, GPI2Gate, MSGate
from .constants import AggregationType, ErrorMitigation
from .constants import ErrorMitigation
11 changes: 2 additions & 9 deletions qiskit_ionq/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,11 @@ class JobStatusMap(enum.Enum):
FAILED = jobstatus.JobStatus.ERROR.name


class AggregationType(enum.Enum):
"""Class for job results aggregation enumerated type."""

AVERAGE = "average"
PLURALITY = "plurality"


class ErrorMitigation(enum.Enum):
"""Class for error mitigation settings enumerated type."""

SYMMETRIZATION = {"symmetrization": True}
NO_SYMMETRIZATION = {"symmetrization": False}
DEBIASING = {"debias": True}
NO_DEBIASING = {"debias": False}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that this requires SDK-side updates if we offer new error mitigation settings.

Is it possible to pass in an arbitrary JSON object for error_mitigation ? Can we support that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im fine with that but then we need to have a source of clear documentation of error mitigation settings that we support - that could be https://docs.ionq.com/#tag/jobs under error_mitigation. ill add that as well to the readme

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping me on the docs change if you don't mind; mostly curious on the process for rolling those out



__all__ = ["APIJobStatus", "JobStatusMap"]
8 changes: 4 additions & 4 deletions qiskit_ionq/ionq_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,14 @@ def get_calibration_data(self, backend_name: str) -> dict:
return res.json()

@retry(exceptions=IonQRetriableError, max_delay=60, backoff=2, jitter=1)
def get_results(self, job_id: str, aggregation=None):
def get_results(self, job_id: str, sharpen=None):
"""Retrieve job results from the IonQ API.

The returned JSON dict will only have data if job has completed.

Args:
job_id (str): The ID of a job to retrieve.
aggregation (str): type of results aggregation
sharpen (bool): if results are error-mitigated
splch marked this conversation as resolved.
Show resolved Hide resolved

Raises:
IonQAPIError: When the API returns a non-200 status code.
Expand All @@ -215,8 +215,8 @@ def get_results(self, job_id: str, aggregation=None):

params = {}

if aggregation:
params["aggregation"] = aggregation
if sharpen is not None:
params["sharpen"] = sharpen

req_path = self.make_path("jobs", job_id, "results")
res = requests.get(req_path, params, headers=self.api_headers, timeout=30)
Expand Down
16 changes: 5 additions & 11 deletions qiskit_ionq/ionq_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
from qiskit.providers import JobV1, jobstatus
from qiskit.providers.exceptions import JobTimeoutError
from .ionq_result import IonQResult as Result
from .constants import AggregationType
from .helpers import decompress_metadata_string_to_dict


Expand Down Expand Up @@ -233,7 +232,7 @@ def get_probabilities(self, circuit=None): # pylint: disable=unused-argument
"""
return self.result().get_probabilities()

def result(self, aggregation: AggregationType = None):
def result(self, sharpen: bool=None):
splch marked this conversation as resolved.
Show resolved Hide resolved
"""Retrieve job result data.

.. NOTE::
Expand All @@ -255,10 +254,10 @@ def result(self, aggregation: AggregationType = None):
Returns:
Result: A Qiskit :class:`Result <qiskit.result.Result>` representation of this job.
"""
# TODO: cache results by aggregation type
# TODO: cache results by sharpen

if aggregation is not None and not isinstance(aggregation, AggregationType):
warnings.warn("Invalid aggregation type")
if sharpen is not None and not isinstance(sharpen, bool):
warnings.warn("Invalid sharpen type")

# Wait for the job to complete.
try:
Expand All @@ -269,12 +268,7 @@ def result(self, aggregation: AggregationType = None):
) from ex

if self._status is jobstatus.JobStatus.DONE:
agg_type = (
aggregation.value
if isinstance(aggregation, AggregationType)
else aggregation
)
response = self._client.get_results(self._job_id, agg_type)
response = self._client.get_results(self._job_id, sharpen)
self._result = self._format_result(response)

return self._result
Expand Down
4 changes: 2 additions & 2 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def formatted_result(provider):
settings = {"lorem": {"ipsum": "dolor"}}

# Create a backend and client to use for accessing the job.
backend = provider.get_backend("ionq_qpu.aria.1")
backend = provider.get_backend("ionq_qpu.aria-1")
backend.set_options(job_settings=settings)
client = backend.create_client()

Expand All @@ -274,7 +274,7 @@ def formatted_result(provider):
with _default_requests_mock() as requests_mock:
# Mock the response with our dummy job response.
requests_mock.get(path,
json=dummy_job_response(job_id, "qpu.aria.1", "completed", settings))
json=dummy_job_response(job_id, "qpu.aria-1", "completed", settings))

requests_mock.get(results_path,
json={"0": 0.5, "2": 0.499999})
Expand Down
37 changes: 11 additions & 26 deletions test/helpers/test_qiskit_to_ionq.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ def test_output_map__with_multiple_registers(
cr1 = ClassicalRegister(2, "cr1")

qc = QuantumCircuit(qr0, qr1, cr0, cr1, name="test_name")
qc.measure([qr0[0], qr0[1], qr1[0], qr1[1]],
[cr0[0], cr0[1], cr1[0], cr1[1]])
qc.measure([qr0[0], qr0[1], qr1[0], qr1[1]], [cr0[0], cr0[1], cr1[0], cr1[1]])

ionq_json = qiskit_to_ionq(
qc, simulator_backend, passed_args={"shots": 123, "sampler_seed": 42}
Expand Down Expand Up @@ -214,8 +213,7 @@ def test_circuit_transpile(simulator_backend):
Args:
simulator_backend (IonQSimulatorBackend): A simulator backend fixture.
"""
new_backend = simulator_backend.with_name(
"ionq_simulator", gateset="native")
new_backend = simulator_backend.with_name("ionq_simulator", gateset="native")
circ = QuantumCircuit(2, 2, name="blame_test")
circ.cnot(1, 0)
circ.h(1)
Expand All @@ -224,7 +222,7 @@ def test_circuit_transpile(simulator_backend):

with pytest.raises(TranspilerError) as exc_info:
transpile(circ, backend=new_backend)
assert "Unable to map source basis" in exc_info.value.message
assert "Unable to translate the operations in the circuit" in exc_info.value.message


def test_circuit_incorrect(simulator_backend):
Expand All @@ -233,8 +231,7 @@ def test_circuit_incorrect(simulator_backend):
Args:
simulator_backend (IonQSimulatorBackend): A simulator backend fixture.
"""
native_backend = simulator_backend.with_name(
"ionq_simulator", gateset="native")
native_backend = simulator_backend.with_name("ionq_simulator", gateset="native")
circ = QuantumCircuit(2, 2, name="blame_test")
circ.cnot(1, 0)
circ.h(1)
Expand Down Expand Up @@ -290,20 +287,15 @@ def test_full_native_circuit(simulator_backend):
Args:
simulator_backend (IonQSimulatorBackend): A simulator backend fixture.
"""
native_backend = simulator_backend.with_name(
"ionq_simulator", gateset="native")
native_backend = simulator_backend.with_name("ionq_simulator", gateset="native")
qc = QuantumCircuit(3, name="blame_test")
qc.append(GPIGate(0.1), [0])
qc.append(GPI2Gate(0.2), [1])
qc.append(MSGate(0.2, 0.3, 0.25), [1, 2])
ionq_json = qiskit_to_ionq(
qc,
native_backend,
passed_args={
"noise_model": "harmony",
"sampler_seed": 23,
"shots": 200
},
passed_args={"noise_model": "harmony", "sampler_seed": 23, "shots": 200},
)
expected_metadata_header = {
"memory_slots": 0,
Expand Down Expand Up @@ -331,8 +323,7 @@ def test_full_native_circuit(simulator_backend):
"circuit": [
{"gate": "gpi", "target": 0, "phase": 0.1},
{"gate": "gpi2", "target": 1, "phase": 0.2},
{"gate": "ms", "targets": [1, 2],
"phases": [0.2, 0.3], "angle": 0.25},
{"gate": "ms", "targets": [1, 2], "phases": [0.2, 0.3], "angle": 0.25},
],
},
}
Expand All @@ -354,8 +345,8 @@ def test_full_native_circuit(simulator_backend):
@pytest.mark.parametrize(
"error_mitigation,expected",
[
(ErrorMitigation.NO_SYMMETRIZATION, {"symmetrization": False}),
(ErrorMitigation.SYMMETRIZATION, {"symmetrization": True}),
(ErrorMitigation.NO_DEBIASING, {"debias": False}),
(ErrorMitigation.DEBIASING, {"debias": True}),
],
)
def test__error_mitigation_settings(simulator_backend, error_mitigation, expected):
Expand All @@ -368,14 +359,8 @@ def test__error_mitigation_settings(simulator_backend, error_mitigation, expecte
"""
qc = QuantumCircuit(1, 1)

args = {
"shots": 123,
"sampler_seed": 42,
"error_mitigation": error_mitigation
}
ionq_json = qiskit_to_ionq(
qc, simulator_backend, passed_args=args
)
args = {"shots": 123, "sampler_seed": 42, "error_mitigation": error_mitigation}
ionq_json = qiskit_to_ionq(qc, simulator_backend, passed_args=args)
actual = json.loads(ionq_json)
actual_error_mitigation = actual.pop("error_mitigation")

Expand Down
13 changes: 6 additions & 7 deletions test/ionq_job/test_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@

from qiskit.qobj.utils import MeasLevel
from qiskit_ionq import exceptions, ionq_job
from qiskit_ionq.constants import AggregationType

from .. import conftest

Expand Down Expand Up @@ -462,7 +461,7 @@ def test_result(mock_backend, requests_mock):
assert job.result().to_dict() == expected_result


def test_result__with_aggregation(mock_backend, requests_mock):
def test_result__with_sharpen(mock_backend, requests_mock):
"""Test basic "happy path" for result fetching.

Args:
Expand All @@ -478,16 +477,16 @@ def test_result__with_aggregation(mock_backend, requests_mock):
path = client.make_path("jobs", job_id)
requests_mock.get(path, status_code=200, json=job_result)

results_path = client.make_path("jobs", job_id, "results") + "?aggregation=average"
results_path = client.make_path("jobs", job_id, "results") + "?sharpen=false"
requests_mock.get(results_path, status_code=200, json={"0": 0.5, "2": 0.499999})

# Create a job ref (this will call .status() to fetch our mock above)
job = ionq_job.IonQJob(mock_backend, job_id)

assert job.result(aggregation=AggregationType.AVERAGE).to_dict() == expected_result
assert job.result(sharpen=False).to_dict() == expected_result


def test_result__bad_aggregation(mock_backend, requests_mock):
def test_result__bad_sharpen(mock_backend, requests_mock):
"""Test basic "happy path" for result fetching.

Args:
Expand All @@ -509,8 +508,8 @@ def test_result__bad_aggregation(mock_backend, requests_mock):
# Create a job ref (this will call .status() to fetch our mock above)
job = ionq_job.IonQJob(mock_backend, job_id)

with pytest.warns(UserWarning, match="Invalid aggregation type"):
job.result(aggregation="blah")
with pytest.warns(UserWarning, match="Invalid sharpen type"):
job.result(sharpen="blah")


def test_result__from_circuit(mock_backend, requests_mock):
Expand Down
8 changes: 4 additions & 4 deletions test/ionq_provider/test_backend_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ def test_backend_eq():
"""Verifies equality works for various backends"""
pro = IonQProvider("123456")

sub1 = pro.get_backend('ionq_qpu.sub.1')
sub2 = pro.get_backend('ionq_qpu.sub.2')
also_sub1 = pro.get_backend('ionq_qpu.sub.1')
simulator = pro.get_backend('ionq_simulator')
sub1 = pro.get_backend("ionq_qpu.sub-1")
sub2 = pro.get_backend("ionq_qpu.sub-2")
also_sub1 = pro.get_backend("ionq_qpu.sub-1")
simulator = pro.get_backend("ionq_simulator")

assert sub1 == also_sub1
assert sub1 != sub2
Expand Down