iqm-exa-common 26.25.0__tar.gz → 26.26.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 (81) hide show
  1. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/CHANGELOG.rst +8 -0
  2. {iqm_exa_common-26.25.0/src/iqm_exa_common.egg-info → iqm_exa_common-26.26.0}/PKG-INFO +1 -1
  3. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/option/center_span_options.py +1 -1
  4. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/option/start_stop_base_options.py +5 -9
  5. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/option/start_stop_options.py +1 -1
  6. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/sweep_values.py +3 -4
  7. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/data/base_model.py +2 -1
  8. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/data/parameter.py +19 -21
  9. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/data/setting_node.py +15 -15
  10. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/helpers/data_helper.py +4 -2
  11. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/helpers/software_version_helper.py +1 -1
  12. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/qcm_data/chad_model.py +3 -2
  13. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/qcm_data/chip_topology.py +7 -7
  14. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/qcm_data/qcm_data_client.py +1 -1
  15. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0/src/iqm_exa_common.egg-info}/PKG-INFO +1 -1
  16. iqm_exa_common-26.26.0/version.txt +1 -0
  17. iqm_exa_common-26.25.0/version.txt +0 -1
  18. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/LICENSE.txt +0 -0
  19. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/MANIFEST.in +0 -0
  20. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/README.rst +0 -0
  21. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/API.rst +0 -0
  22. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/Makefile +0 -0
  23. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/_static/.gitignore +0 -0
  24. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/_static/css/custom.css +0 -0
  25. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/_static/images/favicon.ico +0 -0
  26. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/_static/images/logo.png +0 -0
  27. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/_templates/autosummary-class-template.rst +0 -0
  28. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/_templates/autosummary-module-template.rst +0 -0
  29. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/changelog.rst +0 -0
  30. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/conf.py +0 -0
  31. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/index.rst +0 -0
  32. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/docs/license.rst +0 -0
  33. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/pyproject.toml +0 -0
  34. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/requirements/base.in +0 -0
  35. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/requirements/base.txt +0 -0
  36. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/setup.cfg +0 -0
  37. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/setup.py +0 -0
  38. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/__init__.py +0 -0
  39. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/api/__init__.py +0 -0
  40. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/api/proto_serialization/__init__.py +0 -0
  41. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/api/proto_serialization/_parameter.py +0 -0
  42. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/api/proto_serialization/array.py +0 -0
  43. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/api/proto_serialization/datum.py +0 -0
  44. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/api/proto_serialization/nd_sweep.py +0 -0
  45. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/api/proto_serialization/sequence.py +0 -0
  46. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/api/proto_serialization/setting_node.py +0 -0
  47. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/__init__.py +0 -0
  48. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/__init__.py +0 -0
  49. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/exponential_sweep.py +0 -0
  50. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/fixed_sweep.py +0 -0
  51. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/linear_sweep.py +0 -0
  52. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/option/__init__.py +0 -0
  53. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/option/center_span_base_options.py +0 -0
  54. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/option/constants.py +0 -0
  55. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/option/fixed_options.py +0 -0
  56. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/option/option_converter.py +0 -0
  57. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/option/sweep_options.py +0 -0
  58. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/control/sweep/sweep.py +0 -0
  59. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/data/__init__.py +0 -0
  60. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/data/settingnode_v2.html.jinja2 +0 -0
  61. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/data/value.py +0 -0
  62. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/errors/__init__.py +0 -0
  63. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/errors/exa_error.py +0 -0
  64. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/errors/station_control_errors.py +0 -0
  65. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/helpers/__init__.py +0 -0
  66. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/helpers/deprecation.py +0 -0
  67. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/helpers/json_helper.py +0 -0
  68. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/helpers/numpy_helper.py +0 -0
  69. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/helpers/yaml_helper.py +0 -0
  70. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/logger/__init__.py +0 -0
  71. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/logger/logger.py +0 -0
  72. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/qcm_data/__init__.py +0 -0
  73. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/qcm_data/file_adapter.py +0 -0
  74. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/qcm_data/immutable_base_model.py +0 -0
  75. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/sweep/__init__.py +0 -0
  76. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/sweep/database_serialization.py +0 -0
  77. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/exa/common/sweep/util.py +0 -0
  78. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/iqm_exa_common.egg-info/SOURCES.txt +0 -0
  79. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/iqm_exa_common.egg-info/dependency_links.txt +0 -0
  80. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/iqm_exa_common.egg-info/requires.txt +0 -0
  81. {iqm_exa_common-26.25.0 → iqm_exa_common-26.26.0}/src/iqm_exa_common.egg-info/top_level.txt +0 -0
