qiskit 2.0.3__cp39-abi3-macosx_11_0_arm64.whl → 2.1.0__cp39-abi3-macosx_11_0_arm64.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 (180) hide show
  1. qiskit/VERSION.txt +1 -1
  2. qiskit/__init__.py +19 -1
  3. qiskit/_accelerate.abi3.so +0 -0
  4. qiskit/circuit/__init__.py +104 -20
  5. qiskit/circuit/_add_control.py +57 -31
  6. qiskit/circuit/_classical_resource_map.py +4 -0
  7. qiskit/circuit/annotation.py +504 -0
  8. qiskit/circuit/classical/expr/__init__.py +1 -1
  9. qiskit/circuit/classical/expr/expr.py +104 -446
  10. qiskit/circuit/classical/expr/visitors.py +6 -0
  11. qiskit/circuit/classical/types/types.py +7 -130
  12. qiskit/circuit/controlflow/box.py +32 -7
  13. qiskit/circuit/delay.py +11 -9
  14. qiskit/circuit/library/arithmetic/adders/adder.py +4 -4
  15. qiskit/circuit/library/arithmetic/multipliers/multiplier.py +2 -2
  16. qiskit/circuit/library/arithmetic/piecewise_chebyshev.py +8 -4
  17. qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py +23 -15
  18. qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py +22 -14
  19. qiskit/circuit/library/arithmetic/quadratic_form.py +6 -0
  20. qiskit/circuit/library/arithmetic/weighted_adder.py +43 -24
  21. qiskit/circuit/library/basis_change/qft.py +2 -2
  22. qiskit/circuit/library/blueprintcircuit.py +6 -0
  23. qiskit/circuit/library/boolean_logic/inner_product.py +2 -2
  24. qiskit/circuit/library/boolean_logic/quantum_and.py +2 -2
  25. qiskit/circuit/library/boolean_logic/quantum_or.py +3 -3
  26. qiskit/circuit/library/boolean_logic/quantum_xor.py +2 -2
  27. qiskit/circuit/library/data_preparation/_z_feature_map.py +2 -2
  28. qiskit/circuit/library/data_preparation/_zz_feature_map.py +2 -2
  29. qiskit/circuit/library/data_preparation/pauli_feature_map.py +2 -2
  30. qiskit/circuit/library/fourier_checking.py +2 -2
  31. qiskit/circuit/library/generalized_gates/diagonal.py +5 -1
  32. qiskit/circuit/library/generalized_gates/gms.py +5 -1
  33. qiskit/circuit/library/generalized_gates/linear_function.py +2 -2
  34. qiskit/circuit/library/generalized_gates/permutation.py +5 -1
  35. qiskit/circuit/library/generalized_gates/uc.py +1 -1
  36. qiskit/circuit/library/generalized_gates/unitary.py +21 -2
  37. qiskit/circuit/library/graph_state.py +2 -2
  38. qiskit/circuit/library/grover_operator.py +2 -2
  39. qiskit/circuit/library/hidden_linear_function.py +2 -2
  40. qiskit/circuit/library/iqp.py +2 -2
  41. qiskit/circuit/library/n_local/efficient_su2.py +2 -2
  42. qiskit/circuit/library/n_local/evolved_operator_ansatz.py +1 -1
  43. qiskit/circuit/library/n_local/excitation_preserving.py +7 -9
  44. qiskit/circuit/library/n_local/n_local.py +4 -3
  45. qiskit/circuit/library/n_local/pauli_two_design.py +2 -2
  46. qiskit/circuit/library/n_local/real_amplitudes.py +2 -2
  47. qiskit/circuit/library/n_local/two_local.py +2 -2
  48. qiskit/circuit/library/overlap.py +2 -2
  49. qiskit/circuit/library/pauli_evolution.py +3 -2
  50. qiskit/circuit/library/phase_estimation.py +2 -2
  51. qiskit/circuit/library/standard_gates/dcx.py +11 -12
  52. qiskit/circuit/library/standard_gates/ecr.py +21 -24
  53. qiskit/circuit/library/standard_gates/equivalence_library.py +232 -96
  54. qiskit/circuit/library/standard_gates/global_phase.py +5 -6
  55. qiskit/circuit/library/standard_gates/h.py +22 -45
  56. qiskit/circuit/library/standard_gates/i.py +1 -1
  57. qiskit/circuit/library/standard_gates/iswap.py +13 -31
  58. qiskit/circuit/library/standard_gates/p.py +19 -26
  59. qiskit/circuit/library/standard_gates/r.py +11 -17
  60. qiskit/circuit/library/standard_gates/rx.py +21 -45
  61. qiskit/circuit/library/standard_gates/rxx.py +7 -22
  62. qiskit/circuit/library/standard_gates/ry.py +21 -39
  63. qiskit/circuit/library/standard_gates/ryy.py +13 -28
  64. qiskit/circuit/library/standard_gates/rz.py +18 -35
  65. qiskit/circuit/library/standard_gates/rzx.py +7 -22
  66. qiskit/circuit/library/standard_gates/rzz.py +7 -19
  67. qiskit/circuit/library/standard_gates/s.py +44 -39
  68. qiskit/circuit/library/standard_gates/swap.py +25 -38
  69. qiskit/circuit/library/standard_gates/sx.py +34 -41
  70. qiskit/circuit/library/standard_gates/t.py +18 -27
  71. qiskit/circuit/library/standard_gates/u.py +8 -24
  72. qiskit/circuit/library/standard_gates/u1.py +28 -52
  73. qiskit/circuit/library/standard_gates/u2.py +9 -9
  74. qiskit/circuit/library/standard_gates/u3.py +24 -40
  75. qiskit/circuit/library/standard_gates/x.py +190 -336
  76. qiskit/circuit/library/standard_gates/xx_minus_yy.py +12 -50
  77. qiskit/circuit/library/standard_gates/xx_plus_yy.py +13 -52
  78. qiskit/circuit/library/standard_gates/y.py +19 -23
  79. qiskit/circuit/library/standard_gates/z.py +31 -38
  80. qiskit/circuit/parameter.py +14 -5
  81. qiskit/circuit/parameterexpression.py +109 -75
  82. qiskit/circuit/quantumcircuit.py +172 -99
  83. qiskit/circuit/quantumcircuitdata.py +1 -0
  84. qiskit/circuit/random/__init__.py +37 -2
  85. qiskit/circuit/random/utils.py +445 -56
  86. qiskit/circuit/tools/pi_check.py +5 -13
  87. qiskit/compiler/transpiler.py +1 -1
  88. qiskit/converters/circuit_to_instruction.py +2 -2
  89. qiskit/dagcircuit/dagnode.py +8 -3
  90. qiskit/primitives/__init__.py +2 -2
  91. qiskit/primitives/base/base_estimator.py +2 -2
  92. qiskit/primitives/containers/data_bin.py +0 -3
  93. qiskit/primitives/containers/observables_array.py +192 -108
  94. qiskit/primitives/primitive_job.py +29 -10
  95. qiskit/providers/fake_provider/generic_backend_v2.py +2 -0
  96. qiskit/qasm3/__init__.py +106 -12
  97. qiskit/qasm3/ast.py +15 -1
  98. qiskit/qasm3/exporter.py +59 -36
  99. qiskit/qasm3/printer.py +12 -0
  100. qiskit/qpy/__init__.py +182 -6
  101. qiskit/qpy/binary_io/circuits.py +256 -24
  102. qiskit/qpy/binary_io/parse_sympy_repr.py +5 -0
  103. qiskit/qpy/binary_io/schedules.py +12 -32
  104. qiskit/qpy/binary_io/value.py +36 -18
  105. qiskit/qpy/common.py +11 -3
  106. qiskit/qpy/formats.py +17 -1
  107. qiskit/qpy/interface.py +52 -12
  108. qiskit/qpy/type_keys.py +7 -1
  109. qiskit/quantum_info/__init__.py +10 -0
  110. qiskit/quantum_info/operators/__init__.py +1 -0
  111. qiskit/quantum_info/operators/symplectic/__init__.py +1 -0
  112. qiskit/quantum_info/operators/symplectic/clifford_circuits.py +26 -0
  113. qiskit/quantum_info/operators/symplectic/pauli.py +2 -2
  114. qiskit/result/sampled_expval.py +3 -1
  115. qiskit/synthesis/__init__.py +10 -0
  116. qiskit/synthesis/arithmetic/__init__.py +1 -1
  117. qiskit/synthesis/arithmetic/adders/__init__.py +1 -0
  118. qiskit/synthesis/arithmetic/adders/draper_qft_adder.py +6 -2
  119. qiskit/synthesis/arithmetic/adders/rv_ripple_carry_adder.py +156 -0
  120. qiskit/synthesis/discrete_basis/generate_basis_approximations.py +14 -126
  121. qiskit/synthesis/discrete_basis/solovay_kitaev.py +161 -121
  122. qiskit/synthesis/evolution/lie_trotter.py +10 -7
  123. qiskit/synthesis/evolution/product_formula.py +10 -7
  124. qiskit/synthesis/evolution/qdrift.py +10 -7
  125. qiskit/synthesis/evolution/suzuki_trotter.py +10 -7
  126. qiskit/synthesis/multi_controlled/__init__.py +4 -0
  127. qiskit/synthesis/multi_controlled/mcx_synthesis.py +402 -178
  128. qiskit/synthesis/multi_controlled/multi_control_rotation_gates.py +14 -15
  129. qiskit/synthesis/qft/qft_decompose_lnn.py +7 -25
  130. qiskit/synthesis/unitary/qsd.py +80 -9
  131. qiskit/transpiler/__init__.py +10 -3
  132. qiskit/transpiler/instruction_durations.py +2 -20
  133. qiskit/transpiler/passes/__init__.py +5 -2
  134. qiskit/transpiler/passes/layout/dense_layout.py +26 -6
  135. qiskit/transpiler/passes/layout/disjoint_utils.py +1 -166
  136. qiskit/transpiler/passes/layout/sabre_layout.py +22 -3
  137. qiskit/transpiler/passes/layout/sabre_pre_layout.py +1 -1
  138. qiskit/transpiler/passes/layout/vf2_layout.py +49 -13
  139. qiskit/transpiler/passes/layout/vf2_utils.py +10 -0
  140. qiskit/transpiler/passes/optimization/__init__.py +1 -1
  141. qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +2 -1
  142. qiskit/transpiler/passes/optimization/optimize_clifford_t.py +68 -0
  143. qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +3 -9
  144. qiskit/transpiler/passes/routing/sabre_swap.py +4 -2
  145. qiskit/transpiler/passes/routing/star_prerouting.py +106 -81
  146. qiskit/transpiler/passes/scheduling/__init__.py +1 -1
  147. qiskit/transpiler/passes/scheduling/alignments/check_durations.py +1 -1
  148. qiskit/transpiler/passes/scheduling/padding/__init__.py +1 -0
  149. qiskit/transpiler/passes/scheduling/padding/context_aware_dynamical_decoupling.py +876 -0
  150. qiskit/transpiler/passes/synthesis/__init__.py +1 -0
  151. qiskit/transpiler/passes/synthesis/clifford_unitary_synth_plugin.py +123 -0
  152. qiskit/transpiler/passes/synthesis/hls_plugins.py +494 -93
  153. qiskit/transpiler/passes/synthesis/plugin.py +4 -0
  154. qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py +27 -22
  155. qiskit/transpiler/passmanager_config.py +3 -0
  156. qiskit/transpiler/preset_passmanagers/builtin_plugins.py +149 -28
  157. qiskit/transpiler/preset_passmanagers/common.py +101 -0
  158. qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +6 -0
  159. qiskit/transpiler/preset_passmanagers/level3.py +2 -2
  160. qiskit/transpiler/target.py +15 -2
  161. qiskit/utils/optionals.py +6 -5
  162. qiskit/visualization/circuit/_utils.py +5 -3
  163. qiskit/visualization/circuit/latex.py +9 -2
  164. qiskit/visualization/circuit/matplotlib.py +26 -4
  165. qiskit/visualization/circuit/qcstyle.py +9 -157
  166. qiskit/visualization/dag/__init__.py +13 -0
  167. qiskit/visualization/dag/dagstyle.py +103 -0
  168. qiskit/visualization/dag/styles/__init__.py +13 -0
  169. qiskit/visualization/dag/styles/color.json +10 -0
  170. qiskit/visualization/dag/styles/plain.json +5 -0
  171. qiskit/visualization/dag_visualization.py +169 -98
  172. qiskit/visualization/style.py +223 -0
  173. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/METADATA +7 -6
  174. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/RECORD +178 -169
  175. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/entry_points.txt +6 -0
  176. qiskit/synthesis/discrete_basis/commutator_decompose.py +0 -265
  177. qiskit/synthesis/discrete_basis/gate_sequence.py +0 -421
  178. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/WHEEL +0 -0
  179. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/licenses/LICENSE.txt +0 -0
  180. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/top_level.txt +0 -0
