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

Submit circuit batch #34

Merged
merged 10 commits into from
Jun 28, 2022
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
28 changes: 16 additions & 12 deletions src/iqm_client/iqm_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ class SingleQubitMapping(BaseModel):
class RunRequest(BaseModel):
"""Request for an IQM quantum computer to execute a quantum circuit.
"""
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 @@ -205,23 +205,27 @@ class RunRequest(BaseModel):
)
'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'
'how many times to execute each circuit in the batch'


CircuitMeasurementResults = dict[str, list[list[int]]]
"""Type to represent measurement results from a single circuit. Maps measurement keys to corresponding results."""


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,16 +363,16 @@ 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
circuits: list of 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.
shots: number of times ``circuit`` is executed
Expand All @@ -381,7 +385,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