@@ -2,6 +2,14 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Version 26.26.0 (2025-07-02)
6
+ ============================
7
+
8
+ Bug fixes
9
+ ---------
10
+
11
+ - Fix type errors raised by mypy.
12
+
5
13
  Version 26.25.0 (2025-06-17)
6
14
  ============================
7
15
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-exa-common
3
- Version: 26.25.0
3
+ Version: 26.26.0
4
4
  Summary: Framework for control and measurement of superconducting qubits: common library
5
5
  Author-email: IQM Finland Oy <info@meetiqm.com>
6
6
  License: Apache License
@@ -40,7 +40,7 @@ class CenterSpanOptions(SweepOptions):
40
40
  #: :const:`exa.common.control.sweep.option.constants.DEFAULT_COUNT`.
41
41
  count: int | None = None
42
42
  #: Size of spacing between values.
43
- step: int | float | complex = None
43
+ step: int | float | complex | None = None
44
44
  #: Order of generated values. Default to ascending
45
45
  asc: bool | None = None
46
46
 
@@ -35,26 +35,22 @@ class StartStopBaseOptions(SweepOptions):
35
35
  """
36
36
 
37
37
  #: The power for the start of the interval.
38
- start: int | float | complex
38
+ start: int | float
39
39
  #: The power for the end of the interval.
40
- stop: int | float | complex
40
+ stop: int | float
41
41
  #: Number of values to generate. Default to
42
42
  #: :const:`exa.common.control.sweep.option.constants.DEFAULT_COUNT`.
43
- count: int | None = None
43
+ count: int = DEFAULT_COUNT
44
44
  #: Number, that is raised to the power `start` or `stop`. Default to
45
45
  #: :const:`exa.common.control.sweep.option.constants.DEFAULT_BASE`.
46
- base: int | float | None = None
46
+ base: int = DEFAULT_BASE
47
47
 
48
48
  def __post_init__(self):
49
- if self.count is None:
50
- object.__setattr__(self, "count", DEFAULT_COUNT)
51
- if self.base is None:
52
- object.__setattr__(self, "base", DEFAULT_BASE)
53
49
  if self.start == 0 or self.stop == 0:
54
50
  raise ValueError("Exponential range sweep start and stop values must not be zero.")
55
51
 
56
52
  @property
57
- def data(self) -> list[int | float | complex]:
53
+ def data(self) -> list[int | float]:
58
54
  logger.debug(f"EXPONENTS: ({self.start}, {self.stop}) with base {self.base}")
59
55
  start = math.pow(self.base, self.start)
60
56
  stop = math.pow(self.base, self.stop)
@@ -65,7 +65,7 @@ class StartStopOptions(SweepOptions):
65
65
  count = 1 + math.ceil(abs(self.stop - self.start) / float(np.abs(self.step)))
66
66
  data = self._generate_by_count(count)
67
67
  else:
68
- data = self._generate_by_count(self.count)
68
+ data = self._generate_by_count(self.count if self.count is not None else DEFAULT_COUNT)
69
69
  return data
70
70
 
71
71
  def _generate_by_count(self, count: int) -> SweepValues:
@@ -28,9 +28,8 @@ def validate_sweep_values(sweep_values: Any) -> Any:
28
28
  if isinstance(sweep_values, np.ndarray):
29
29
  sweep_values = sweep_values.tolist()
30
30
  for index, value in enumerate(sweep_values):
31
- if isinstance(value, dict):
32
- if "__complex__" in value:
33
- sweep_values[index] = complex(value["real"], value["imag"])
31
+ if isinstance(value, dict) and "__complex__" in value:
32
+ sweep_values[index] = complex(value["real"], value["imag"])
34
33
  return sweep_values
35
34
 
36
35
 
@@ -51,7 +50,7 @@ def serialize_sweep_values(sweep_values: Any) -> Any:
51
50
 
52
51
 
53
52
  SweepValues = Annotated[
54
- list[Any] | np.ndarray[Any],
53
+ list[Any] | np.ndarray,
55
54
  PlainValidator(validate_sweep_values),
56
55
  PlainSerializer(serialize_sweep_values),
57
56
  WithJsonSchema(core_schema.any_schema()),
@@ -1,3 +1,4 @@
1
+ from collections.abc import Mapping
1
2
  from typing import Any, Self
2
3
 
3
4
  import pydantic
@@ -24,7 +25,7 @@ class BaseModel(pydantic.BaseModel):
24
25
  frozen=True, # This makes instances of the model potentially hashable if all the attributes are hashable
25
26
  )
26
27
 
27
- def model_copy(self, *, update: dict[str, Any] | None = None, deep: bool = True) -> Self:
28
+ def model_copy(self, *, update: Mapping[str, Any] | None = None, deep: bool = True) -> Self:
28
29
  """Returns a copy of the model.
