iqm-pulla 11.0.0__tar.gz → 11.2.0__tar.gz

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 (75) hide show
  1. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/CHANGELOG.rst +16 -0
  2. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/PKG-INFO +1 -1
  3. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/Quick Start.ipynb +10 -0
  4. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/cpc/__init__.py +2 -19
  5. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/cpc/compiler/compiler.py +70 -20
  6. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/pulla/pulla.py +113 -53
  7. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm_pulla.egg-info/PKG-INFO +1 -1
  8. iqm_pulla-11.2.0/version.txt +1 -0
  9. iqm_pulla-11.0.0/version.txt +0 -1
  10. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/AUTHORS.rst +0 -0
  11. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/LICENSE.txt +0 -0
  12. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/MANIFEST.in +0 -0
  13. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/README.rst +0 -0
  14. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/API.rst +0 -0
  15. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/Compilation Stages.ipynb +0 -0
  16. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/Configuration and Usage.ipynb +0 -0
  17. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/Custom Gates and Implementations.ipynb +0 -0
  18. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/Example - Compilation With Local Calibration Set.ipynb +0 -0
  19. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/Example - Executing QIR programs.ipynb +0 -0
  20. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/Example - Measuring T1.ipynb +0 -0
  21. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/Example - Randomized Benchmarking.ipynb +0 -0
  22. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/Example - Simple Dynamical Decoupling.ipynb +0 -0
  23. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/_static/images/favicon.ico +0 -0
  24. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/_static/images/logo.png +0 -0
  25. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/_templates/autosummary-class-template.rst +0 -0
  26. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/_templates/autosummary-module-template.rst +0 -0
  27. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/authors.rst +0 -0
  28. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/changelog.rst +0 -0
  29. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/common_errors.rst +0 -0
  30. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/conf.py +0 -0
  31. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/examples.rst +0 -0
  32. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/index.rst +0 -0
  33. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/license.rst +0 -0
  34. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/migration_guide.rst +0 -0
  35. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/readme.rst +0 -0
  36. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/references.bib +0 -0
  37. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/references.rst +0 -0
  38. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/docs/user_guides.rst +0 -0
  39. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/pyproject.toml +0 -0
  40. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/requirements/base.in +0 -0
  41. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/requirements/base.in.internal +0 -0
  42. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/requirements/base.txt +0 -0
  43. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/requirements/notebook.in +0 -0
  44. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/requirements/notebook.txt +0 -0
  45. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/requirements/qir.in +0 -0
  46. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/requirements/qir.txt +0 -0
  47. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/requirements/qiskit.in.internal +0 -0
  48. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/requirements/qiskit.txt +0 -0
  49. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/setup.cfg +0 -0
  50. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/setup.py +0 -0
  51. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/cpc/compiler/__init__.py +0 -0
  52. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/cpc/compiler/dd.py +0 -0
  53. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/cpc/compiler/errors.py +0 -0
  54. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/cpc/compiler/standard_stages.py +0 -0
  55. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/cpc/compiler/station_settings.py +0 -0
  56. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/cpc/interface/__init__.py +0 -0
  57. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/cpc/interface/compiler.py +0 -0
  58. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/cpc/py.typed +0 -0
  59. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/pulla/__init__.py +0 -0
  60. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/pulla/calibration.py +0 -0
  61. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/pulla/interface.py +0 -0
  62. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/pulla/py.typed +0 -0
  63. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/pulla/quantum_architecture.py +0 -0
  64. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/pulla/utils.py +0 -0
  65. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/pulla/utils_cirq.py +0 -0
  66. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/pulla/utils_dd.py +0 -0
  67. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/pulla/utils_qir.py +0 -0
  68. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm/pulla/utils_qiskit.py +0 -0
  69. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm_pulla.egg-info/SOURCES.txt +0 -0
  70. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm_pulla.egg-info/dependency_links.txt +0 -0
  71. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm_pulla.egg-info/requires.txt +0 -0
  72. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/src/iqm_pulla.egg-info/top_level.txt +0 -0
  73. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/tests/.pylintrc +0 -0
  74. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/tests/__init__.py +0 -0
  75. {iqm_pulla-11.0.0 → iqm_pulla-11.2.0}/tests/conftest.py +0 -0
