Skip to content

Commit

Permalink
Submit circuit batch (#34)
Browse files Browse the repository at this point in the history
* Submit circuit batch

Co-authored-by: Matthias Beuerle <[email protected]>
Co-authored-by: Ville Bergholm <[email protected]>
Co-authored-by: Matthias Beuerle <[email protected]>
Co-authored-by: Ville Bergholm <[email protected]>
  • Loading branch information
5 people authored Jun 28, 2022
1 parent d0749cc commit 4e888b0
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 35 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Changelog
=========

Version 4.0
===========

* Implement functionality to submit a batch of circuits in one job. `#34 <https://github.com/iqm-finland/iqm-client/pull/34>`_

Version 3.3
===========

Expand Down
59 changes: 36 additions & 23 deletions src/iqm_client/iqm_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,17 @@
Circuit output
==============
The :class:`RunResult` class represents the results of a quantum circuit execution.
If the run succeeded, :attr:`RunResult.measurements` contains the output of the circuit, consisting
of the results of the measurement operations in the circuit.
It is a dictionary that maps each measurement key to a 2D array of measurement results, represented as a nested list.
``RunResult.measurements[key][shot][index]`` is the result of measuring the ``index``'th qubit in measurement
operation ``key`` in the shot ``shot``. The results are nonnegative integers representing the computational
basis state (for qubits, 0 or 1) that was the measurement outcome.
The :class:`RunResult` class represents the results of the quantum circuit execution.
If the run succeeded, :attr:`RunResult.measurements` contains the output of the batch of circuits,
consisting of the results of the measurement operations in each circuit.
It is a list of dictionaries, where each dict maps each measurement key to a 2D array of measurement
results, represented as a nested list.
``RunResult.measurements[circuit_index][key][shot][qubit_index]`` is the result of measuring the
``qubit_index``'th qubit in measurement operation ``key`` in the shot ``shot`` in the
``circuit_index``'th circuit of the batch.
The results are nonnegative integers representing the computational basis state (for qubits, 0 or 1)
that was the measurement outcome.
----
"""
Expand Down Expand Up @@ -190,10 +194,12 @@ class SingleQubitMapping(BaseModel):


class RunRequest(BaseModel):
"""Request for an IQM quantum computer to execute a quantum circuit.
"""Request for an IQM quantum computer to execute a batch of quantum circuits.
Note: all circuits in a batch must measure the same qubits otherwise batch execution fails.
"""
circuit: Circuit = Field(..., description='quantum circuit to execute')
'quantum circuit to execute'
circuits: list[Circuit] = Field(..., description='batch of quantum circuit(s) to execute')
'batch of quantum circuit(s) to execute'
settings: Optional[dict[str, Any]] = Field(
None,
description='EXA settings node containing the calibration data, or None if using default settings'
Expand All @@ -204,24 +210,30 @@ class RunRequest(BaseModel):
description='mapping of logical qubit names to physical qubit names, or None if using physical qubit names'
)
'mapping of logical qubit names to physical qubit names, or None if using physical qubit names'
shots: int = Field(..., description='how many times to execute the circuit')
'how many times to execute the circuit'
shots: int = Field(..., description='how many times to execute each circuit in the batch')
'how many times to execute each circuit in the batch'


CircuitMeasurementResults = dict[str, list[list[int]]]
"""Measurement results from a single circuit. For each measurement operation in the circuit,
maps the measurement key to the corresponding results. The outer list elements correspond to shots,
and the inner list elements to the qubits measured in the measurement operation."""


class RunResult(BaseModel):
"""Results of a circuit execution.
"""Results of executing a batch of circuit(s).
* ``measurements`` is present iff the status is ``'ready'``.
* ``message`` carries additional information for the ``'failed'`` status.
* If the status is ``'pending'``, ``measurements`` and ``message`` are ``None``.
"""
status: RunStatus = Field(..., description="current status of the run, in ``{'pending', 'ready', 'failed'}``")
"current status of the run, in ``{'pending', 'ready', 'failed'}``"
measurements: Optional[dict[str, list[list[int]]]] = Field(
measurements: Optional[list[CircuitMeasurementResults]] = Field(
None,
description='if the run has finished successfully, the measurement results for the circuit'
description='if the run has finished successfully, the measurement results for the circuit(s)'
)
'if the run has finished successfully, the measurement results for the circuit'
'if the run has finished successfully, the measurement results for the circuit(s)'
message: Optional[str] = Field(None, description='if the run failed, an error message')
'if the run failed, an error message'
warnings: Optional[list[str]] = Field(None, description='list of warning messages')
Expand Down Expand Up @@ -359,18 +371,19 @@ def __init__(
self._credentials = _get_credentials(credentials)
self._update_tokens()

def submit_circuit(
def submit_circuits(
self,
circuit: Circuit,
circuits: list[Circuit],
qubit_mapping: Optional[list[SingleQubitMapping]] = None,
shots: int = 1
) -> UUID:
"""Submits a quantum circuit to be executed on a quantum computer.
"""Submits a batch of quantum circuits for execution on a quantum computer.
Args:
circuit: circuit to be executed
qubit_mapping: Mapping of human-readable (logical) qubit names in ``circuit`` to physical qubit names.
Can be set to ``None`` if ``circuit`` already uses physical qubit names.
circuits: list of circuit to be executed
qubit_mapping: Mapping of human-readable (logical) qubit names in to physical qubit names.
Can be set to ``None`` if all ``circuits`` already use physical qubit names.
Note that the ``qubit_mapping`` is used for all ``circuits``.
shots: number of times ``circuit`` is executed
Returns:
Expand All @@ -381,7 +394,7 @@ def submit_circuit(

data = RunRequest(
qubit_mapping=qubit_mapping,
circuit=circuit,
circuits=circuits,
settings=self._settings,
shots=shots
)
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def generate_server_stubs(base_url):
).thenReturn(
MockJsonResponse(
200,
{'status': 'ready', 'measurements': {'result': [[1, 0, 1, 1], [1, 0, 0, 1], [1, 0, 1, 1], [1, 0, 1, 1]]}}
{'status': 'ready', 'measurements': [{'result': [[1, 0, 1, 1], [1, 0, 0, 1], [1, 0, 1, 1], [1, 0, 1, 1]]}]}
)
)

Expand Down
5 changes: 4 additions & 1 deletion tests/test_generate_run_request_json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ def sample_valid_run_request(sample_circuit):
Returns valid example run request.
"""
return RunRequest(
circuit=sample_circuit,
circuits=[sample_circuit],
settings={},
shots=1000,
qubit_mapping=[{'logical_name': 'q1', 'physical_name': 'qubit_1'}]
).dict()


@pytest.fixture
def sample_invalid_run_request(sample_valid_run_request):
"""
Expand All @@ -44,13 +45,15 @@ def sample_invalid_run_request(sample_valid_run_request):
invalid_run_request['shots'] = 'not_a_number'
return invalid_run_request


def test_jsonschema_validates_run_requests(sample_valid_run_request):
"""
Tests that the generated json schema validates valid run requests.
"""
json_schema = generate_json_schema(RunRequest, '')
validate(schema=json_schema, instance=sample_valid_run_request)


def test_jsonschema_throws_validation_errors(sample_invalid_run_request):
"""
Tests that the generated json schema rejects invalid run requests.
Expand Down
19 changes: 9 additions & 10 deletions tests/test_iqm_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,44 +24,43 @@
from tests.conftest import MockJsonResponse, existing_run, missing_run


def test_submit_circuit_returns_id(mock_server, settings_dict, base_url, sample_circuit):
def test_submit_circuits_returns_id(mock_server, settings_dict, base_url, sample_circuit):
"""
Tests sending a circuit
"""
client = IQMClient(base_url, settings_dict)
job_id = client.submit_circuit(
job_id = client.submit_circuits(
qubit_mapping=[
SingleQubitMapping(logical_name='Qubit A', physical_name='qubit_1'),
SingleQubitMapping(logical_name='Qubit B', physical_name='qubit_2')
],
circuit=Circuit.parse_obj(sample_circuit),
circuits=[Circuit.parse_obj(sample_circuit)],
shots=1000)
assert job_id == existing_run


def test_submit_circuit_without_settings_returns_id(mock_server, base_url, sample_circuit):
def test_submit_circuits_without_settings_returns_id(mock_server, base_url, sample_circuit):
"""
Tests sending a circuit
"""
client = IQMClient(base_url)
job_id = client.submit_circuit(
job_id = client.submit_circuits(
qubit_mapping=[
SingleQubitMapping(logical_name='Qubit A', physical_name='qubit_1'),
SingleQubitMapping(logical_name='Qubit B', physical_name='qubit_2')
],
circuit=Circuit.parse_obj(sample_circuit),
circuits=[Circuit.parse_obj(sample_circuit)],
shots=1000)
assert job_id == existing_run



def test_submit_circuit_without_qubit_mapping_returns_id(mock_server, settings_dict, base_url, sample_circuit):
def test_submit_circuits_without_qubit_mapping_returns_id(mock_server, settings_dict, base_url, sample_circuit):
"""
Tests sending a circuit without qubit mapping
"""
client = IQMClient(base_url, settings_dict)
job_id = client.submit_circuit(
circuit=Circuit.parse_obj(sample_circuit),
job_id = client.submit_circuits(
circuits=[Circuit.parse_obj(sample_circuit)],
shots=1000)
assert job_id == existing_run

Expand Down

0 comments on commit 4e888b0

Please sign in to comment.