29
30
 
30
31
  Overrides the Pydantic default 'model_copy' to set 'deep=True' by default.
@@ -116,7 +116,7 @@ class DataType(IntEnum):
116
116
  else:
117
117
  return False
118
118
 
119
- def _cast(self, value: str) -> Any: # noqa: PLR0911
119
+ def _cast(self, value: str | None) -> Any: # noqa: PLR0911
120
120
  if value is None:
121
121
  return None
122
122
  elif self in [DataType.FLOAT, DataType.NUMBER]:
@@ -238,13 +238,12 @@ class Parameter(BaseModel):
238
238
  raise ValidationError("Parameter 'element_indices' must be one or more ints.")
239
239
  object.__setattr__(self, "element_indices", idxs)
240
240
  # there may be len(idxs) num of "__" separated indices at the end, remove those to get the parent name
241
- parent_name = self.name.replace("__" + "__".join([str(idx) for idx in idxs]), "")
241
+ seperated_indices = "__".join([str(idx) for idx in idxs])
242
+ parent_name = self.name.replace("__" + seperated_indices, "")
242
243
  object.__setattr__(self, "_parent_name", parent_name)
243
244
  object.__setattr__(self, "_parent_label", self.label.replace(f" {idxs}", ""))
244
245
  object.__setattr__(self, "label", f"{self._parent_label} {idxs}")
245
- name = self._parent_name
246
- for index in idxs:
247
- name += f"__{index}"
246
+ name = parent_name + "__" + seperated_indices
248
247
  object.__setattr__(self, "name", name)
249
248
 
250
249
  @property
@@ -272,7 +271,7 @@ class Parameter(BaseModel):
272
271
  def build_data_set(
273
272
  variables: list[tuple[Parameter, list[Any]]],
274
273
  data: tuple[Parameter, SweepValues],
275
- attributes: dict[str, Any] = None,
274
+ attributes: dict[str, Any] | None = None,
276
275
  extra_variables: list[tuple[str, int]] | None = None,
277
276
  ):
278
277
  """Build an xarray Dataset, where the only DataArray is given by `results` and coordinates are given by
@@ -289,9 +288,9 @@ class Parameter(BaseModel):
289
288
  extra_variables: Valueless dimensions and their sizes.
290
289
 
291
290
  """
292
- variable_names = []
293
- variable_sizes = []
294
- variable_data_arrays = {}
291
+ variable_names: list[str] = []
292
+ variable_sizes: list[int] = []
293
+ variable_data_arrays: dict[str, xr.DataArray] = {}
295
294
  for variable in variables:
296
295
  variable_names.append(variable[0].name)
297
296
  variable_sizes.append(len(variable[1]))
