iqm-pulse 10.3.0__tar.gz → 10.4.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 (82) hide show
  1. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/CHANGELOG.rst +9 -0
  2. {iqm_pulse-10.3.0/src/iqm_pulse.egg-info → iqm_pulse-10.4.0}/PKG-INFO +1 -1
  3. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/builder.py +32 -10
  4. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/__init__.py +2 -2
  5. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/default_gates.py +37 -23
  6. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0/src/iqm_pulse.egg-info}/PKG-INFO +1 -1
  7. iqm_pulse-10.4.0/version.txt +1 -0
  8. iqm_pulse-10.3.0/version.txt +0 -1
  9. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/LICENSE.txt +0 -0
  10. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/MANIFEST.in +0 -0
  11. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/README.rst +0 -0
  12. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/API.rst +0 -0
  13. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/Makefile +0 -0
  14. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/_static/.gitignore +0 -0
  15. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/_static/css/custom.css +0 -0
  16. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/_static/images/favicon.ico +0 -0
  17. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/_static/images/feedback_timing.svg +0 -0
  18. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/_static/images/logo.png +0 -0
  19. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/_static/images/playlist_breakdown.svg +0 -0
  20. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/_static/images/pulse_timing.svg +0 -0
  21. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/_static/images/readout_timing.svg +0 -0
  22. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/_templates/autosummary-class-template.rst +0 -0
  23. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/_templates/autosummary-module-template.rst +0 -0
  24. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/changelog.rst +0 -0
  25. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/concepts.rst +0 -0
  26. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/conf.py +0 -0
  27. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/custom_gates.rst +0 -0
  28. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/index.rst +0 -0
  29. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/license.rst +0 -0
  30. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/pulse_timing.rst +0 -0
  31. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/references.bib +0 -0
  32. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/references.rst +0 -0
  33. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/docs/using_builder.rst +0 -0
  34. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/pyproject.toml +0 -0
  35. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/requirements/base.in +0 -0
  36. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/requirements/base.txt +0 -0
  37. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/setup.cfg +0 -0
  38. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/setup.py +0 -0
  39. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/__init__.py +0 -0
  40. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/base_utils.py +0 -0
  41. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/circuit_operations.py +0 -0
  42. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gate_implementation.py +0 -0
  43. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/barrier.py +0 -0
  44. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/conditional.py +0 -0
  45. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/cz.py +0 -0
  46. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/delay.py +0 -0
  47. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/enums.py +0 -0
  48. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/flux_multiplexer.py +0 -0
  49. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/measure.py +0 -0
  50. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/move.py +0 -0
  51. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/prx.py +0 -0
  52. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/reset.py +0 -0
  53. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/rz.py +0 -0
  54. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/sx.py +0 -0
  55. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/gates/u.py +0 -0
  56. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/__init__.py +0 -0
  57. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/channel.py +0 -0
  58. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/fast_drag.py +0 -0
  59. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/hd_drag.py +0 -0
  60. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/instructions.py +0 -0
  61. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/playlist.py +0 -0
  62. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/schedule.py +0 -0
  63. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/visualisation/__init__.py +0 -0
  64. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/visualisation/base.py +0 -0
  65. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/visualisation/templates/playlist_inspection.jinja2 +0 -0
  66. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/visualisation/templates/static/logo.png +0 -0
  67. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/visualisation/templates/static/moment.min.js +0 -0
  68. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.css +0 -0
  69. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.js +0 -0
  70. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/playlist/waveforms.py +0 -0
  71. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/py.typed +0 -0
  72. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/quantum_ops.py +0 -0
  73. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/scheduler.py +0 -0
  74. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/timebox.py +0 -0
  75. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/utils.py +0 -0
  76. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm/pulse/validation.py +0 -0
  77. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm_pulse.egg-info/SOURCES.txt +0 -0
  78. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm_pulse.egg-info/dependency_links.txt +0 -0
  79. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm_pulse.egg-info/requires.txt +0 -0
  80. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/src/iqm_pulse.egg-info/top_level.txt +0 -0
  81. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/tests/.pylintrc +0 -0
  82. {iqm_pulse-10.3.0 → iqm_pulse-10.4.0}/tests/__init__.py +0 -0
@@ -2,6 +2,15 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Version 10.4.0 (2025-08-12)
6
+ ===========================
7
+
8
+ Features
9
+ --------
10
+
11
+ - Allow deprecating quantum operations and/or their implementations while still preserving them in the quantum operation
12
+ library (the names of deprecated canonical operations are still reserved).
13
+
5
14
  Version 10.3.0 (2025-08-11)