@@ -13,18 +13,23 @@
13
13
  # pylint: disable=invalid-name
14
14
 
15
15
  """Binary IO for circuit objects."""
16
+
17
+ from __future__ import annotations
18
+
16
19
  import itertools
17
20
  from collections import defaultdict
18
21
  import io
19
22
  import json
20
23
  import struct
21
24
  import uuid
25
+ import typing
22
26
  import warnings
23
27
 
24
28
  import numpy as np
25
29
 
26
30
  from qiskit import circuit as circuit_mod
27
31
  from qiskit.circuit import library, controlflow, CircuitInstruction, ControlFlowOp, IfElseOp
32
+ from qiskit.circuit.annotation import iter_namespaces
28
33
  from qiskit.circuit.classical import expr
29
34
  from qiskit.circuit import ClassicalRegister, Clbit
30
35
  from qiskit.circuit.gate import Gate
@@ -41,11 +46,81 @@ from qiskit.circuit.instruction import Instruction
41
46
  from qiskit.circuit.quantumcircuit import QuantumCircuit
42
47
  from qiskit.circuit import QuantumRegister, Qubit
43
48
  from qiskit.qpy import common, formats, type_keys
49
+ from qiskit.qpy.exceptions import QpyError, UnsupportedFeatureForVersion
44
50
  from qiskit.qpy.binary_io import value, schedules