@@ -326,9 +325,9 @@ class Parameter(BaseModel):
326
325
  def build_data_array(
327
326
  self,
328
327
  data: np.ndarray,
329
- dimensions: list[Hashable] = None,
330
- coords: dict[Hashable, Any] = None,
331
- metadata: dict[str, Any] = None,
328
+ dimensions: list[str] | list[Hashable] | None = None,
329
+ coords: dict[Hashable, Any] | None = None,
330
+ metadata: dict[str, Any] | None = None,
332
331
  ) -> xr.DataArray:
333
332
  """Attach Parameter information to a numerical array.
334
333
 
@@ -365,7 +364,7 @@ class Parameter(BaseModel):
365
364
  da = xr.DataArray(name=self.name, data=data, attrs=attrs, dims=dimensions, coords=coords)
366
365
  # copying the coordinate metadata, if present, to the new DataArray coordinates
367
366
  if coords:
368
- for key in [k for k in coords.keys() if isinstance(coords[k], xr.DataArray)]:
367
+ for key in [k for k in coords if isinstance(coords[k], xr.DataArray)]:
369
368
  da[key].attrs = coords[key].attrs
370
369
  return da
371
370
 
@@ -474,7 +473,7 @@ class Setting(BaseModel):
474
473
  return self.parameter.unit
475
474
 
476
475
  @property
477
- def element_indices(self) -> tuple[int, ...] | None:
476
+ def element_indices(self) -> int | list[int] | None:
478
477
  """Element-wise indices of the parameter in ``self``."""
479
478
  return self.parameter.element_indices
480
479
 
@@ -483,15 +482,14 @@ class Setting(BaseModel):
483
482
  return next((setting for setting in values if setting.parameter.name == name), None)
484
483
 
485
484
  @staticmethod
486
- def remove_by_name(name: str, values: set[Setting] = None) -> set[Setting]:
487
- if values is None:
488
- values = set()
485
+ def remove_by_name(name: str, values: set[Setting]) -> set[Setting]:
489
486
  removed = copy.deepcopy(values)
490
- removed.discard(Setting.get_by_name(name, values))
487
+ if setting := Setting.get_by_name(name, values):
488
+ removed.discard(setting)
491
489
  return removed
492
490
 
493
491
  @staticmethod
494
- def replace(settings: Setting | list[Setting], values: set[Setting] = None) -> set[Setting]:
492
+ def replace(settings: Setting | list[Setting], values: set[Setting] | None = None) -> set[Setting]:
495
493
  if values is None:
496
494
  values = set()
497
495
  if not isinstance(settings, list):
@@ -518,7 +516,7 @@ class Setting(BaseModel):
518
516
  diff = first.difference(second)
519
517
  for s in first.intersection(second):
520
518
  a, b = [Setting.get_by_name(s.parameter.name, group) for group in [first, second]]
521
- if a.value != b.value:
519
+ if a is not None and b is not None and a.value != b.value:
522
520
  diff.add(a)
523
521
  return diff
524
522
 
@@ -555,7 +553,7 @@ class Setting(BaseModel):
555
553
  def __hash__(self):
556
554
  return hash(self.parameter)
557
555
 
558
- def __eq__(self, other: Setting):
556
+ def __eq__(self, other: Any) -> bool:
559
557
  if not (isinstance(other, Setting) and self.parameter == other.parameter):
560
558
  return False
561
559
  if isinstance(self.value, np.ndarray):
@@ -224,12 +224,12 @@ from exa.common.qcm_data.chip_topology import sort_components
224
224
  logger = logging.getLogger(__name__)
225
225
 
226
226
 
227
- def _fix_path_recursive(node: SettingNode | dict, path: str) -> SettingNode | dict:
227
+ def _fix_path_recursive(node: SettingNode, path: str) -> SettingNode:
228
228
  """Recursively travel the settings tree and fix the ``path``attribute (also aligns ``name``,
229
229
  based on the node type). Deep copies all the child nodes.
