iqm-client 32.0.0__py3-none-any.whl → 33.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. iqm/cirq_iqm/devices/iqm_device_metadata.py +2 -1
  2. iqm/cirq_iqm/examples/demo_common.py +1 -1
  3. iqm/cirq_iqm/examples/demo_iqm_execution.py +3 -3
  4. iqm/cirq_iqm/iqm_sampler.py +47 -29
  5. iqm/cirq_iqm/serialize.py +1 -1
  6. iqm/cirq_iqm/transpiler.py +3 -1
  7. iqm/iqm_client/__init__.py +0 -2
  8. iqm/iqm_client/errors.py +6 -17
  9. iqm/iqm_client/iqm_client.py +199 -602
  10. iqm/iqm_client/models.py +20 -611
  11. iqm/iqm_client/transpile.py +11 -8
  12. iqm/iqm_client/validation.py +18 -9
  13. iqm/iqm_server_client/__init__.py +14 -0
  14. iqm/iqm_server_client/errors.py +6 -0
  15. iqm/iqm_server_client/iqm_server_client.py +755 -0
  16. iqm/iqm_server_client/models.py +179 -0
  17. iqm/iqm_server_client/py.typed +0 -0
  18. iqm/qiskit_iqm/__init__.py +8 -0
  19. iqm/qiskit_iqm/examples/bell_measure.py +2 -2
  20. iqm/qiskit_iqm/examples/transpile_example.py +9 -4
  21. iqm/qiskit_iqm/fake_backends/fake_adonis.py +2 -1
  22. iqm/qiskit_iqm/fake_backends/fake_aphrodite.py +2 -1
  23. iqm/qiskit_iqm/fake_backends/fake_apollo.py +2 -1
  24. iqm/qiskit_iqm/fake_backends/fake_deneb.py +2 -1
  25. iqm/qiskit_iqm/fake_backends/iqm_fake_backend.py +8 -7
  26. iqm/qiskit_iqm/iqm_backend.py +3 -4
  27. iqm/qiskit_iqm/iqm_circuit_validation.py +8 -7
  28. iqm/qiskit_iqm/iqm_job.py +106 -88
  29. iqm/qiskit_iqm/iqm_move_layout.py +2 -1
  30. iqm/qiskit_iqm/iqm_naive_move_pass.py +115 -56
  31. iqm/qiskit_iqm/iqm_provider.py +49 -36
  32. iqm/qiskit_iqm/iqm_target.py +12 -8
  33. iqm/qiskit_iqm/iqm_transpilation.py +219 -26
  34. iqm/qiskit_iqm/qiskit_to_iqm.py +150 -41
  35. iqm/qiskit_iqm/transpiler_plugins.py +11 -8
  36. {iqm_client-32.0.0.dist-info → iqm_client-33.0.0.dist-info}/METADATA +4 -14
  37. iqm_client-33.0.0.dist-info/RECORD +63 -0
  38. iqm/iqm_client/api.py +0 -90
  39. iqm/iqm_client/authentication.py +0 -206
  40. iqm_client-32.0.0.dist-info/RECORD +0 -60
  41. {iqm_client-32.0.0.dist-info → iqm_client-33.0.0.dist-info}/AUTHORS.rst +0 -0
  42. {iqm_client-32.0.0.dist-info → iqm_client-33.0.0.dist-info}/LICENSE.txt +0 -0
  43. {iqm_client-32.0.0.dist-info → iqm_client-33.0.0.dist-info}/WHEEL +0 -0
  44. {iqm_client-32.0.0.dist-info → iqm_client-33.0.0.dist-info}/entry_points.txt +0 -0
  45. {iqm_client-32.0.0.dist-info → iqm_client-33.0.0.dist-info}/top_level.txt +0 -0
@@ -20,9 +20,10 @@ from collections.abc import Iterable
20
20
  import cirq
21
21
  from cirq import Gate, NamedQid, devices, ops
22
22
  from iqm.cirq_iqm.serialize import _IQM_CIRQ_OP_MAP
23
- from iqm.iqm_client import DynamicQuantumArchitecture
24
23
  import networkx as nx
25
24
 
25
+ from iqm.station_control.interface.models import DynamicQuantumArchitecture
26
+
26
27
 
27
28
  @cirq.value.value_equality
28
29
  class IQMDeviceMetadata(devices.DeviceMetadata):