45
51
  from qiskit.quantum_info.operators import SparsePauliOp, Clifford
46
52
  from qiskit.synthesis import evolution as evo_synth
47
53
  from qiskit.transpiler.layout import Layout, TranspileLayout
48
54
 
55
+ if typing.TYPE_CHECKING:
56
+ from qiskit.circuit.annotation import QPYSerializer, Annotation
57
+
58
+
59
+ class _AnnotationSerializationState:
60
+ def __init__(self, factories: dict[str, typing.Callable[[], QPYSerializer]]):
61
+ self.factories = factories
62
+ self.serializers = {}
63
+ self.potential_serializers = {}
64
+
65
+ @property
66
+ def num_serializers(self) -> int:
67
+ """The number of constructed serializers."""
68
+ return len(self.serializers)
69
+
70
+ def serialize(self, annotation: Annotation) -> (int, bytes):
71
+ """Serialize an annotation using a known serializer (initializing one, if necessary).
72
+
73
+ Returns the index of the serializer used, and the serialized annotation."""
74
+ for namespace in iter_namespaces(annotation.namespace):
75
+ if (existing := self.serializers.get(namespace, None)) is not None:
76
+ index, serializer = existing
77
+ if (out := serializer.dump_annotation(namespace, annotation)) is not NotImplemented:
78
+ return index, out
79
+ if (serializer := self.potential_serializers.get(namespace, None)) is not None:
80
+ if (out := serializer.dump_annotation(namespace, annotation)) is not NotImplemented:
81
+ del self.potential_serializers[namespace]
82
+ index = len(self.serializers)
83
+ self.serializers[namespace] = (index, serializer)
84
+ return index, out
85
+ if (factory := self.factories.get(namespace, None)) is not None:
86
+ serializer = factory()
87
+ if (out := serializer.dump_annotation(namespace, annotation)) is NotImplemented:
88
+ self.potential_serializers[namespace] = serializer
89
+ else:
90
+ index = len(self.serializers)
91
+ self.serializers[namespace] = (index, serializer)
92
+ return index, out
93
+ raise QpyError(f"No configured annotation serializer could handle {annotation}")
94
+
95
+ def iter_serializers(self) -> typing.Iterator[tuple[str, QPYSerializer]]:
96
+ """Iterate over the namespaces and serializers that have had at least one successful use, in
97
+ order of first use."""
98
+ return (
99
+ # Python dictionaries are insertion ordered, and we assign indices in insertion order.
100
+ (namespace, serializer)
101
+ for (namespace, (_, serializer)) in self.serializers.items()
102
+ )
103
+
104
+
105
+ class _AnnotationDeserializationState:
106
+ def __init__(self, factories: dict[str, typing.Callable[[], QPYSerializer]]):
107
+ self.factories = factories
108
+ self.deserializers = []
109
+
110
+ def initialize(self, namespace: str, payload: bytes):
111
+ """Initialize a suitable deserializer using the given state payload."""
112
+ for parent_namespace in iter_namespaces(namespace):
113
+ if (factory := self.factories.get(parent_namespace, None)) is not None:
114
+ deserializer = factory()
115
+ deserializer.load_state(namespace, payload)
116
+ self.deserializers.append(deserializer)
117
+ return
118
+ raise QpyError(f"No configured annotation deserializer matched namespace '{namespace}'")
119
+
120
+ def load(self, index: int, payload: bytes) -> Annotation:
121
+ """Load a payload using the deserializer of a given index."""
122
+ return self.deserializers[index].load_annotation(payload)
123
+
49
124
 