230
230
  """
231
- settings = {}
232
- subtrees = {}
231
+ settings: dict[str, Setting] = {}
232
+ subtrees: dict[str, SettingNode] = {}
233
233
  for key, setting in node.settings.items():
234
234
  child_path = f"{path}.{key}"
235
235
  update_dict = {"path": child_path}
@@ -305,8 +305,8 @@ class SettingNode(BaseModel):
305
305
  generate_paths: bool = True,
306
306
  **kwargs,
307
307
  ):
308
- settings: dict[str, Any] = settings or {}
309
- subtrees: dict[str, Any] = subtrees or {}
308
+ settings = settings or {}
309
+ subtrees = subtrees or {}
310
310
 
311
311
  for key, child in kwargs.items():
312
312
  if isinstance(child, Setting):
@@ -650,7 +650,7 @@ class SettingNode(BaseModel):
650
650
  append_lines(self, lines, [])
651
651
  print("\n", "\n".join(lines))
652
652
 
653
- def __eq__(self, other: SettingNode):
653
+ def __eq__(self, other: Any) -> bool:
654
654
  return isinstance(other, SettingNode) and (
655
655
  (self.name, self.settings, self.subtrees) == (other.name, other.settings, other.subtrees)
656
656
  )
@@ -708,9 +708,12 @@ class SettingNode(BaseModel):
708
708
  else:
709
709
  self.settings[key] = self.settings[key].update(value)
710
710
 
711
- def setting_with_path_name(self, setting: Setting) -> Setting:
711
+ def setting_with_path_name(self, setting: Setting) -> Setting | None:
712
712
  """Get a copy of a setting with its name replaced with the path name."""
713
- return self.find_by_name(setting.name).with_path_name()
713
+ first_item = self.find_by_name(setting.name)
714
+ if isinstance(first_item, Setting):
715
+ return first_item.with_path_name()
716
+ return None
714
717
 
715
718
  def diff(self, other: SettingNode, *, path: str = "") -> list[str]:
716
719
  """Recursive diff between two SettingNodes.
@@ -888,7 +891,7 @@ class SettingNode(BaseModel):
888
891
  latest_node[fragment] = SettingNode(name=fragment, align_name=latest_node.align_name)
889
892
  latest_node = latest_node[fragment]
890
893
  # finally add the nodes
891
- nodes_to_add = nodes.values() if isinstance(nodes, dict) else nodes
894
+ nodes_to_add: Iterable[Setting | Parameter | SettingNode] = nodes.values() if isinstance(nodes, dict) else nodes
892
895
  nodes_keys = list(nodes.keys()) if isinstance(nodes, dict) else []
893
896
  for idx, node in enumerate(nodes_to_add):
894
897
  key = nodes_keys[idx] if isinstance(nodes, dict) else node.name.split(".")[-1]
@@ -926,10 +929,7 @@ class SettingNode(BaseModel):
926
929
  ):
927
930
  if isinstance(locus, str):
928
931
  locus = locus.split("__")
929
- if gate_settings.symmetric.value:
930
- loci = list(permutations(locus))
931
- else:
932
- loci = [tuple(locus)]
932
+ loci = list(permutations(locus)) if gate_settings.symmetric.value else [tuple(locus)]
933
933
  for permuted_locus in loci:
934
934
  locus_str = "__".join(permuted_locus)
935
935
  if locus_str in gate_settings[impl].override_default_for_loci.value:
@@ -987,7 +987,7 @@ class SettingNode(BaseModel):
987
987
  The locus node (string) paths corresponding to this gate.
988
988
 
989
989
  """
990
- node_paths = []
990
+ node_paths: list[str] = []
991
991
  if "gates" not in self.children or gate not in self.gates.children:
992
992
  return node_paths
993
993
  if implementations is not None:
@@ -1034,7 +1034,7 @@ class SettingNode(BaseModel):
1034
1034
 
1035
1035
  raise ValueError(f"Locus {locus} cannot be found in the gate properties characterization settings.")
1036
1036
 
1037
- def _get_symmetric_loci(self, gate: str, implementation: str, locus: str) -> list[str]:
1037
+ def _get_symmetric_loci(self, gate: str, implementation: str, locus: str | Iterable[str]) -> list[str]:
1038
1038
  if not isinstance(locus, str):
1039
1039
  if self.gate_definitions[gate][implementation].symmetric.value:
1040
1040
  str_loci = ["__".join(sort_components(locus))]
@@ -13,13 +13,15 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
+ from collections.abc import Hashable
17
+
16
18
  import xarray as xr
17
19
 
18
20
  """Helper methods for data manipulation.