@@ -138,7 +138,7 @@ def simulate_without_measurements(
138
138
  return state
139
139
 
140
140
 
141
- def pause() -> None:
141
+ def pause() -> None: # noqa: D103
142
142
  input("\npress enter\n")
143
143
 
144
144
 
@@ -13,13 +13,13 @@
13
13
  # limitations under the License.
14
14
  """Demonstrates executing a quantum circuit on an IQM quantum computer.
15
15
 
16
- Set the STATION_CONTROL_URL environment variable before running this script.
16
+ Set the IQM_SERVER_URL environment variable before running this script.
17
17
  Also, if the server you are running against requires authentication you will also have to use
18
18
  the IQM_TOKENS_FILE or the IQM_TOKEN variable to set the access token.
19
19
 
20
20
  E.g.
21
21
 
22
- export STATION_CONTROL_URL="https://example.com/station"
22
+ export IQM_SERVER_URL="https://example.com"
23
23
  export IQM_TOKENS_FILE="/path/to/my/tokens.json"
24
24
  export IQM_TOKEN="<token here>"
25
25
  """
@@ -45,7 +45,7 @@ def demo_run_circuit() -> None:
45
45
  print("Original circuit:\n")
46
46
  print(circuit)
47
47
 
48
- sampler = IQMSampler(os.environ["STATION_CONTROL_URL"])
48
+ sampler = IQMSampler(os.environ["IQM_SERVER_URL"])
49
49
 
50
50
  circuit_routed, _, _ = sampler.device.route_circuit(circuit)
51
51
  circuit_decomposed = sampler.device.decompose_circuit(circuit_routed)
@@ -25,20 +25,29 @@ import warnings
25
25
  import cirq
26
26
  from iqm.cirq_iqm.devices.iqm_device import IQMDevice, IQMDeviceMetadata
27
27
  from iqm.cirq_iqm.serialize import serialize_circuit
28
- from iqm.iqm_client import CircuitBatch, CircuitCompilationOptions, IQMClient, JobAbortionError, RunRequest
28
+ from iqm.iqm_client import APITimeoutError, CircuitCompilationOptions, CircuitExecutionError, IQMClient
29
+ from iqm.iqm_server_client.models import JobStatus
29
30
  import numpy as np
30
31
 
32
+ from exa.common.errors.station_control_errors import StationControlError
33
+ from iqm.station_control.interface.models import CircuitBatch, RunRequest
34
+
31
35
 
32
36
  class IQMSampler(cirq.work.Sampler):
33
37
  """Circuit sampler for executing quantum circuits on IQM quantum computers.
34
38
 
35
- IQMSampler connects to a quantum computer through an IQM server.
39
+ IQMSampler connects to a quantum computer through an IQM Server.
36
40
  If the server requires user authentication, you can provide it either using environment
37
41
  variables, or as keyword arguments to IQMSampler. The user authentication kwargs are passed
38
42
  through to :class:`~iqm.iqm_client.iqm_client.IQMClient` as is, and are documented there.
39
43
 
40
44
  Args:
41
- url: URL of the IQM server. Has to start with http or https.
45
+ url: URL of the IQM Server (e.g. "https://resonance.meetiqm.com/").
46
+ quantum_computer: ID or alias of the quantum computer to connect to, if the IQM Server
47
+ instance controls more than one (e.g. "garnet"). ``None`` means connect to the
48
+ default one.
49
+ use_timeslot: Submits the job to the timeslot queue if set to ``True``. If set to ``False``,
50
+ the job is submitted to the normal on-demand queue.
42
51
  device: Device to execute the circuits on. If ``None``, the device will be created based
43
52
  on the calibration-specific dynamic quantum architecture obtained from
44
53
  :class:`~iqm.iqm_client.iqm_client.IQMClient`.
@@ -54,17 +63,20 @@ class IQMSampler(cirq.work.Sampler):
54
63
  self,
55
64
  url: str,
56
65
  *,
66
+ quantum_computer: str | None = None,
57
67
  device: IQMDevice | None = None,
58
68
  calibration_set_id: UUID | None = None,
59
- run_sweep_timeout: int | None = None,
69
+ run_sweep_timeout: float | None = None,
60
70
  compiler_options: CircuitCompilationOptions | None = None,
71
+ use_timeslot: bool = False,
61
72
  **user_auth_args, # contains keyword args token or tokens_file
62
73
  ):
63
- self._client = IQMClient(url, **user_auth_args)
74
+ self._client = IQMClient(url, quantum_computer=quantum_computer, **user_auth_args)
64
75
  dqa = self._client.get_dynamic_quantum_architecture(calibration_set_id)
65
76
  server_device_metadata = IQMDeviceMetadata.from_architecture(dqa)
66
77
  self._use_default_calibration_set = calibration_set_id is None
67
78
  self._calibration_set_id = dqa.calibration_set_id
79
+ self._use_timeslot = use_timeslot
68
80
  if device is None:
69
81
  self._device = IQMDevice(server_device_metadata)
70
82
  else:
@@ -85,13 +97,6 @@ class IQMSampler(cirq.work.Sampler):
85
97
  """Returns the device used by the sampler."""
86
98
  return self._device
87
99
 
88
- def close_client(self): # noqa: ANN201
89
- """Close IQMClient's session with the user authentication server. Discard the client."""
90
- if not self._client:
91
- return
92
- self._client.close_auth_session()
93
- self._client = None
94
-
95
100
  def run_sweep( # type: ignore[override]
96
101
  self, program: cirq.Circuit, params: cirq.Sweepable, repetitions: int = 1
97
102
  ) -> list[IQMResult]:
@@ -122,7 +127,6 @@ class IQMSampler(cirq.work.Sampler):
122
127
  ValueError: circuits are not valid for execution
123
128
  CircuitExecutionError: something went wrong on the server
124
129
  APITimeoutError: server did not return the results in the allocated time
125
- RuntimeError: IQM client session has been closed
126
130
 
127
131
  """
128
132
  results, metadata = self._send_circuits(
@@ -153,9 +157,6 @@ class IQMSampler(cirq.work.Sampler):
153
157
 
154
158
  serialized_circuits: CircuitBatch = [serialize_circuit(circuit) for circuit in programs]
155
159
 
156
- if not self._client:
157
- raise RuntimeError("Cannot submit circuits since session to IQM client has been closed.")
158
-
159
160
  different_calset_ids = set()
160
161
  for circuit in programs:
161
162
  if hasattr(circuit, "iqm_calibration_set_id"):
@@ -192,7 +193,7 @@ class IQMSampler(cirq.work.Sampler):
192
193
  ) -> tuple[list[dict[str, np.ndarray]], ResultMetadata]:
193
194
  """Sends a batch of circuits to be executed and retrieves the results.
194
195
 
195
- If a user interrupts the program while it is waiting for results, attempts to abort the submitted job.
196
+ If a user interrupts the program while it is waiting for results, attempts to cancel the submitted job.
196
197
 
197
198
  Args:
198
199
  circuits: quantum circuits to execute
@@ -201,29 +202,46 @@ class IQMSampler(cirq.work.Sampler):
201
202
  Returns:
202
203
  circuit execution results, result metadata
203
204
 
205
+ Raises:
206
+ CircuitExecutionError: something went wrong on the server
207
+ APITimeoutError: server did not return the results in the allocated time
208
+
204
209
  """
205
210
  run_request = self.create_run_request(circuits, repetitions=repetitions)
206
- job_id = self._client.submit_run_request(run_request)
207
-
208
- timeout_arg = [self._run_sweep_timeout] if self._run_sweep_timeout is not None else []
211
+ job = self._client.submit_run_request(run_request, use_timeslot=self._use_timeslot)
209
212
 
210
213
  try:
211
- results = self._client.wait_for_results(job_id, *timeout_arg)
212
-
214
+ if self._run_sweep_timeout is not None:
215
+ status = job.wait_for_completion(timeout_secs=self._run_sweep_timeout)
216
+ else:
217
+ status = job.wait_for_completion()
213
218
  except KeyboardInterrupt:
219
+ # user pressed Ctrl-C before the job finished
214
220
  try:
215
- self._client.abort_job(job_id)
216
- except JobAbortionError as e:
217
- warnings.warn(f"Failed to abort job: {e}")
221
+ job.cancel()
222
+ except StationControlError as e:
223
+ warnings.warn(f"Failed to cancel job: {e}")
218
224
  finally:
219
225
  sys.exit()
220
226
 
221
- if results.measurements is None:
222
- raise RuntimeError("No measurements returned from IQM quantum computer.")
227
+ if status not in JobStatus.terminal_statuses():
228
+ # cancel jobs which did not complete in time
229
+ # FIXME this is bad UX, we should return a job object instead so the user can decide if they want to cancel
230
+ job.cancel()
231
+ raise APITimeoutError(f"Job {job.job_id} didn't finish in the given time. Cancelled.")
232
+
233
+ if status != JobStatus.COMPLETED:
234
+ raise CircuitExecutionError(f"Job {job.job_id} did not finish successfully: {status}: {job._errors}")
235
+
236
+ result = job.result()
237
+ if result is None:
238
+ raise RuntimeError("No measurements received from IQM Server.")
223
239
 
224
240
  return (
225
- [{k: np.array(v) for k, v in measurements.items()} for measurements in results.measurements],
226
- ResultMetadata(job_id, results.metadata.calibration_set_id, run_request),
241
+ [{k: np.array(v) for k, v in measurements.items()} for measurements in result],
242
+ ResultMetadata(
243
+ job.job_id, job.data.compilation.calibration_set_id if job.data.compilation else None, run_request
244
+ ),
227
245
  )
228
246
 
229
247
  @staticmethod
iqm/cirq_iqm/serialize.py CHANGED
@@ -90,7 +90,7 @@ def circuit_operation_to_operation(circuit_operation: CircuitOperation) -> Opera
90
90
 
91
91
 
92
92
  class OperationNotSupportedError(RuntimeError):
93
- """Raised when a given operation is not supported by the IQM server."""
93
+ """Raised when a given operation is not supported by the IQM Server."""
94
94
 
95
95
 
96
96
  def operation_to_circuit_operation(operation: Operation) -> CircuitOperation:
@@ -21,6 +21,8 @@ from cirq import Circuit
21
21
  from iqm.cirq_iqm.serialize import deserialize_circuit, serialize_circuit
22
22
  from iqm.iqm_client import ExistingMoveHandlingOptions, transpile_insert_moves
23
23
 
24
+ from iqm.station_control.interface.models import QubitMapping
25
+
24
26
  if TYPE_CHECKING:
25
27
  from iqm.cirq_iqm.devices import IQMDevice
26
28
 
@@ -29,7 +31,7 @@ def transpile_insert_moves_into_circuit(
29
31
  cirq_circuit: Circuit,
30
32
  device: IQMDevice,
31
33
  existing_moves: ExistingMoveHandlingOptions = ExistingMoveHandlingOptions.KEEP,
32
- qubit_mapping: dict[str, str] | None = None,
34
+ qubit_mapping: QubitMapping | None = None,
33
35
  ) -> Circuit:
34
36
  """Transpile the circuit to insert MOVE gates where needed.
35
37
 
@@ -17,8 +17,6 @@ from importlib.metadata import PackageNotFoundError, version
17
17
  import sys
18
18
  import warnings
19
19
 
20
- from iqm.iqm_client.api import * # noqa: F403
21
- from iqm.iqm_client.authentication import * # noqa: F403
22
20
  from iqm.iqm_client.errors import * # noqa: F403
23
21
  from iqm.iqm_client.iqm_client import * # noqa: F403
24
22
  from iqm.iqm_client.models import * # noqa: F403
iqm/iqm_client/errors.py CHANGED
@@ -14,14 +14,6 @@
14
14
  """This module contains error classes required by IQMClient."""
15
15
 
16
16
 
17
- class ClientAuthenticationError(RuntimeError):
18
- """Something went wrong with user authentication."""
19
-
20
-
21
- class ClientConfigurationError(RuntimeError):
22
- """Wrong configuration provided."""
23
-
24
-
25
17
  class CircuitValidationError(RuntimeError):
26
18
  """Circuit validation failed."""
27
19
 
@@ -31,16 +23,13 @@ class CircuitTranspilationError(RuntimeError):
31
23
 
32
24
 
33
25
  class CircuitExecutionError(RuntimeError):
34
- """Something went wrong on the server."""
26
+ """Something went wrong on the server.
27
+
28
+ Should be used in sync calls that must return valid circuit execution results.
29
+ Async calls may instead return a job object that indicates the
30
+ excution did not succeed without raising anything.
31
+ """
35
32
 
36
33
 
37
34
  class APITimeoutError(CircuitExecutionError):
38
35
  """Executing a job on the server took too long."""
39
-
40
-
41
- class JobAbortionError(RuntimeError):
42
- """Job abortion failed."""
43
-
44
-
45
- class EndpointRequestError(RuntimeError):
46
- """Retrieving something from a server endpoint failed because we did not understand the response."""