6
15
  ===========================
7
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulse
3
- Version: 10.3.0
3
+ Version: 10.4.0
4
4
  Summary: A Python-based project for providing interface and implementations for control pulses.
5
5
  Author-email: IQM Finland Oy <info@meetiqm.com>
6
6
  License: Apache License
@@ -45,7 +45,7 @@ from iqm.pulse.gate_implementation import (
45
45
  OpCalibrationDataTree,
46
46
  )
47
47
  from iqm.pulse.gates import _validate_implementation, get_implementation_class
48
- from iqm.pulse.gates.default_gates import _quantum_ops_library
48
+ from iqm.pulse.gates.default_gates import _deprecated_implementations, _deprecated_ops, _quantum_ops_library
49
49
  from iqm.pulse.playlist.channel import ChannelProperties, ProbeChannelProperties
50
50
  from iqm.pulse.playlist.instructions import (
51
51
  AcquisitionMethod,
@@ -184,8 +184,12 @@ def validate_quantum_circuit(
184
184
  def build_quantum_ops(ops: dict[str, dict[str, Any]]) -> QuantumOpTable:
185
185
  """Builds the table of known quantum operations.
186
186
 
187
- Hardcoded default native ops table is extended by the ones in ``ops``.
188
- In case of name collisions, the content of ``ops`` takes priority over the defaults.
187
+ Hardcoded canonical ops table is extended by the ones in ``ops``.
188
+ In case of name collisions, the content of ``ops`` takes priority over the defaults,
189
+ with the following caveats:
190
+
191
+ * canonical implementation names cannot be redefined, and
192
+ * for canonical operations you can only change :attr:`implementations` and :attr:`defaults_for_locus`.
189
193
 
190
194
  Args:
191
195
  ops: Contents of the ``gate_definitions`` section defining
@@ -204,7 +208,23 @@ def build_quantum_ops(ops: dict[str, dict[str, Any]]) -> QuantumOpTable:
204
208
  ValueError: Operation attributes don't match defaults or are invalid.
205
209
 
206
210
  """
207
- op_table = copy.deepcopy(_quantum_ops_library)
211
+ # build the table of native ops
212
+ op_table = {}
213
+ for op_name, op in _quantum_ops_library.items():
214
+ # filter out deprecated ops, unless requested by the user in ``ops``
215
+ if op_name in _deprecated_ops and op_name not in ops:
216
+ continue
217
+
218
+ new_op = copy.deepcopy(op) # to prevent modifications to the hardcoded table
219
+ if (deprecated_impls := _deprecated_implementations.get(op_name)) is not None:
220
+ # filter out deprecated implementations
221
+ non_deprecated = {
222
+ impl_name: impl for impl_name, impl in op.implementations.items() if impl_name not in deprecated_impls
223
+ }
224
+ new_op = replace(op, implementations=non_deprecated)
225
+ op_table[op_name] = new_op
226
+
227
+ # add ops defined by the user
208
228
  for op_name, op_definition in ops.items():
209
229
  # prepare the implementations
210
230
  implementations: dict[str, type[GateImplementation]] = {}
@@ -236,9 +256,9 @@ def build_quantum_ops(ops: dict[str, dict[str, Any]]) -> QuantumOpTable:
236
256
  f"appear in the implementations dict."
237
257
  )
238
258
 
239
- # prepare a new op, or modify the existing one
240
- if (old_op := op_table.get(op_name)) is not None:
241
- # known op: only some fields can be redefined, and they have been popped out already
259
+ if (old_op := _quantum_ops_library.get(op_name)) is not None:
260
+ # modify a canonical operation
261
+ # only some fields can be modified, and they have been popped out already from op_definition
242
262
  if op_definition:
243
263
  # TODO this should be an error, but there are so many old experiment.yml files in use
244
264
  # that still have the old syntax that being strict about this would be disruptive.
@@ -247,15 +267,17 @@ def build_quantum_ops(ops: dict[str, dict[str, Any]]) -> QuantumOpTable:
247
267
  f"'{op_name}' is a canonical operation, which means the fields {set(op_definition)} "
248
268
  "provided by the user may not be changed."
249
269
  )
250
- op_table[op_name] = replace(old_op, implementations=implementations, defaults_for_locus=defaults_for_locus)
270
+
271
+ op = replace(old_op, implementations=implementations, defaults_for_locus=defaults_for_locus)
251
272
  else:
252
- # new op
253
- op_table[op_name] = QuantumOp(
273
+ # entirely new quantum operation defined by the user
274
+ op = QuantumOp(
254
275
  name=op_name,
255
276
  implementations=implementations,
256
277
  defaults_for_locus=defaults_for_locus,
257
278
  **op_definition,
258
279
  )
280
+ op_table[op_name] = op
259
281
 
260
282
  return op_table
261
283
 
@@ -46,7 +46,7 @@ from iqm.pulse.gates.cz import (
46
46
  FluxPulseGate_CRF_CRF,
47
47
  FluxPulseGate_TGSS_CRF,
48
48
  )
49
- from iqm.pulse.gates.default_gates import _default_implementations, _quantum_ops_library
49
+ from iqm.pulse.gates.default_gates import _implementation_library, _quantum_ops_library
50
50
  from iqm.pulse.gates.delay import Delay
51
51
  from iqm.pulse.gates.flux_multiplexer import FluxMultiplexer_SampleLinear
52
52
  from iqm.pulse.gates.measure import Measure_Constant, Measure_Constant_Qnd, Shelved_Measure_Constant
@@ -157,7 +157,7 @@ def _validate_implementation(
157
157
  ValueError: A canonical implementation name is being redefined.
158
158
 
159
159
  """
160
- default_implementations = _default_implementations.get(op_name, {})
160
+ default_implementations = _implementation_library.get(op_name, {})
161
161
 
162
162
  # check if the implementation name is canonical for this op
163
163
  if (impl_class := default_implementations.get(impl_name)) is not None:
@@ -15,7 +15,7 @@
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
- from typing import TYPE_CHECKING
18
+ from typing import TYPE_CHECKING, Final
19
19
 
20
20
  import numpy as np
21
21
 
@@ -63,7 +63,7 @@ if TYPE_CHECKING:
63
63
  from iqm.pulse.gate_implementation import GateImplementation
64
64
 
65
65
 
66
- _default_implementations: dict[str, dict[str, type[GateImplementation]]] = {
66
+ _implementation_library: dict[str, dict[str, type[GateImplementation]]] = {
67
67
  "barrier": {"": Barrier},
68
68
  "delay": {"wait": Delay},
69
69
  "measure": {
@@ -105,9 +105,11 @@ _default_implementations: dict[str, dict[str, type[GateImplementation]]] = {
105
105
  "reset_wait": {"reset_wait": Reset_Wait},
106
106
  "flux_multiplexer": {"sample_linear": FluxMultiplexer_SampleLinear},
107
107
  }
108
- """Canonical implementation names for the canonical quantum operations.
109
- A collection of mappings between default implementation names and their GateImplementation classes,
110
- for different gates."""
108
+ """For each canonical quantum operation name, maps its canonical implementation implementation names
109
+ to their GateImplementation classes.
110
+
111
+ Canonical names are reserved, and the users cannot redefine them.
112
+ """
111
113
 
112
114
  _quantum_ops_library = {
113
115
  op.name: op
@@ -115,101 +117,113 @@ _quantum_ops_library = {
115
117
  QuantumOp(
116
118
  "barrier",
117
119
  0,
118
- implementations=_default_implementations["barrier"], # type: ignore[arg-type]
120
+ implementations=_implementation_library["barrier"],
119
121
  symmetric=True,
120
122
  ),
121
123
  QuantumOp(
122
124
  "delay",
123
125
  0,
124
126
  ("duration",),
125
- implementations=_default_implementations["delay"], # type: ignore[arg-type]
127
+ implementations=_implementation_library["delay"],
126
128
  symmetric=True,
127
129
  ),
128
130
  QuantumOp(
129
131
  "measure",
130
132
  0,
131
133
  ("key",),
132
- implementations=_default_implementations["measure"], # type: ignore[arg-type]
134
+ implementations=_implementation_library["measure"],
133
135
  factorizable=True,
134
136
  ),
135
137
  QuantumOp(
136
138
  "prx",
137
139
  1,
138
140
  ("angle", "phase"),
139
- implementations=_default_implementations["prx"], # type: ignore[arg-type]
141
+ implementations=_implementation_library["prx"],
140
142
  unitary=get_unitary_prx,
141
143
  ),
142
144
  QuantumOp(
143
145
  "prx_12",
144
146
  1,
145
147
  ("angle", "phase"),
146
- implementations=_default_implementations["prx_12"], # type: ignore[arg-type]
148
+ implementations=_implementation_library["prx_12"],
147
149
  ),
148
150
  QuantumOp(
149
151
  "u",
150
152
  1,
151
153
  ("theta", "phi", "lam"),
152
- implementations=_default_implementations["u"], # type: ignore[arg-type]
154
+ implementations=_implementation_library["u"],
153
155
  unitary=get_unitary_u,
154
156
  ),
155
157
  QuantumOp(
156
158
  "sx",
157
159
  1,
158
- implementations=_default_implementations["sx"], # type: ignore[arg-type]
160
+ implementations=_implementation_library["sx"],
159
161
  unitary=lambda: get_unitary_prx(np.pi / 2, 0),
160
162
  ),
161
163
  QuantumOp(
162
164
  "rz",
163
165
  1,
164
166
  ("angle",),
165
- implementations=_default_implementations["rz"], # type: ignore[arg-type]
167
+ implementations=_implementation_library["rz"],
166
168
  unitary=get_unitary_rz,
167
169
  ),
168
170
  QuantumOp(
169
171
  "rz_physical",
170
172
  1,
171
- implementations=_default_implementations["rz_physical"], # type: ignore[arg-type]
173
+ implementations=_implementation_library["rz_physical"],
172
174
  ),
173
175
  QuantumOp(
174
176
  "cz",
175
177
  2,
176
178
  (),
177
- implementations=_default_implementations["cz"], # type: ignore[arg-type]
179
+ implementations=_implementation_library["cz"],
178
180
  symmetric=True,
179
181
  unitary=lambda: np.diag([1.0, 1.0, 1.0, -1.0]),
180
182
  ),
181
183
  QuantumOp(
182
184
  "move",
183
185
  2,
184
- implementations=_default_implementations["move"], # type: ignore[arg-type]
186
+ implementations=_implementation_library["move"],
185
187
  ),
186
188
  QuantumOp(
187
189
  "cc_prx",
188
190
  1,
189
191
  ("angle", "phase", "feedback_qubit", "feedback_key"),
190
- implementations=_default_implementations["cc_prx"], # type: ignore[arg-type]
192
+ implementations=_implementation_library["cc_prx"],
191
193
  ),
192
194
  QuantumOp(
193
195
  "reset",
194
196
  0,
195
- implementations=_default_implementations["reset"], # type: ignore[arg-type]
197
+ implementations=_implementation_library["reset"],
196
198
  symmetric=True,
197
199
  factorizable=True,
198
200
  ),
199
201
  QuantumOp(
200
202
  "reset_wait",
201
203
  0,
202
- implementations=_default_implementations["reset_wait"], # type: ignore[arg-type]
204
+ implementations=_implementation_library["reset_wait"],
203
205
  symmetric=True,
204
206
  factorizable=True,
205
207
  ),
206
208
  QuantumOp(
207
209
  "flux_multiplexer",
208
210
  0,
209
- implementations=_default_implementations["flux_multiplexer"], # type: ignore[arg-type]
211
+ implementations=_implementation_library["flux_multiplexer"],
210
212
  ),
211
213
  ]
212
214
  }
213
- """Library of the current canonical quantum operations provided by iqm-pulse."""
214
- # NOTE If a canonical operation is removed in the future, consider adding a second dict for former
215
- # canonical operations so that we retain information about them.
215
+ """Canonical quantum operations provided by iqm-pulse.
216
+
217
+ Their names are reserved, and the users cannot redefine them.
218
+ """
219
+
220
+ _deprecated_ops: Final[set[str]] = set()
221
+ """Names of canonical quantum operations that are deprecated.
222
+
223
+ They are not included by default in ScheduleBuilder unless the user specifically requests them."""
224
+ _deprecated_implementations: Final[dict[str, set[str]]] = {}
225
+ """For each canonical quantum operation name, canonical implementation names that are deprecated.
226
+
227
+ They are not included by default in ScheduleBuilder unless the user specifically requests them."""
228
+ # TODO: deprecate gaussian_smoothed_square and everything with the tgss waveform as that is considered inferior to crf.
229
+ # TODO: deprecate PRX_drag_gaussian
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulse
3
- Version: 10.3.0
3
+ Version: 10.4.0
4
4
  Summary: A Python-based project for providing interface and implementations for control pulses.
5
5
  Author-email: IQM Finland Oy <info@meetiqm.com>
6
6
  License: Apache License
@@ -0,0 +1 @@
1
+ 10.4.0
@@ -1 +0,0 @@
1
- 10.3.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