19
21
  """
20
22
 
21
23
 
22
- def add_data_array(ds: xr.Dataset, da: xr.DataArray, name: str | None = None) -> xr.Dataset:
24
+ def add_data_array(ds: xr.Dataset, da: xr.DataArray, name: Hashable | None = None) -> xr.Dataset:
23
25
  """Add data array `da` to dataset `ds`.
24
26
 
25
27
  Unlike the default xarray command, preserves metadata of the dataset.
@@ -50,7 +52,7 @@ def add_data_array(ds: xr.Dataset, da: xr.DataArray, name: str | None = None) ->
50
52
  ds[name] = da
51
53
  for key in ds.coords:
52
54
  if attributes.get(key):
53
- ds.coords[key].attrs = attributes.get(key)
55
+ ds.coords[key].attrs = attributes.get(key) # type:ignore[assignment]
54
56
  for key in ds.data_vars:
55
57
  if attributes.get(key):
56
58
  ds.data_vars[key].attrs = attributes[key]
@@ -30,7 +30,7 @@ def _is_editable(pkg_name: str) -> bool:
30
30
  ``importlib.metadata``, so it might break anytime.
31
31
  """
32
32
  dist = distribution(pkg_name)
33
- return dist.files and dist.files[0].name.startswith("__editable__.")
33
+ return dist.files is not None and dist.files[0].name.startswith("__editable__.")
34
34
 
35
35
 
36
36
  def get_all_software_versions(reload_module: bool = False) -> dict[str, str]:
@@ -17,13 +17,14 @@
17
17
  from collections.abc import Collection
18
18
  from functools import cached_property
19
19
  import re
20
+ from typing import Any
20
21
 
21
22
  from pydantic import Field, field_validator
22
23
 
23
24
  from exa.common.qcm_data.immutable_base_model import ImmutableBaseModel
24
25
 
25
26
 
26
- def _natural_sort_key(name: str) -> tuple[int, ...]:
27
+ def _natural_sort_key(name: str) -> tuple[int | str | Any, ...]:
27
28
  return tuple(int(item) if item.isdigit() else item.lower() for item in re.split(r"(\d+)", name))
28
29
 
29
30
 
@@ -76,7 +77,7 @@ class Components(ImmutableBaseModel):
76
77
 
77
78
  @cached_property
78
79
  def all(self) -> dict[str, Component]:
79
- components: tuple[Qubit, Coupler, ProbeLine, Launcher, ComputationalResonator] = (
80
+ components: tuple[Qubit | Coupler | ProbeLine | Launcher | ComputationalResonator, ...] = (
80
81
  self.qubits + self.couplers + self.probe_lines + self.launchers + self.computational_resonators
81
82
  )
82
83
  return {component.name: component for component in components}
@@ -109,7 +109,7 @@ class ChipTopology:
109
109
  coupler: tuple(sort_components(components)) for coupler, components in couplers.items()
110
110
  }
111
111
  """Map from each coupler to all other components it connects to. The values are sorted."""
112
- component_to_couplers = {}
112
+ component_to_couplers: dict = {}
113
113
  for coupler, components in couplers.items():
114
114
  for c in components:
115
115
  component_to_couplers.setdefault(c, set()).add(coupler)
@@ -128,7 +128,7 @@ class ChipTopology:
128
128
  Components without connection to a probe line don't appear.
129
129
  """
130
130
 
131
- self._locus_mappings: dict[Locus, dict[tuple[str, ...]]] = {
131
+ self._locus_mappings: dict[str, dict[Locus, tuple[str, ...]]] = {
132
132
  DEFAULT_1QB_MAPPING: {(qubit,): (qubit,) for qubit in self.qubits_sorted},
133
133
  DEFAULT_2QB_MAPPING: {
134
134
  frozenset(comps): (coupler,)
@@ -244,7 +244,7 @@ class ChipTopology:
244
244
  """Get probelines that are connected to any of the given components."""
245
245
  return {self.component_to_probe_line[c] for c in components if c in self.component_to_probe_line}
246
246
 
247
- def get_connected_coupler_map(self, components: Collection[str]) -> ComponentMap:
247
+ def get_connected_coupler_map(self, components: Collection[str]) -> dict[str, tuple[str, ...]]:
248
248
  """Returns a `ComponentMap`, including only the couplers between components that both are in the given subset.
249
249
 
250
250
  Args:
@@ -261,7 +261,7 @@ class ChipTopology:
261
261
  }
262
262
 
263
263
  @staticmethod
264
- def limit_values(dct: ComponentMap, limit_to: Collection[str]) -> ComponentMap:
264
+ def limit_values(dct: ComponentMap, limit_to: Collection[str]) -> dict[str, Collection[str]]:
265
265
  """Prunes the given dictionary (e.g. a coupler-to-qubits map) to a subset of values.
266
266
 
267
267
  Used to prune e.g. :attr:`coupler_to_components` to a subset of relevant elements.
@@ -313,7 +313,7 @@ class ChipTopology:
313
313
  self._validate_locus_mapping(mapping)
314
314
  self._locus_mappings[name] = mapping
315
315
 
316
- def _validate_locus_mapping(self, mapping: dict[str | tuple[str], Locus] | None = None) -> None:
316
+ def _validate_locus_mapping(self, mapping: dict[Locus, tuple[str, ...]]) -> None:
317
317
  """Validate that the components given in mapping are found in self and the mapping is correctly formed."""
318
318
  for locus, mapped in mapping.items():
319
319
  if not isinstance(locus, tuple) and not isinstance(locus, frozenset):
@@ -325,7 +325,7 @@ class ChipTopology:
325
325
  if locus_component not in self.all_components:
326
326
  raise ValueError(f"Locus component {locus_component} is not found in this ChipTopology.")
327
327
 
328
- def map_locus(self, locus: Locus, name: str | None = None) -> str | tuple[str] | None:
328
+ def map_locus(self, locus: Locus, name: str | None = None) -> str | tuple[str, ...] | None:
329
329
  """Returns the mapped components for the given locus and the given gate.
330
330
 
331
331
  If the locus or the gate is not found from the locus mappings of self, returns None.
@@ -388,7 +388,7 @@ class ChipTopology:
388
388
  name = DEFAULT_1QB_MAPPING
389
389
  elif default_mapping_dimension == 2:
390
390
  name = DEFAULT_2QB_MAPPING
391
- return [locus for locus in self._locus_mappings.get(name, {})]
391
+ return list(self._locus_mappings.get(name, {}))
392
392
 
393
393
  def get_common_computational_resonator(self, first_qubit: str, second_qubit: str) -> str:
394
394
  """Convenience method for getting the name of a computational resonator which is connected to both specified
@@ -62,7 +62,7 @@ class QCMDataClient:
62
62
  # Make the cache containers local to the instances so that the reference from cache to the instance
63
63
  # gets scraped off with the instance
64
64
  # https://rednafi.github.io/reflections/dont-wrap-instance-methods-with-functoolslru_cache-decorator-in-python.html
65
- self._send_request = cache(self._send_request)
65
+ self._send_request = cache(self._send_request) # type:ignore[method-assign]
66
66
 
67
67
  @property
68
68
  def root_url(self) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-exa-common
3
- Version: 26.25.0
3
+ Version: 26.26.0
4
4
  Summary: Framework for control and measurement of superconducting qubits: common library
5
5
  Author-email: IQM Finland Oy <info@meetiqm.com>
6
6
  License: Apache License
@@ -0,0 +1 @@
1
+ 26.26.0
@@ -1 +0,0 @@
1
- 26.25.0