50
125
  def _read_header_v12(file_obj, version, vectors, metadata_deserializer=None):
51
126
  data = formats.CIRCUIT_HEADER_V12._make(
@@ -158,6 +233,50 @@ def _read_registers(file_obj, num_registers):
158
233
  return registers
159
234
 
160
235
 
236
+ def _read_annotation_states(file_obj, annotation_factories) -> _AnnotationDeserializationState:
237
+ state = _AnnotationDeserializationState(annotation_factories)
238
+ static_payload = formats.ANNOTATION_HEADER_STATIC._make(
239
+ struct.unpack(
240
+ formats.ANNOTATION_HEADER_STATIC_PACK,
241
+ file_obj.read(formats.ANNOTATION_HEADER_STATIC_SIZE),
242
+ )
243
+ )
244
+ for _ in range(static_payload.num_namespaces):
245
+ payload = formats.ANNOTATION_STATE_HEADER._make(
246
+ struct.unpack(
247
+ formats.ANNOTATION_STATE_HEADER_PACK,
248
+ file_obj.read(formats.ANNOTATION_STATE_HEADER_SIZE),
249
+ )
250
+ )
251
+ state.initialize(
252
+ file_obj.read(payload.namespace_size).decode("utf-8"), file_obj.read(payload.state_size)
253
+ )
254
+ return state
255
+
256
+
257
+ def _read_instruction_annotation(file_obj, annotation_state):
258
+ header = formats.INSTRUCTION_ANNOTATION._make(
259
+ struct.unpack(
260
+ formats.INSTRUCTION_ANNOTATION_PACK,
261
+ file_obj.read(formats.INSTRUCTION_ANNOTATION_SIZE),
262
+ )
263
+ )
264
+ return annotation_state.load(header.namespace_index, file_obj.read(header.payload_size))
265
+
266
+
267
+ def _read_instruction_annotations(file_obj, annotation_state):
268
+ header = formats.INSTRUCTION_ANNOTATIONS_HEADER._make(
269
+ struct.unpack(
270
+ formats.INSTRUCTION_ANNOTATIONS_HEADER_PACK,
271
+ file_obj.read(formats.INSTRUCTION_ANNOTATIONS_HEADER_SIZE),
272
+ )
273
+ )
274
+ return [
275
+ _read_instruction_annotation(file_obj, annotation_state)
276
+ for _ in range(header.num_annotations)
277
+ ]
278
+
279
+
161
280
  def _loads_instruction_parameter(
162
281
  type_key,
163
282
  data_bytes,
@@ -167,9 +286,12 @@ def _loads_instruction_parameter(
167
286
  circuit,
168
287
  use_symengine,
169
288
  standalone_vars,
289
+ annotation_factories,
170
290
  ):
171
291
  if type_key == type_keys.Program.CIRCUIT:
172
- param = common.data_from_binary(data_bytes, read_circuit, version=version)
292
+ param = common.data_from_binary(
293
+ data_bytes, read_circuit, version=version, annotation_factories=annotation_factories
294
+ )
173
295
  elif type_key == type_keys.Value.MODIFIER:
174
296
  param = common.data_from_binary(data_bytes, _read_modifier)
175
297
  elif type_key == type_keys.Container.RANGE:
@@ -186,6 +308,7 @@ def _loads_instruction_parameter(
186
308
  circuit=circuit,
187
309
  use_symengine=use_symengine,
188
310
  standalone_vars=standalone_vars,
311
+ annotation_factories=annotation_factories,
189
312
  )
190
313
  )
191
314
  elif type_key == type_keys.Value.INTEGER:
@@ -229,6 +352,7 @@ def _read_instruction(
229
352
  vectors,
230
353
  use_symengine,
231
354
  standalone_vars,
355
+ annotation_state,
232
356
  ):
233
357
  if version < 5:
234
358
  instruction = formats.CIRCUIT_INSTRUCTION._make(
@@ -237,6 +361,10 @@ def _read_instruction(
237
361
  file_obj.read(formats.CIRCUIT_INSTRUCTION_SIZE),
238
362
  )
239
363
  )
364
+ conditional_key = (
365
+ type_keys.Condition.TWO_TUPLE if instruction.has_condition else type_keys.Condition.NONE
366
+ )
367
+ has_annotations = False
240
368
  else:
241
369
  instruction = formats.CIRCUIT_INSTRUCTION_V2._make(
242
370
  struct.unpack(
@@ -244,6 +372,11 @@ def _read_instruction(
244
372
  file_obj.read(formats.CIRCUIT_INSTRUCTION_V2_SIZE),
245
373
  )
246
374
  )
375
+ conditional_key = type_keys.Condition(instruction.extras_key & 0b11)
376
+ has_annotations = bool(
377
+ instruction.extras_key & type_keys.InstructionExtraFlags.HAS_ANNOTATIONS
378
+ )
379
+
247
380
  gate_name = file_obj.read(instruction.name_size).decode(common.ENCODE)
248
381
  label = file_obj.read(instruction.label_size).decode(common.ENCODE)
249
382
  condition_register = file_obj.read(instruction.condition_register_size).decode(common.ENCODE)
@@ -251,14 +384,12 @@ def _read_instruction(
251
384
  cargs = []
252
385
  params = []
253
386
  condition = None
254
- if (version < 5 and instruction.has_condition) or (
255
- version >= 5 and instruction.conditional_key == type_keys.Condition.TWO_TUPLE
256
- ):
387
+ if conditional_key == type_keys.Condition.TWO_TUPLE:
257
388
  condition = (
258
389
  _loads_register_param(condition_register, circuit, registers),
259
390
  instruction.condition_value,
260
391
  )
261
- elif version >= 5 and instruction.conditional_key == type_keys.Condition.EXPRESSION:
392
+ elif conditional_key == type_keys.Condition.EXPRESSION:
262
393
  condition = value.read_value(
263
394
  file_obj,
264
395
  version,
@@ -268,6 +399,7 @@ def _read_instruction(
268
399
  use_symengine=use_symengine,
269
400
  standalone_vars=standalone_vars,
270
401
  )
402
+
271
403
  # Load Arguments
272
404
  if circuit is not None:
273
405
  for _qarg in range(instruction.num_qargs):
@@ -303,9 +435,15 @@ def _read_instruction(
303
435
  circuit,
304
436
  use_symengine,
305
437
  standalone_vars,
438
+ annotation_factories=annotation_state.factories,
306
439
  )
307
440
  params.append(param)
308
441
 
442
+ # Load annotations.
443
+ annotations = (
444
+ _read_instruction_annotations(file_obj, annotation_state) if has_annotations else None
445
+ )
446
+
309
447
  # Load Gate object
310
448
  if gate_name in {"Gate", "Instruction", "ControlledGate"}:
311
449
  inst_obj = _parse_custom_operation(
@@ -317,6 +455,7 @@ def _read_instruction(
317
455
  registers,
318
456
  use_symengine,
319
457
  standalone_vars,
458
+ annotation_state=annotation_state,
320
459
  )
321
460
  if condition is not None:
322
461
  warnings.warn(
@@ -346,6 +485,7 @@ def _read_instruction(
346
485
  registers,
347
486
  use_symengine,
348
487
  standalone_vars,
488
+ annotation_state=annotation_state,
349
489
  )
350
490
  inst_obj.condition = condition
351
491
  if instruction.label_size > 0:
@@ -371,7 +511,9 @@ def _read_instruction(
371
511
  gate = gate_class(condition, *params, label=label)
372
512
  elif gate_name == "BoxOp":
373
513
  *params, duration, unit = params
374
- gate = gate_class(*params, label=label, duration=duration, unit=unit)
514
+ gate = gate_class(
515
+ *params, label=label, duration=duration, unit=unit, annotations=annotations or ()
516
+ )
375
517
  elif version >= 5 and issubclass(gate_class, ControlledGate):
376
518
  if gate_name in {
377
519
  "MCPhaseGate",
@@ -460,6 +602,7 @@ def _parse_custom_operation(
460
602
  registers,
461
603
  use_symengine,
462
604
  standalone_vars,
605
+ annotation_state,
463
606
  ):
464
607
  if version >= 5:
465
608
  (
@@ -501,6 +644,7 @@ def _parse_custom_operation(
501
644
  vectors,
502
645
  use_symengine,
503
646
  standalone_vars,
647
+ annotation_state=annotation_state,
504
648
  )
505
649
  if ctrl_state < 2**num_ctrl_qubits - 1:
506
650
  # If open controls, we need to discard the control suffix when setting the name.
@@ -527,6 +671,7 @@ def _parse_custom_operation(
527
671
  vectors,
528
672
  use_symengine,
529
673
  standalone_vars,
674
+ annotation_state=annotation_state,
530
675
  )
531
676
  inst_obj = AnnotatedOperation(base_op=base_gate, modifiers=params)
532
677
  return inst_obj
@@ -595,7 +740,7 @@ def _read_modifier(file_obj):
595
740
  raise TypeError("Unsupported modifier.")
596
741
 
597
742
 
598
- def _read_custom_operations(file_obj, version, vectors):
743
+ def _read_custom_operations(file_obj, version, vectors, annotation_state):
599
744
  custom_operations = {}
600
745
  custom_definition_header = formats.CUSTOM_CIRCUIT_DEF_HEADER._make(
601
746
  struct.unpack(
@@ -627,7 +772,10 @@ def _read_custom_operations(file_obj, version, vectors):
627
772
  def_binary = file_obj.read(data.size)
628
773
  if version < 3 or not name.startswith(r"###PauliEvolutionGate_"):
629
774
  definition_circuit = common.data_from_binary(
630
- def_binary, read_circuit, version=version
775
+ def_binary,
776
+ read_circuit,
777
+ version=version,
778
+ annotation_factories=annotation_state.factories,
631
779
  )
632
780
  elif name.startswith(r"###PauliEvolutionGate_"):
633
781
  definition_circuit = common.data_from_binary(
@@ -684,11 +832,13 @@ def _dumps_register(register, index_map):
684
832
 
685
833
 
686
834
  def _dumps_instruction_parameter(
687
- param, index_map, use_symengine, *, version, standalone_var_indices
835
+ param, index_map, use_symengine, *, version, standalone_var_indices, annotation_factories
688
836
  ):
689
837
  if isinstance(param, QuantumCircuit):
690
838
  type_key = type_keys.Program.CIRCUIT
691
- data_bytes = common.data_to_binary(param, write_circuit, version=version)
839
+ data_bytes = common.data_to_binary(
840
+ param, write_circuit, version=version, annotation_factories=annotation_factories
841
+ )
692
842
  elif isinstance(param, Modifier):
693
843
  type_key = type_keys.Value.MODIFIER
694
844
  data_bytes = common.data_to_binary(param, _write_modifier)
@@ -704,6 +854,7 @@ def _dumps_instruction_parameter(
704
854
  use_symengine=use_symengine,
705
855
  version=version,
706
856
  standalone_var_indices=standalone_var_indices,
857
+ annotation_factories=annotation_factories,
707
858
  )
708
859
  elif isinstance(param, int):
709
860
  # TODO This uses little endian. This should be fixed in next QPY version.
@@ -736,6 +887,7 @@ def _write_instruction(
736
887
  index_map,
737
888
  use_symengine,
738
889
  version,
890
+ annotation_state,
739
891
  standalone_var_indices=None,
740
892
  ):
741
893
  if isinstance(instruction.operation, Instruction):
@@ -788,14 +940,14 @@ def _write_instruction(
788
940
  custom_operations[gate_class_name] = instruction.operation
789
941
  custom_operations_list.append(gate_class_name)
790
942
 
791
- condition_type = type_keys.Condition.NONE
943
+ extra_type = type_keys.Condition.NONE
792
944
  condition_register = b""
793
945
  condition_value = 0
794
946
  if (op_condition := getattr(instruction.operation, "_condition", None)) is not None:
795
947
  if isinstance(op_condition, expr.Expr):
796
- condition_type = type_keys.Condition.EXPRESSION
948
+ extra_type = type_keys.Condition.EXPRESSION
797
949
  else:
798
- condition_type = type_keys.Condition.TWO_TUPLE
950
+ extra_type = type_keys.Condition.TWO_TUPLE
799
951
  condition_register = _dumps_register(instruction.operation._condition[0], index_map)
800
952
  condition_value = int(instruction.operation._condition[1])
801
953
 
@@ -806,6 +958,7 @@ def _write_instruction(
806
958
  else:
807
959
  label_raw = b""
808
960
 
961
+ annotations = []
809
962
  # The instruction params we store are about being able to reconstruct the objects; they don't
810
963
  # necessarily need to match one-to-one to the `params` field.
811
964
  if isinstance(instruction.operation, controlflow.SwitchCaseOp):
@@ -819,6 +972,10 @@ def _write_instruction(
819
972
  instruction.operation.duration,
820
973
  instruction.operation.unit,
821
974
  ]
975
+ annotations = [
976
+ annotation_state.serialize(annotation)
977
+ for annotation in instruction.operation.annotations
978
+ ]
822
979
  elif isinstance(instruction.operation, Clifford):
823
980
  instruction_params = [instruction.operation.tableau]
824
981
  elif isinstance(instruction.operation, AnnotatedOperation):
@@ -826,6 +983,9 @@ def _write_instruction(
826
983
  else:
827
984
  instruction_params = getattr(instruction.operation, "params", [])
828
985
 
986
+ if annotations:
987
+ extra_type |= type_keys.InstructionExtraFlags.HAS_ANNOTATIONS
988
+
829
989
  num_ctrl_qubits = getattr(instruction.operation, "num_ctrl_qubits", 0)
830
990
  ctrl_state = getattr(instruction.operation, "ctrl_state", 0)
831
991
  instruction_raw = struct.pack(
@@ -835,7 +995,7 @@ def _write_instruction(
835
995
  len(instruction_params),
836
996
  instruction.operation.num_qubits,
837
997
  instruction.operation.num_clbits,
838
- condition_type.value,
998
+ int(extra_type),
839
999
  len(condition_register),
840
1000
  condition_value,
841
1001
  num_ctrl_qubits,
@@ -844,6 +1004,7 @@ def _write_instruction(
844
1004
  file_obj.write(instruction_raw)
845
1005
  file_obj.write(gate_class_name)
846
1006
  file_obj.write(label_raw)
1007
+ condition_type = type_keys.Condition(extra_type & 0b11)
847
1008
  if condition_type is type_keys.Condition.EXPRESSION:
848
1009
  value.write_value(
849
1010
  file_obj,
@@ -854,6 +1015,7 @@ def _write_instruction(
854
1015
  )
855
1016
  else:
856
1017
  file_obj.write(condition_register)
1018
+
857
1019
  # Encode instruction args
858
1020
  for qbit in instruction.qubits:
859
1021
  instruction_arg_raw = struct.pack(
@@ -873,8 +1035,20 @@ def _write_instruction(
873
1035
  use_symengine,
874
1036
  version=version,
875
1037
  standalone_var_indices=standalone_var_indices,
1038
+ annotation_factories=annotation_state.factories,
876
1039
  )
877
1040
  common.write_generic_typed_data(file_obj, type_key, data_bytes)
1041
+ if annotations:
1042
+ if version < 15:
1043
+ raise UnsupportedFeatureForVersion("annotations", 15, version)
1044
+ file_obj.write(struct.pack(formats.INSTRUCTION_ANNOTATIONS_HEADER_PACK, len(annotations)))
1045
+ for serializer_index, annotation_payload in annotations:
1046
+ file_obj.write(
1047
+ struct.pack(
1048
+ formats.INSTRUCTION_ANNOTATION_PACK, serializer_index, len(annotation_payload)
1049
+ )
1050
+ )
1051
+ file_obj.write(annotation_payload)
878
1052
  return custom_operations_list
879
1053
 
880
1054
 
@@ -944,7 +1118,15 @@ def _write_modifier(file_obj, modifier):
944
1118
 
945
1119
 
946
1120
  def _write_custom_operation(
947
- file_obj, name, operation, custom_operations, use_symengine, version, *, standalone_var_indices
1121
+ file_obj,
1122
+ name,
1123
+ operation,
1124
+ custom_operations,
1125
+ use_symengine,
1126
+ version,
1127
+ *,
1128
+ standalone_var_indices,
1129
+ annotation_state,
948
1130
  ):
949
1131
  type_key = type_keys.CircuitInstruction.assign(operation)
950
1132
  has_definition = False
@@ -970,7 +1152,12 @@ def _write_custom_operation(
970
1152
  # Build internal definition to support overloaded subclasses by
971
1153
  # calling definition getter on object
972
1154
  operation.definition # pylint: disable=pointless-statement
973
- data = common.data_to_binary(operation._definition, write_circuit, version=version)
1155
+ data = common.data_to_binary(
1156
+ operation._definition,
1157
+ write_circuit,
1158
+ version=version,
1159
+ annotation_factories=annotation_state.factories,
1160
+ )
974
1161
  size = len(data)
975
1162
  num_ctrl_qubits = operation.num_ctrl_qubits
976
1163
  ctrl_state = operation.ctrl_state
@@ -980,7 +1167,12 @@ def _write_custom_operation(
980
1167
  base_gate = operation.base_op
981
1168
  elif operation.definition is not None:
982
1169
  has_definition = True
983
- data = common.data_to_binary(operation.definition, write_circuit, version=version)
1170
+ data = common.data_to_binary(
1171
+ operation.definition,
1172
+ write_circuit,
1173
+ version=version,
1174
+ annotation_factories=annotation_state.factories,
1175
+ )
984
1176
  size = len(data)
985
1177
  if base_gate is None:
986
1178
  base_gate_raw = b""
@@ -994,6 +1186,7 @@ def _write_custom_operation(
994
1186
  use_symengine,
995
1187
  version,
996
1188
  standalone_var_indices=standalone_var_indices,
1189
+ annotation_state=annotation_state,
997
1190
  )
998
1191
  base_gate_raw = base_gate_buffer.getvalue()
999
1192
  name_raw = name.encode(common.ENCODE)
@@ -1203,7 +1396,12 @@ def _read_layout_v2(file_obj, circuit):
1203
1396
 
1204
1397
 
1205
1398
  def write_circuit(
1206
- file_obj, circuit, metadata_serializer=None, use_symengine=False, version=common.QPY_VERSION
1399
+ file_obj,
1400
+ circuit,
1401
+ metadata_serializer=None,
1402
+ use_symengine=False,
1403
+ version=common.QPY_VERSION,
1404
+ annotation_factories=None,
1207
1405
  ):
1208
1406
  """Write a single QuantumCircuit object in the file like object.
1209
1407
 
@@ -1219,7 +1417,10 @@ def write_circuit(
1219
1417
  platforms. Please check that your target platform is supported by the symengine library
1220
1418
  before setting this option, as it will be required by qpy to deserialize the payload.
1221
1419
  version (int): The QPY format version to use for serializing this circuit
1420
+ annotation_factories (dict): a mapping of namespaces to zero-argument factory functions that
1421
+ produce instances of :class:`.annotation.QPYSerializer`.
1222
1422
  """
1423
+ annotation_state = _AnnotationSerializationState(annotation_factories or {})
1223
1424
  metadata_raw = json.dumps(
1224
1425
  circuit.metadata, separators=(",", ":"), cls=metadata_serializer
1225
1426
  ).encode(common.ENCODE)
@@ -1269,6 +1470,7 @@ def write_circuit(
1269
1470
  use_symengine,
1270
1471
  version,
1271
1472
  standalone_var_indices=standalone_var_indices,
1473
+ annotation_state=annotation_state,
1272
1474
  )
1273
1475
 
1274
1476
  with io.BytesIO() as custom_operations_buffer:
@@ -1287,12 +1489,33 @@ def write_circuit(
1287
1489
  use_symengine,
1288
1490
  version,
1289
1491
  standalone_var_indices=standalone_var_indices,
1492
+ annotation_state=annotation_state,
1290
1493
  )
1291
1494
  )
1495
+ # We only write this out after we've done the annotations.
1496
+ custom_operations_payload = custom_operations_buffer.getvalue()
1292
1497
 
1293
- file_obj.write(struct.pack(formats.CUSTOM_CIRCUIT_DEF_HEADER_PACK, len(custom_operations)))
1294
- file_obj.write(custom_operations_buffer.getvalue())
1498
+ if version >= 15:
1499
+ file_obj.write(
1500
+ struct.pack(formats.ANNOTATION_HEADER_STATIC_PACK, annotation_state.num_serializers)
1501
+ )
1502
+ for namespace, serializer in annotation_state.iter_serializers():
1503
+ namespace_bytes = namespace.encode("utf-8")
1504
+ serializer_state = serializer.dump_state()
1505
+ file_obj.write(
1506
+ struct.pack(
1507
+ formats.ANNOTATION_STATE_HEADER_PACK,
1508
+ len(namespace_bytes),
1509
+ len(serializer_state),
1510
+ )
1511
+ )
1512
+ file_obj.write(namespace_bytes)
1513
+ file_obj.write(serializer_state)
1514
+ elif annotation_state.num_serializers:
1515
+ raise UnsupportedFeatureForVersion(annotations, 15, version)
1295
1516
 
1517
+ file_obj.write(struct.pack(formats.CUSTOM_CIRCUIT_DEF_HEADER_PACK, len(custom_operations)))
1518
+ file_obj.write(custom_operations_payload)
1296
1519
  file_obj.write(instruction_buffer.getvalue())
1297
1520
  instruction_buffer.close()
1298
1521
 
@@ -1304,7 +1527,9 @@ def write_circuit(
1304
1527
  _write_layout(file_obj, circuit)
1305
1528
 
1306
1529
 
1307
- def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=False):
1530
+ def read_circuit(
1531
+ file_obj, version, metadata_deserializer=None, use_symengine=False, annotation_factories=None
1532
+ ):
1308
1533
  """Read a single QuantumCircuit object from the file like object.
1309
1534
 
1310
1535
  Args:
@@ -1322,6 +1547,8 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa
1322
1547
  supported in all platforms. Please check that your target platform is supported by
1323
1548
  the symengine library before setting this option, as it will be required by qpy to
1324
1549
  deserialize the payload.
1550
+ annotation_factories (dict): mapping of namespaces to factory functions for custom
1551
+ annotation deserializer objects.
1325
1552
  Returns:
1326
1553
  QuantumCircuit: The circuit object from the file.
1327
1554
 
@@ -1420,7 +1647,11 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa
1420
1647
  circ.add_uninitialized_var(declaration)
1421
1648
  for stretch in var_segments[type_keys.ExprVarDeclaration.STRETCH_LOCAL]:
1422
1649
  circ.add_stretch(stretch)
1423
- custom_operations = _read_custom_operations(file_obj, version, vectors)
1650
+ if version >= 15:
1651
+ annotation_state = _read_annotation_states(file_obj, annotation_factories or {})
1652
+ else:
1653
+ annotation_state = _AnnotationDeserializationState(annotation_factories or {})
1654
+ custom_operations = _read_custom_operations(file_obj, version, vectors, annotation_state)
1424
1655
  for _instruction in range(num_instructions):
1425
1656
  _read_instruction(
1426
1657
  file_obj,
@@ -1431,16 +1662,17 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa
1431
1662
  vectors,
1432
1663
  use_symengine,
1433
1664
  standalone_var_indices,
1665
+ annotation_state=annotation_state,
1434
1666
  )
1435
1667
 
1436
1668
  # Consume calibrations, but don't use them since pulse gates are not supported as of Qiskit 2.0
1437
1669
  if version >= 5:
1438
1670
  _read_calibrations(file_obj, version, vectors, metadata_deserializer)
1439
1671
 
1440
- for vec_name, (vector, initialized_params) in vectors.items():
1672
+ for vector, initialized_params in vectors.values():
1441
1673
  if len(initialized_params) != len(vector):
1442
1674
  warnings.warn(
1443
- f"The ParameterVector: '{vec_name}' is not fully identical to its "
1675
+ f"The ParameterVector: '{vector.name}' is not fully identical to its "
1444
1676
  "pre-serialization state. Elements "
1445
1677
  f"{', '.join([str(x) for x in set(range(len(vector))) - initialized_params])} "
1446
1678
  "in the ParameterVector will be not equal to the pre-serialized ParameterVector "
@@ -15,6 +15,7 @@
15
15
  import ast
16
16
 
17
17
  from qiskit.qpy.exceptions import QpyError
18
+ from qiskit.utils.optionals import HAS_SYMPY
18
19
 
19
20
 
20
21
  ALLOWED_CALLERS = {
@@ -113,6 +114,10 @@ class ParseSympyWalker(ast.NodeVisitor):
113
114
  self.stack.append(obj)
114
115
 
115
116
 
117
+ @HAS_SYMPY.require_in_call(
118
+ "Sympy is required to parse parameter expressions encoded using sympy's "
119
+ "srepr in QPY format versions < 13"
120
+ )
116
121
  def parse_sympy_repr(sympy_repr: str):
117
122
  """Parse a given sympy srepr into a symbolic expression object."""
118
123
  tree = ast.parse(sympy_repr, mode="eval")