@@ -2,6 +2,22 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Version 11.2.0 (2025-09-12)
6
+ ===========================
7
+
8
+ Features
9
+ --------
10
+
11
+ - Allow executing jobs in async fashion
12
+
13
+ Version 11.1.0 (2025-09-12)
14
+ ===========================
15
+
16
+ Features
17
+ --------
18
+
19
+ - Postprocessing stages.
20
+
5
21
  Version 11.0.0 (2025-09-11)
6
22
  ===========================
7
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulla
3
- Version: 11.0.0
3
+ Version: 11.2.0
4
4
  Summary: Client library for pulse-level access to an IQM quantum computer
5
5
  Author-email: IQM Finland Oy <developers@meetiqm.com>
6
6
  License: Apache License
@@ -388,6 +388,16 @@
388
388
  "print(f\"Qiskit result counts:\\n{qiskit_result.get_counts()}\\n\")"
389
389
  ]
390
390
  },
391
+ {
392
+ "cell_type": "markdown",
393
+ "id": "1778cf4c",
394
+ "metadata": {},
395
+ "source": [
396
+ "By default, `execute()` submits the job and waits for its completion by polling the server every second, until interrupted. If you pass `wait_completion=False`, then the method will terminate immediately after submitting the job and return a `StationControlResult` object with the job id in `PENDING` status. \n",
397
+ "\n",
398
+ "You can then manually retrieve job results by calling `get_execution_result()`. It will also, by default, wait for the job to complete until interrupted. If you pass `wait_completion=False`, then `get_execution_result()` will query the server only once and return the current state; it is then up to you to call `get_execution_result()` until the job is completed."
399
+ ]
400
+ },
391
401
  {
392
402
  "cell_type": "markdown",
393
403
  "id": "2014d3e7",
@@ -14,26 +14,9 @@
14
14
  """IQM Circuit to Pulse Compiler.
15
15
 
16
16
  IQM Circuit to Pulse Compiler is a Python-based library for converting quantum circuits
17
- into :class:`instruction schedules <exa.pulse.pulse_schedule.PulseSchedule>`
18
- (which map ``Station Control`` controller names to their pulse playlists) and Station Control settings
17
+ into :class:`instruction schedules <iqm.pulse.playlist.schedule.Schedule>`
18
+ (which map ``Station Control`` controller names to lists of hardware instructions) and Station Control settings
19
19
  required for circuit execution, using the calibration data it is given.
20
20
  The generated schedules and settings can be sent to Station Control
21
21
  for execution on real or simulated quantum hardware.
22
-
23
- CPC is normally only accessed indirectly through Cocos or its reference client
24
- `IQM client <https://docs.meetiqm.com/iqm-client/index.html>`_,
25
- or a frontend such as
26
- `Cirq on IQM <https://docs.meetiqm.com/iqm-client/user_guide_cirq.html>`_ or
27
- `Qiskit on IQM <https://docs.meetiqm.com/iqm-client/user_guide_qiskit.html>`_.
28
22
  """
29
-
30
- # from importlib.metadata import PackageNotFoundError, version
31
-
32
- # try:
33
- # # Change here if project is renamed and does not equal the package name
34
- # DIST_NAME = "iqm-cocos"
35
- # __version__ = version(DIST_NAME)
36
- # except PackageNotFoundError: # pragma: no cover
37
- # __version__ = "unknown"
38
- # finally:
39
- # del version, PackageNotFoundError
@@ -20,7 +20,7 @@ can be executed by the IQM server on quantum hardware.
20
20
 
21
21
  from __future__ import annotations
22
22
 
23
- from collections.abc import Callable, Iterable
23
+ from collections.abc import Callable, Collection, Iterable
24
24
  from copy import deepcopy
25
25
  import functools
26
26
  import inspect
@@ -28,8 +28,10 @@ import logging
28
28
  from typing import TYPE_CHECKING, Any
29
29
 
30
30
  from opentelemetry import trace
31
+ from typing_extensions import deprecated
31
32
 
32
33
  from exa.common.data.setting_node import SettingNode
34
+ from exa.common.helpers.deprecation import format_deprecated
33
35
  from iqm.cpc.compiler.errors import CalibrationError, ClientError, InsufficientContextError
34
36
  from iqm.cpc.interface.compiler import (
35
37
  CircuitBoundaryMode,
@@ -187,13 +189,16 @@ class Compiler:
187
189
  Args:
188
190
  calibration_set: Calibration data.
189
191
  chip_topology: Physical layout and connectivity of the quantum chip.
190
- channel_properties: Channel properties.
191
- component_channels: Mapping between components and their control channels.
192
- component_mapping: Custom mapping of components. Defaults to None.
192
+ channel_properties: Control channel properties for the station.
193
+ component_channels: Mapping from QPU component name to a mapping from ``('drive', 'flux', 'readout')``
194
+ to the name of the control channel responsible for that function of the component.
195
+ component_mapping: Mapping of logical QPU component names to physical QPU component names.
196
+ ``None`` means the identity mapping.
193
197
  options: Circuit execution options.
194
198
  Defaults to STANDARD_CIRCUIT_EXECUTION_OPTIONS.
195
- stages: List of compilation stages. Defaults to None.
196
- Note that in the absence of stages, the compiler will not be ready to compile circuits.
199
+ stages: Compilation stages to use. ``None`` means none.
200
+ Note that meaningful circuit compilation requires at least some stages.
201
+ pp_stages: Post-processing stages to use. ``None`` means none.
197
202
  strict: If True, raises CalibrationError on calibration validation failures.
198
203
  If False, only logs warnings. Defaults to False.
199
204
 
@@ -202,7 +207,7 @@ class Compiler:
202
207
 
203
208
  """
204
209
 
205
- def __init__(
210
+ def __init__( # noqa: PLR0913
206
211
  self,
207
212
  *,
208
213
  calibration_set: CalibrationSet,
@@ -211,13 +216,15 @@ class Compiler:
211
216
  component_channels: dict[str, dict[str, str]],
212
217
  component_mapping: dict[str, str] | None = None,
213
218
  options: CircuitExecutionOptions = STANDARD_CIRCUIT_EXECUTION_OPTIONS,
214
- stages: list[CompilationStage] | None = None,
219
+ stages: Collection[CompilationStage] | None = None,
220
+ pp_stages: Collection[CompilationStage] | None = None,
215
221
  strict: bool = False, # consider extending to e.g. errors: Literal["raise", "warning", "ignore"] = "warning"
216
222
  ):
217
223
  self._calibration_set = calibration_set
218
224
  self.component_mapping = component_mapping
219
225
  self.options = options
220
226
  self.stages = stages or []
227
+ self.pp_stages = pp_stages or []
221
228
 
222
229
  self.builder: ScheduleBuilder = initialize_schedule_builder(
223
230
  calibration_set, chip_topology, channel_properties, component_channels
@@ -357,11 +364,12 @@ class Compiler:
357
364
  )
358
365
  self._refresh()
359
366
 
367
+ @deprecated(format_deprecated(old="`ready`", new=None, since="12.08.2025"))
360
368
  def ready(self) -> bool:
361
369
  """Check if the compiler is ready to compile circuits. The compiler is ready if at least one stage is defined, and
362
370
  all the stages are non-empty.
363
371
  """ # noqa: E501
364
- if len(self.stages) == 0:
372
+ if not self.stages:
365
373
  return False
366
374
  for stage in self.stages:
367
375
  if not stage.ready():
@@ -394,7 +402,7 @@ class Compiler:
394
402
  full: Iff True, also print the docstring of each pass function.
395
403
 
396
404
  """
397
- if len(self.stages) == 0:
405
+ if not self.stages:
398
406
  print("No stages defined.")
399
407
  return
400
408
 
@@ -409,7 +417,10 @@ class Compiler:
409
417
  print()
410
418
 
411
419
  def compiler_context(self) -> dict[str, Any]:
412
- """Return initial compiler context dictionary. Used automatically by :meth:`Compiler.compile`."""
420
+ """Return initial compiler context dictionary.
421
+
422
+ Used automatically by :meth:`compile`.
423
+ """
413
424
  return {
414
425
  "calibration_set": self._calibration_set,
415
426
  "builder": self.builder,
@@ -422,27 +433,66 @@ class Compiler:
422
433
  def compile(
423
434
  self, data: Iterable[Any], context: dict[str, Any] | None = None
424
435
  ) -> tuple[Iterable[Any], dict[str, Any]]:
425
- """Run all compiler stages. Initial context will be derived from :meth:`Compiler.compiler_context` unless a custom
436
+ """Run all compiler stages.
437
+
438
+ Initial context will be derived using :meth:`compiler_context` unless a custom
439
+ context dictionary is provided.
440
+
441
+ Args:
442
+ data: Circuits to be compiled.
443
+ context: Custom initial compiler context dictionary.
444
+
445
+ Returns:
446
+ Compiled ``data``, final context.
447
+
448
+ """
449
+ cpc_logger.info("Running compilation stages...")
450
+ return self.run_stages(self.stages, data, context or self.compiler_context())
451
+
452
+ def postprocess(
453
+ self, data: Iterable[Any], context: dict[str, Any] | None = None
454
+ ) -> tuple[Iterable[Any], dict[str, Any]]:
455
+ """Run all post-processing stages.
456
+
457
+ Initial context will be derived using :meth:`compiler_context` unless a custom
426
458
  context dictionary is provided.
427
459
 
428
460
  Args:
429
- data: An iterable of circuits to be compiled.
461
+ data: Any data, e.g. execution results derived from :meth:`Pulla.execute`
430
462
  context: Custom initial compiler context dictionary.
431
463
 
464
+ Returns:
465
+ Postprocessed ``data``, final context.
466
+
432
467
  """ # noqa: E501
433
- if not self.ready():
434
- raise RuntimeError("Compiler is not ready: no stages defined, or some stages have zero passes.")
468
+ cpc_logger.info("Running postprocessing stages...")
469
+ return self.run_stages(self.pp_stages, data, context or self.compiler_context())
470
+
471
+ def run_stages(
472
+ self, stages: Collection[CompilationStage], data: Iterable[Any], context: dict[str, Any]
473
+ ) -> tuple[Iterable[Any], dict[str, Any]]:
474
+ """Run the given stages in given order on the given data.
475
+
476
+ Args:
477
+ stages: Stages to run on ``data``.
478
+ data: The data to be processed.
479
+ context: Additional information that is passed to the first stage.
480
+ Each stage may make modifications to ``context`` before it is passed to the next stage.
435
481
 
436
- # Dictionary of context to be passed through all the passes
437
- if context is None:
438
- context = self.compiler_context()
482
+ Returns:
483
+ Processed data, final context.
439
484
 
440
- # Run the stages
485
+ """
486
+ if not stages:
487
+ raise RuntimeError("No stages defined.")
441
488
  for stage in self.stages:
489
+ if not stage.ready():
490
+ raise RuntimeError(f"Stage {stage.name} is not ready.")
491
+
492
+ for stage in stages:
442
493
  cpc_logger.info('Running stage "%s"...', stage.name)
443
494
  data, context = stage.run(data, context)
444
495
 
445
- cpc_logger.info("Compilation finished.")
446
496
  return data, context
447
497
 
448
498
  def build_settings(self, context: dict[str, Any], shots: int) -> tuple[SettingNode, dict[str, Any]]:
@@ -210,6 +210,7 @@ class Pulla:
210
210
  context: dict[str, Any],
211
211
  settings: SettingNode,
212
212
  verbose: bool = True,
213
+ wait_completion: bool = True,
213
214
  ) -> StationControlResult:
214
215
  """Executes a quantum circuit on the remote quantum computer.
215
216
 
@@ -218,6 +219,7 @@ class Pulla:
218
219
  context: Context object of the successful compiler run, containing the readout mappings.
219
220
  settings: Station settings.
220
221
  verbose: Whether to print results.
222
+ wait_completion: If True, returns immediately with job ID. If False, waits for completion.
221
223
 
222
224
  Returns:
223
225
  results of the execution
@@ -240,72 +242,130 @@ class Pulla:
240
242
  )
241
243
  )
242
244
  job_id = uuid.UUID(sweep_response["job_id"])
243
- try:
244
- logger.info("Created job in queue with ID: %s", job_id)
245
- if href := sweep_response.get("job_href"):
246
- logger.info("Job link: %s", href)
247
245
 
248
- logger.info("Waiting for the job to finish...")
246
+ logger.info("Created job in queue with ID: %s", job_id)
247
+ if href := sweep_response.get("job_href"):
248
+ logger.info("Job link: %s", href)
249
249
 
250
- while True:
251
- job_data = self._station_control.get_job(job_id)
252
- sc_result = StationControlResult(sweep_id=job_id, task_id=job_id, status=TaskStatus.PENDING)
250
+ if wait_completion:
251
+ return self.get_execution_result(job_id, context, verbose, wait_completion=True)
252
+ else:
253
+ return StationControlResult(sweep_id=job_id, task_id=job_id, status=TaskStatus.PENDING)
253
254
 
254
- if job_data.job_status <= JobExecutorStatus.EXECUTION_STARTED: # type: ignore[operator]
255
- # Wait in the task queue while showing a progress bar
255
+ def get_execution_result(
256
+ self,
257
+ job_id: uuid.UUID,
258
+ context: dict[str, Any],
259
+ verbose: bool = True,
260
+ wait_completion: bool = True,
261
+ ) -> StationControlResult:
262
+ """Get execution results.
256
263
 
257
- interrupted = self._station_control._wait_job_completion(str(job_id), get_progress_bar_callback()) # type: ignore[attr-defined]
258
- if interrupted:
259
- raise KeyboardInterrupt
260
- else:
261
- # job is not in queue or executing, so we can query the sweep
262
- sweep_data = self._station_control.get_sweep(job_id)
263
- if job_data.job_status == JobExecutorStatus.READY:
264
- logger.info("Sweep status: %s", str(sweep_data.job_status))
265
-
266
- sc_result.status = TaskStatus.READY
267
- sc_result.result = map_sweep_results_to_logical_qubits(
268
- self._station_control.get_sweep_results(job_id),
269
- context["readout_mappings"],
270
- context["options"].heralding_mode,
271
- )
272
- sc_result.start_time = (
273
- sweep_data.begin_timestamp.isoformat() if sweep_data.begin_timestamp else None
274
- )
275
- sc_result.end_time = sweep_data.end_timestamp.isoformat() if sweep_data.end_timestamp else None
264
+ Args:
265
+ job_id: The ID of the job to process.
266
+ context: Context object of the successful compiler run, containing the readout mappings.
267
+ verbose: Whether to print results.
268
+ wait_completion: If True, waits for job completion. If False, returns current status.
276
269
 
277
- if verbose:
278
- # TODO: Consider using just 'logger.debug' here and remove 'verbose'
279
- logger.info(sc_result.result)
270
+ Returns:
271
+ The processed station control result.
280
272
 
281
- return sc_result
273
+ """
274
+ sc_result = StationControlResult(sweep_id=job_id, task_id=job_id, status=TaskStatus.PENDING)
282
275
 
283
- if job_data.job_status == JobExecutorStatus.FAILED:
284
- sc_result.status = TaskStatus.FAILED
285
- sc_result.start_time = (
286
- sweep_data.begin_timestamp.isoformat() if sweep_data.begin_timestamp else None
287
- )
288
- sc_result.end_time = sweep_data.end_timestamp.isoformat() if sweep_data.end_timestamp else None
289
- sc_result.message = str(job_data.job_error)
290
- logger.error("Submission failed! Error: %s", sc_result.message)
291
- return sc_result
276
+ try:
277
+ if wait_completion:
278
+ logger.info("Waiting for the job to finish...")
292
279
 
293
- if job_data.job_status == JobExecutorStatus.ABORTED:
294
- sc_result.status = TaskStatus.FAILED
295
- sc_result.start_time = (
296
- sweep_data.begin_timestamp.isoformat() if sweep_data.begin_timestamp else None
280
+ while True:
281
+ job_data = self._station_control.get_job(job_id)
282
+ sc_result.status = TaskStatus.PENDING
283
+
284
+ if job_data.job_status <= JobExecutorStatus.EXECUTION_STARTED: # type: ignore[operator]
285
+ if wait_completion:
286
+ # Wait in the task queue while showing a progress bar
287
+ interrupted = self._station_control._wait_job_completion( # type: ignore[attr-defined]
288
+ str(job_id), get_progress_bar_callback()
289
+ )
290
+ if interrupted:
291
+ raise KeyboardInterrupt
292
+ else:
293
+ # Non-blocking check - return current status
294
+ sc_result.status = (
295
+ TaskStatus.PROGRESS
296
+ if job_data.job_status == JobExecutorStatus.EXECUTION_STARTED
297
+ else TaskStatus.PENDING
297
298
  )
298
- sc_result.end_time = sweep_data.end_timestamp.isoformat() if sweep_data.end_timestamp else None
299
- sc_result.message = str(job_data.job_error)
300
- logger.error("Submission was revoked!")
301
299
  return sc_result
302
-
303
- time.sleep(1)
300
+ else:
301
+ # job is not in queue or executing, so we can query the sweep
302
+ result_or_nothing = self._get_result_of_started_job(
303
+ context, job_data, job_id, sc_result, wait_completion, verbose
304
+ )
305
+ if result_or_nothing is not None:
306
+ return result_or_nothing
307
+
308
+ if wait_completion:
309
+ time.sleep(1)
310
+ else:
311
+ break
304
312
 
305
313
  except KeyboardInterrupt as exc:
306
- logger.info("Caught KeyboardInterrupt, revoking job %s", job_id)
307
- self._station_control.abort_job(job_id)
314
+ if wait_completion:
315
+ logger.info("Caught KeyboardInterrupt, revoking job %s", job_id)
316
+ self._station_control.abort_job(job_id)
308
317
  raise KeyboardInterrupt from exc
309
318
 
319
+ return sc_result
320
+
321
+ def _get_result_of_started_job(
322
+ self,
323
+ context: dict[str, Any],
324
+ job_data: Any,
325
+ job_id: uuid.UUID,
326
+ sc_result: StationControlResult,
327
+ wait_completion: bool,
328
+ verbose: bool,
329
+ ) -> StationControlResult | None:
330
+ sweep_data = self._station_control.get_sweep(job_id)
331
+ if job_data.job_status == JobExecutorStatus.READY:
332
+ if wait_completion:
333
+ logger.info("Sweep status: %s", str(sweep_data.job_status))
334
+
335
+ sc_result.status = TaskStatus.READY
336
+ sc_result.result = map_sweep_results_to_logical_qubits(
337
+ self._station_control.get_sweep_results(job_id),
338
+ context["readout_mappings"],
339
+ context["options"].heralding_mode,
340
+ )
341
+ sc_result.start_time = sweep_data.begin_timestamp.isoformat() if sweep_data.begin_timestamp else None
342
+ sc_result.end_time = sweep_data.end_timestamp.isoformat() if sweep_data.end_timestamp else None
343
+
344
+ if verbose and wait_completion:
345
+ # TODO: Consider using just 'logger.debug' here and remove 'verbose'
346
+ logger.info(sc_result.result)
347
+
348
+ return sc_result
349
+
350
+ if job_data.job_status == JobExecutorStatus.FAILED:
351
+ sc_result.status = TaskStatus.FAILED
352
+ sc_result.start_time = sweep_data.begin_timestamp.isoformat() if sweep_data.begin_timestamp else None
353
+ sc_result.end_time = sweep_data.end_timestamp.isoformat() if sweep_data.end_timestamp else None
354
+ sc_result.message = str(job_data.job_error)
355
+ if wait_completion:
356
+ logger.error("Submission failed! Error: %s", sc_result.message)
357
+ return sc_result
358
+
359
+ if job_data.job_status == JobExecutorStatus.ABORTED:
360
+ sc_result.status = TaskStatus.FAILED
361
+ sc_result.start_time = sweep_data.begin_timestamp.isoformat() if sweep_data.begin_timestamp else None
362
+ sc_result.end_time = sweep_data.end_timestamp.isoformat() if sweep_data.end_timestamp else None
363
+ sc_result.message = str(job_data.job_error)
364
+ if wait_completion:
365
+ logger.error("Submission was revoked!")
366
+ return sc_result
367
+
368
+ return None
369
+
310
370
 
311
371
  init_loggers({"iqm": "INFO"})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulla
3
- Version: 11.0.0
3
+ Version: 11.2.0
4
4
  Summary: Client library for pulse-level access to an IQM quantum computer
5
5
  Author-email: IQM Finland Oy <developers@meetiqm.com>
6
6
  License: Apache License
@@ -0,0 +1 @@
1
+ 11.2.0
@@ -1 +0,0 @@
1
- 11.0.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes