classiq 0.43.3__py3-none-any.whl → 0.44.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. classiq/__init__.py +7 -1
  2. classiq/_internals/client.py +4 -7
  3. classiq/_internals/host_checker.py +34 -12
  4. classiq/_internals/jobs.py +2 -2
  5. classiq/applications/chemistry/chemistry_model_constructor.py +12 -6
  6. classiq/applications/combinatorial_helpers/allowed_constraints.py +4 -1
  7. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +1 -1
  8. classiq/applications/finance/finance_model_constructor.py +3 -2
  9. classiq/applications/grover/grover_model_constructor.py +7 -5
  10. classiq/applications/hamiltonian/__init__.py +0 -0
  11. classiq/applications/hamiltonian/pauli_decomposition.py +113 -0
  12. classiq/applications/qnn/qlayer.py +1 -1
  13. classiq/exceptions.py +4 -0
  14. classiq/interface/_version.py +1 -1
  15. classiq/interface/ast_node.py +1 -18
  16. classiq/interface/backend/backend_preferences.py +10 -16
  17. classiq/interface/backend/ionq/ionq_quantum_program.py +1 -1
  18. classiq/interface/backend/pydantic_backend.py +0 -5
  19. classiq/interface/backend/quantum_backend_providers.py +3 -2
  20. classiq/interface/chemistry/operator.py +5 -1
  21. classiq/interface/debug_info/__init__.py +0 -0
  22. classiq/interface/debug_info/debug_info.py +32 -0
  23. classiq/interface/executor/execution_preferences.py +1 -45
  24. classiq/interface/executor/result.py +25 -12
  25. classiq/interface/generator/application_apis/arithmetic_declarations.py +8 -5
  26. classiq/interface/generator/application_apis/chemistry_declarations.py +78 -60
  27. classiq/interface/generator/application_apis/combinatorial_optimization_declarations.py +19 -10
  28. classiq/interface/generator/application_apis/entangler_declarations.py +11 -6
  29. classiq/interface/generator/application_apis/finance_declarations.py +36 -22
  30. classiq/interface/generator/application_apis/qsvm_declarations.py +21 -15
  31. classiq/interface/generator/arith/arithmetic_expression_abc.py +21 -1
  32. classiq/interface/generator/arith/binary_ops.py +5 -4
  33. classiq/interface/generator/arith/extremum_operations.py +43 -19
  34. classiq/interface/generator/constant.py +1 -1
  35. classiq/interface/generator/expressions/atomic_expression_functions.py +1 -0
  36. classiq/interface/generator/expressions/expression_constants.py +3 -1
  37. classiq/interface/generator/expressions/qmod_qarray_proxy.py +52 -66
  38. classiq/interface/generator/expressions/qmod_qstruct_proxy.py +35 -0
  39. classiq/interface/generator/expressions/sympy_supported_expressions.py +2 -1
  40. classiq/interface/generator/functions/builtins/core_library/__init__.py +4 -2
  41. classiq/interface/generator/functions/builtins/core_library/atomic_quantum_functions.py +41 -41
  42. classiq/interface/generator/functions/builtins/core_library/exponentiation_functions.py +52 -42
  43. classiq/interface/generator/functions/builtins/open_lib_functions.py +1095 -3347
  44. classiq/interface/generator/functions/builtins/quantum_operators.py +9 -22
  45. classiq/interface/generator/functions/classical_function_declaration.py +14 -6
  46. classiq/interface/generator/functions/classical_type.py +7 -76
  47. classiq/interface/generator/functions/concrete_types.py +55 -0
  48. classiq/interface/generator/functions/function_declaration.py +10 -10
  49. classiq/interface/generator/functions/type_name.py +104 -0
  50. classiq/interface/generator/generated_circuit_data.py +3 -3
  51. classiq/interface/generator/model/model.py +11 -0
  52. classiq/interface/generator/model/preferences/preferences.py +5 -0
  53. classiq/interface/generator/quantum_function_call.py +3 -0
  54. classiq/interface/generator/quantum_program.py +2 -2
  55. classiq/interface/generator/register_role.py +7 -1
  56. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +1 -3
  57. classiq/interface/generator/types/builtin_struct_declarations/pauli_struct_declarations.py +1 -2
  58. classiq/interface/generator/types/qstruct_declaration.py +17 -0
  59. classiq/interface/generator/types/struct_declaration.py +1 -1
  60. classiq/interface/helpers/validation_helpers.py +1 -17
  61. classiq/interface/ide/visual_model.py +9 -2
  62. classiq/interface/interface_version.py +1 -0
  63. classiq/interface/model/bind_operation.py +25 -5
  64. classiq/interface/model/classical_parameter_declaration.py +8 -5
  65. classiq/interface/model/control.py +5 -5
  66. classiq/interface/model/handle_binding.py +185 -12
  67. classiq/interface/model/inplace_binary_operation.py +16 -4
  68. classiq/interface/model/model.py +28 -5
  69. classiq/interface/model/native_function_definition.py +8 -4
  70. classiq/interface/model/parameter.py +14 -0
  71. classiq/interface/model/port_declaration.py +20 -2
  72. classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +21 -6
  73. classiq/interface/model/quantum_expressions/arithmetic_operation.py +30 -6
  74. classiq/interface/model/quantum_expressions/quantum_expression.py +4 -9
  75. classiq/interface/model/quantum_function_call.py +135 -192
  76. classiq/interface/model/quantum_function_declaration.py +147 -165
  77. classiq/interface/model/quantum_lambda_function.py +24 -6
  78. classiq/interface/model/quantum_statement.py +34 -8
  79. classiq/interface/model/quantum_type.py +61 -10
  80. classiq/interface/model/quantum_variable_declaration.py +1 -1
  81. classiq/interface/model/statement_block.py +2 -0
  82. classiq/interface/model/validation_handle.py +7 -0
  83. classiq/interface/server/global_versions.py +4 -4
  84. classiq/interface/server/routes.py +2 -0
  85. classiq/interface/source_reference.py +59 -0
  86. classiq/qmod/__init__.py +2 -3
  87. classiq/qmod/builtins/functions.py +39 -11
  88. classiq/qmod/builtins/operations.py +171 -40
  89. classiq/qmod/declaration_inferrer.py +99 -56
  90. classiq/qmod/expression_query.py +1 -1
  91. classiq/qmod/model_state_container.py +2 -0
  92. classiq/qmod/native/pretty_printer.py +71 -53
  93. classiq/qmod/pretty_print/pretty_printer.py +98 -52
  94. classiq/qmod/qfunc.py +11 -5
  95. classiq/qmod/qmod_parameter.py +1 -2
  96. classiq/qmod/qmod_variable.py +364 -172
  97. classiq/qmod/quantum_callable.py +3 -3
  98. classiq/qmod/quantum_expandable.py +119 -65
  99. classiq/qmod/quantum_function.py +15 -3
  100. classiq/qmod/semantics/annotation.py +12 -13
  101. classiq/qmod/semantics/error_manager.py +36 -10
  102. classiq/qmod/semantics/static_semantics_visitor.py +163 -75
  103. classiq/qmod/semantics/validation/func_call_validation.py +42 -96
  104. classiq/qmod/semantics/validation/handle_validation.py +85 -0
  105. classiq/qmod/semantics/validation/types_validation.py +108 -1
  106. classiq/qmod/type_attribute_remover.py +32 -0
  107. classiq/qmod/utilities.py +26 -5
  108. {classiq-0.43.3.dist-info → classiq-0.44.0.dist-info}/METADATA +3 -3
  109. {classiq-0.43.3.dist-info → classiq-0.44.0.dist-info}/RECORD +111 -99
  110. classiq/qmod/qmod_struct.py +0 -13
  111. /classiq/{interface/ide/show.py → show.py} +0 -0
  112. {classiq-0.43.3.dist-info → classiq-0.44.0.dist-info}/WHEEL +0 -0
@@ -8,30 +8,40 @@ from typing import ( # type: ignore[attr-defined]
8
8
  Generic,
9
9
  Iterator,
10
10
  Literal,
11
+ Mapping,
11
12
  Optional,
12
13
  Tuple,
13
14
  Type,
14
15
  TypeVar,
15
16
  Union,
16
17
  _GenericAlias,
18
+ cast,
17
19
  get_args,
18
20
  get_origin,
19
- overload,
20
21
  )
21
22
 
22
23
  from typing_extensions import Annotated, ParamSpec, Self, _AnnotatedAlias
23
24
 
24
- from classiq.interface.ast_node import SourceReference
25
25
  from classiq.interface.generator.expressions.expression import Expression
26
+ from classiq.interface.generator.expressions.qmod_qarray_proxy import (
27
+ ILLEGAL_SLICE_BOUNDS_MSG,
28
+ ILLEGAL_SLICE_MSG,
29
+ ILLEGAL_SLICING_STEP_MSG,
30
+ SLICE_OUT_OF_BOUNDS_MSG,
31
+ SUBSCRIPT_OUT_OF_BOUNDS_MSG,
32
+ )
26
33
  from classiq.interface.generator.functions.port_declaration import (
27
34
  PortDeclarationDirection,
28
35
  )
36
+ from classiq.interface.generator.functions.type_name import TypeName
37
+ from classiq.interface.generator.types.qstruct_declaration import QStructDeclaration
29
38
  from classiq.interface.model.handle_binding import (
39
+ FieldHandleBinding,
30
40
  HandleBinding,
31
41
  SlicedHandleBinding,
32
42
  SubscriptHandleBinding,
33
43
  )
34
- from classiq.interface.model.port_declaration import PortDeclaration
44
+ from classiq.interface.model.port_declaration import AnonPortDeclaration
35
45
  from classiq.interface.model.quantum_expressions.amplitude_loading_operation import (
36
46
  AmplitudeLoadingOperation,
37
47
  )
@@ -44,18 +54,26 @@ from classiq.interface.model.quantum_type import (
44
54
  QuantumNumeric,
45
55
  QuantumType,
46
56
  )
57
+ from classiq.interface.source_reference import SourceReference
47
58
 
48
59
  from classiq.exceptions import ClassiqValueError
49
- from classiq.qmod.qmod_parameter import ArrayBase, CBool, CInt, CParam, CParamScalar
60
+ from classiq.qmod.model_state_container import QMODULE, ModelStateContainer
61
+ from classiq.qmod.qmod_parameter import ArrayBase, CBool, CInt, CParamScalar
50
62
  from classiq.qmod.quantum_callable import QCallable
51
63
  from classiq.qmod.symbolic_expr import Symbolic, SymbolicExpr
52
64
  from classiq.qmod.symbolic_type import SymbolicTypes
53
65
  from classiq.qmod.utilities import get_source_ref, version_portable_get_args
54
66
 
55
- ILLEGAL_SLICING_STEP_MSG = "Slicing with a step of a quantum variable is not supported"
56
- SLICE_OUT_OF_BOUNDS_MSG = "Slice end index out of bounds"
57
- UNSUPPORTED_ELEMENT_TYPE = "Only QBit and QNum are supported as element type for QArray"
58
- QARRAY_ELEMENT_NOT_SUBSCRIPTABLE = "Subscripting an element in QArray is illegal"
67
+ QVAR_PROPERTIES_ARE_SYMBOLIC = True
68
+
69
+
70
+ @contextmanager
71
+ def set_symbolic_qvar_properties(symbolic: bool) -> Iterator[None]:
72
+ global QVAR_PROPERTIES_ARE_SYMBOLIC
73
+ previous_symbolic = QVAR_PROPERTIES_ARE_SYMBOLIC
74
+ QVAR_PROPERTIES_ARE_SYMBOLIC = symbolic
75
+ yield
76
+ QVAR_PROPERTIES_ARE_SYMBOLIC = previous_symbolic
59
77
 
60
78
 
61
79
  def _is_input_output_typehint(type_hint: Any) -> bool:
@@ -84,18 +102,30 @@ def _no_current_expandable() -> Iterator[None]:
84
102
 
85
103
 
86
104
  class QVar(Symbolic):
87
- def __init__(self, name: str, depth: int = 2) -> None:
88
- super().__init__(name, True)
89
- self._name = name
90
- source_ref = get_source_ref(sys._getframe(depth))
91
- if QCallable.CURRENT_EXPANDABLE is not None:
105
+ def __init__(
106
+ self,
107
+ origin: Union[str, HandleBinding],
108
+ *,
109
+ expr_str: Optional[str] = None,
110
+ depth: int = 2,
111
+ ) -> None:
112
+ super().__init__(str(origin), True)
113
+ source_ref = (
114
+ get_source_ref(sys._getframe(depth))
115
+ if isinstance(origin, str)
116
+ else origin.source_ref
117
+ )
118
+ self._base_handle: HandleBinding = (
119
+ HandleBinding(name=origin) if isinstance(origin, str) else origin
120
+ )
121
+ if isinstance(origin, str) and QCallable.CURRENT_EXPANDABLE is not None:
92
122
  QCallable.CURRENT_EXPANDABLE.add_local_handle(
93
- self._name, self.get_qmod_type(), source_ref
123
+ origin, self.get_qmod_type(), source_ref
94
124
  )
125
+ self._expr_str = expr_str if expr_str is not None else str(origin)
95
126
 
96
- @abc.abstractmethod
97
127
  def get_handle_binding(self) -> HandleBinding:
98
- raise NotImplementedError()
128
+ return self._base_handle
99
129
 
100
130
  @abc.abstractmethod
101
131
  def get_qmod_type(self) -> QuantumType:
@@ -107,6 +137,9 @@ class QVar(Symbolic):
107
137
  return QVar.from_type_hint(type_hint.__args__[0])
108
138
  type_ = get_origin(type_hint) or type_hint
109
139
  if issubclass(type_, QVar):
140
+ if issubclass(type_, QStruct):
141
+ with _no_current_expandable():
142
+ type_("DUMMY")._add_qmod_qstruct(qmodule=QMODULE)
110
143
  return type_
111
144
  return None
112
145
 
@@ -115,6 +148,16 @@ class QVar(Symbolic):
115
148
  def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
116
149
  raise NotImplementedError()
117
150
 
151
+ @classmethod
152
+ @abc.abstractmethod
153
+ def to_qvar(
154
+ cls,
155
+ origin: Union[str, HandleBinding],
156
+ type_hint: Any,
157
+ expr_str: Optional[str],
158
+ ) -> Self:
159
+ raise NotImplementedError()
160
+
118
161
  @classmethod
119
162
  def port_direction(cls, type_hint: Any) -> PortDeclarationDirection:
120
163
  if _is_input_output_typehint(type_hint):
@@ -124,7 +167,7 @@ class QVar(Symbolic):
124
167
  return PortDeclarationDirection.Inout
125
168
 
126
169
  def __str__(self) -> str:
127
- return str(self.get_handle_binding())
170
+ return self._expr_str
128
171
 
129
172
 
130
173
  _Q = TypeVar("_Q", bound=QVar)
@@ -133,9 +176,15 @@ Input = Annotated[_Q, PortDeclarationDirection.Input]
133
176
 
134
177
 
135
178
  class QScalar(QVar, SymbolicExpr):
136
- def __init__(self, name: str, depth: int = 2) -> None:
137
- QVar.__init__(self, name, depth)
138
- SymbolicExpr.__init__(self, name, True)
179
+ def __init__(
180
+ self,
181
+ origin: Union[str, HandleBinding],
182
+ *,
183
+ _expr_str: Optional[str] = None,
184
+ depth: int = 2,
185
+ ) -> None:
186
+ QVar.__init__(self, origin, expr_str=_expr_str, depth=depth)
187
+ SymbolicExpr.__init__(self, str(origin), True)
139
188
 
140
189
  def _insert_arith_operation(
141
190
  self, expr: SymbolicTypes, inplace: bool, source_ref: SourceReference
@@ -165,9 +214,6 @@ class QScalar(QVar, SymbolicExpr):
165
214
  )
166
215
  )
167
216
 
168
- def get_handle_binding(self) -> HandleBinding:
169
- return HandleBinding(name=self._name)
170
-
171
217
  def __ior__(self, other: Any) -> Self:
172
218
  if not isinstance(other, get_args(SymbolicTypes)):
173
219
  raise TypeError(
@@ -201,6 +247,15 @@ class QBit(QScalar):
201
247
  def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
202
248
  return QuantumBit()
203
249
 
250
+ @classmethod
251
+ def to_qvar(
252
+ cls,
253
+ origin: Union[str, HandleBinding],
254
+ type_hint: Any,
255
+ expr_str: Optional[str],
256
+ ) -> "QBit":
257
+ return QBit(origin, _expr_str=expr_str)
258
+
204
259
  def get_qmod_type(self) -> QuantumType:
205
260
  return QuantumBit()
206
261
 
@@ -209,28 +264,13 @@ _P = ParamSpec("_P")
209
264
 
210
265
 
211
266
  class QNum(Generic[_P], QScalar):
212
- QMOD_TYPE = QuantumNumeric
213
-
214
- @overload
215
- def __init__(self, name: str):
216
- pass
217
-
218
- @overload
219
267
  def __init__(
220
268
  self,
221
- name: str,
222
- size: Union[int, CInt],
223
- is_signed: Union[bool, CBool],
224
- fraction_digits: Union[int, CInt],
225
- ):
226
- pass
227
-
228
- def __init__(
229
- self,
230
- name: str,
231
- size: Union[int, CInt, None] = None,
232
- is_signed: Union[bool, CBool, None] = None,
233
- fraction_digits: Union[int, CInt, None] = None,
269
+ name: Union[str, HandleBinding],
270
+ size: Union[int, CInt, Expression, None] = None,
271
+ is_signed: Union[bool, CBool, Expression, None] = None,
272
+ fraction_digits: Union[int, CInt, Expression, None] = None,
273
+ _expr_str: Optional[str] = None,
234
274
  ):
235
275
  if (
236
276
  size is None
@@ -241,48 +281,93 @@ class QNum(Generic[_P], QScalar):
241
281
  raise ClassiqValueError(
242
282
  "Assign none or all of size, is_signed, and fraction_digits"
243
283
  )
244
- self._size = None if size is None else Expression(expr=str(size))
245
- self._is_signed = None if is_signed is None else Expression(expr=str(is_signed))
284
+ self._size = (
285
+ size
286
+ if size is None or isinstance(size, Expression)
287
+ else Expression(expr=str(size))
288
+ )
289
+ self._is_signed = (
290
+ is_signed
291
+ if is_signed is None or isinstance(is_signed, Expression)
292
+ else Expression(expr=str(is_signed))
293
+ )
246
294
  self._fraction_digits = (
247
- None if fraction_digits is None else Expression(expr=str(fraction_digits))
295
+ fraction_digits
296
+ if fraction_digits is None or isinstance(fraction_digits, Expression)
297
+ else Expression(expr=str(fraction_digits))
248
298
  )
249
- super().__init__(name, 3)
299
+ super().__init__(name, _expr_str=_expr_str, depth=3)
250
300
 
251
301
  @classmethod
252
- def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
253
- type_args = get_args(type_hint)
302
+ def _get_attributes(cls, type_hint: Any) -> Tuple[Any, Any, Any]:
303
+ type_args = version_portable_get_args(type_hint)
254
304
  if len(type_args) == 0:
255
- return cls.QMOD_TYPE()
256
- type_args = type_args[0]
305
+ return None, None, None
257
306
  if len(type_args) != 3:
258
307
  raise ClassiqValueError(
259
308
  "QNum receives three type arguments: QNum[size: int | CInt, "
260
309
  "is_signed: bool | CBool, fraction_digits: int | CInt]"
261
310
  )
262
- return cls.QMOD_TYPE(
263
- size=Expression(expr=get_type_hint_expr(type_args[0])),
264
- is_signed=Expression(expr=get_type_hint_expr(type_args[1])),
265
- fraction_digits=Expression(expr=get_type_hint_expr(type_args[2])),
311
+ return type_args[0], type_args[1], type_args[2]
312
+
313
+ @classmethod
314
+ def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
315
+ size, is_signed, fraction_digits = cls._get_attributes(type_hint)
316
+ return QuantumNumeric(
317
+ size=(
318
+ Expression(expr=get_type_hint_expr(size)) if size is not None else None
319
+ ),
320
+ is_signed=(
321
+ Expression(expr=get_type_hint_expr(is_signed))
322
+ if is_signed is not None
323
+ else None
324
+ ),
325
+ fraction_digits=(
326
+ Expression(expr=get_type_hint_expr(fraction_digits))
327
+ if fraction_digits is not None
328
+ else None
329
+ ),
266
330
  )
267
331
 
332
+ @classmethod
333
+ def to_qvar(
334
+ cls,
335
+ origin: Union[str, HandleBinding],
336
+ type_hint: Any,
337
+ expr_str: Optional[str],
338
+ ) -> "QNum":
339
+ return QNum(origin, *cls._get_attributes(type_hint), _expr_str=expr_str)
340
+
268
341
  def get_qmod_type(self) -> QuantumType:
269
- return self.QMOD_TYPE(
342
+ return QuantumNumeric(
270
343
  size=self._size,
271
344
  is_signed=self._is_signed,
272
345
  fraction_digits=self._fraction_digits,
273
346
  )
274
347
 
275
348
  @property
276
- def size(self) -> CParamScalar:
277
- return CParamScalar(f"get_field({self._name}, 'size')")
349
+ def size(self) -> Union[CParamScalar, int]:
350
+ if not QVAR_PROPERTIES_ARE_SYMBOLIC:
351
+ if TYPE_CHECKING:
352
+ assert self._size is not None
353
+ return self._size.to_int_value()
354
+ return CParamScalar(f"get_field({self}, 'size')")
278
355
 
279
356
  @property
280
- def fraction_digits(self) -> CParamScalar:
281
- return CParamScalar(f"get_field({self._name}, 'fraction_digits')")
357
+ def fraction_digits(self) -> Union[CParamScalar, int]:
358
+ if not QVAR_PROPERTIES_ARE_SYMBOLIC:
359
+ if TYPE_CHECKING:
360
+ assert self._fraction_digits is not None
361
+ return self._fraction_digits.to_int_value()
362
+ return CParamScalar(f"get_field({self}, 'fraction_digits')")
282
363
 
283
364
  @property
284
- def is_signed(self) -> CParamScalar:
285
- return CParamScalar(f"get_field({self._name}, 'is_signed')")
365
+ def is_signed(self) -> Union[CParamScalar, bool]:
366
+ if not QVAR_PROPERTIES_ARE_SYMBOLIC:
367
+ if TYPE_CHECKING:
368
+ assert self._is_signed is not None
369
+ return self._is_signed.to_bool_value()
370
+ return CParamScalar(f"get_field({self}, 'is_signed')")
286
371
 
287
372
  # Support comma-separated generic args in older Python versions
288
373
  if sys.version_info[0:2] < (3, 10):
@@ -292,84 +377,83 @@ class QNum(Generic[_P], QScalar):
292
377
 
293
378
 
294
379
  class QArray(ArrayBase[_P], QVar):
380
+ # TODO [CAD-18620]: improve type hints
295
381
  def __init__(
296
382
  self,
297
- name: str,
298
- element_type: _GenericAlias = QBit,
299
- length: Optional[Union[int, CInt]] = None,
300
- # TODO [CAD-18620]: improve type hints
301
- slice_: Optional[Tuple[int, int]] = None,
302
- index_: Optional[Union[int, CInt]] = None,
383
+ name: Union[str, HandleBinding],
384
+ element_type: Union[_GenericAlias, QuantumType] = QBit,
385
+ length: Optional[Union[int, SymbolicExpr, Expression]] = None,
386
+ _expr_str: Optional[str] = None,
303
387
  ) -> None:
304
- if not issubclass(get_origin(element_type) or element_type, (QBit, QNum)):
305
- raise ClassiqValueError(UNSUPPORTED_ELEMENT_TYPE)
306
388
  self._element_type = element_type
307
- self._length = length
308
- self._slice = slice_
309
- self._index = index_
310
- super().__init__(name)
311
-
312
- def get_handle_binding(self) -> HandleBinding:
313
- if self._index is not None:
314
- return SubscriptHandleBinding(
315
- name=self._name,
316
- index=Expression(expr=str(self._index)),
317
- )
318
-
319
- if self._slice is not None:
320
- return SlicedHandleBinding(
321
- name=self._name,
322
- start=Expression(expr=str(self._slice[0])),
323
- end=Expression(expr=str(self._slice[1])),
324
- )
325
-
326
- return HandleBinding(name=self._name)
327
-
328
- def __getitem__(self, key: Union[slice, int, CInt]) -> Any:
329
- if self._index is not None:
330
- raise ClassiqValueError(QARRAY_ELEMENT_NOT_SUBSCRIPTABLE)
331
-
332
- # TODO [CAD-18620]: improve type hints
333
- new_index: Optional[Any] = None
389
+ self._length = (
390
+ length
391
+ if length is None or isinstance(length, Expression)
392
+ else Expression(expr=str(length))
393
+ )
394
+ super().__init__(name, expr_str=_expr_str)
334
395
 
335
- if isinstance(key, slice):
336
- if key.step is not None:
337
- raise ClassiqValueError(ILLEGAL_SLICING_STEP_MSG)
338
- new_slice = self._get_new_slice(key.start, key.stop)
396
+ def __getitem__(self, key: Union[slice, int, SymbolicExpr]) -> Any:
397
+ return (
398
+ self._get_slice(key) if isinstance(key, slice) else self._get_subscript(key)
399
+ )
339
400
 
340
- else:
341
- if isinstance(key, CParam) and not isinstance(key, CParamScalar):
342
- raise ClassiqValueError("Non-classical parameter for slicing")
343
- new_slice = self._get_new_slice(key, key + 1)
344
- new_index = new_slice[0]
401
+ def _get_subscript(self, index: Union[slice, int, SymbolicExpr]) -> Any:
402
+ if isinstance(index, SymbolicExpr) and index.is_quantum:
403
+ raise ClassiqValueError("Non-classical parameter for slicing")
404
+ if (
405
+ isinstance(index, int)
406
+ and self._length is not None
407
+ and self._length.is_evaluated()
408
+ ):
409
+ length = self._length.to_int_value()
410
+ if index < 0 or index >= length:
411
+ raise ClassiqValueError(SUBSCRIPT_OUT_OF_BOUNDS_MSG)
412
+
413
+ return _create_qvar_for_qtype(
414
+ self.get_qmod_type().element_type,
415
+ SubscriptHandleBinding(
416
+ base_handle=self._base_handle,
417
+ index=Expression(expr=str(index)),
418
+ ),
419
+ expr_str=f"{self}[{index}]",
420
+ )
345
421
 
422
+ def _get_slice(self, slice_: slice) -> Any:
423
+ if slice_.step is not None:
424
+ raise ClassiqValueError(ILLEGAL_SLICING_STEP_MSG)
425
+ if not isinstance(slice_.start, (int, SymbolicExpr)) or not isinstance(
426
+ slice_.stop, (int, SymbolicExpr)
427
+ ):
428
+ raise ClassiqValueError(ILLEGAL_SLICE_MSG)
346
429
  if (
347
- self._slice is not None
348
- and not isinstance(new_slice[1], Symbolic)
349
- and not isinstance(self._slice[1], Symbolic)
350
- and new_slice[1] > self._slice[1]
351
- ) or (
352
- self._length is not None
353
- and not isinstance(new_slice[1], Symbolic)
354
- and not isinstance(self._length, Symbolic)
355
- and new_slice[1] > self._length
430
+ isinstance(slice_.start, int)
431
+ and isinstance(slice_.stop, int)
432
+ and slice_.start >= slice_.stop
356
433
  ):
357
- raise ClassiqValueError(SLICE_OUT_OF_BOUNDS_MSG)
358
- # prevent addition to local handles, since this is used for slicing existing local handles
359
- with _no_current_expandable():
360
- if new_index is None:
361
- array_class = QArray
362
- else:
363
- array_class = QArraySubscript
364
- return array_class(
365
- self._name, length=self._length, slice_=new_slice, index_=new_index
434
+ raise ClassiqValueError(
435
+ ILLEGAL_SLICE_BOUNDS_MSG.format(slice_.start, slice_.stop)
366
436
  )
367
-
368
- # TODO [CAD-18620]: improve type hints
369
- def _get_new_slice(self, start: Any, end: Any) -> Tuple[Any, Any]:
370
- if self._slice is not None:
371
- return (self._slice[0] + start, self._slice[0] + end)
372
- return (start, end)
437
+ if self._length is not None and self._length.is_evaluated():
438
+ length = self._length.to_int_value()
439
+ if (
440
+ isinstance(slice_.start, int)
441
+ and slice_.start < 0
442
+ or isinstance(slice_.stop, int)
443
+ and slice_.stop > length
444
+ ):
445
+ raise ClassiqValueError(SLICE_OUT_OF_BOUNDS_MSG)
446
+
447
+ return QArray(
448
+ name=SlicedHandleBinding(
449
+ base_handle=self._base_handle,
450
+ start=Expression(expr=str(slice_.start)),
451
+ end=Expression(expr=str(slice_.stop)),
452
+ ),
453
+ element_type=self._element_type,
454
+ length=slice_.stop - slice_.start,
455
+ _expr_str=f"{self}[{slice_.start}:{slice_.stop}]",
456
+ )
373
457
 
374
458
  def __len__(self) -> int:
375
459
  raise ClassiqValueError(
@@ -384,69 +468,177 @@ class QArray(ArrayBase[_P], QVar):
384
468
  else:
385
469
 
386
470
  @property
387
- def len(self) -> CParamScalar:
471
+ def len(self) -> Union[CParamScalar, int]:
472
+ if not QVAR_PROPERTIES_ARE_SYMBOLIC:
473
+ return self._length.to_int_value()
388
474
  if self._length is not None:
389
475
  return CParamScalar(f"{self._length}")
390
- return CParamScalar(f"get_field({self._name}, 'len')")
476
+ return CParamScalar(f"get_field({self}, 'len')")
391
477
 
392
478
  @classmethod
393
- def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
479
+ def _get_attributes(cls, type_hint: Any) -> Tuple[Type[QVar], Any]:
394
480
  type_args = version_portable_get_args(type_hint)
395
- if len(type_args) == 1 and isinstance(type_args[0], (str, int)):
396
- type_args = (QBit, type_args[0])
481
+ if len(type_args) == 0:
482
+ return QBit, None
483
+ if len(type_args) == 1:
484
+ if isinstance(type_args[0], (str, int)):
485
+ return QBit, type_args[0]
486
+ return type_args[0], None
487
+ if len(type_args) != 2:
488
+ raise ClassiqValueError(
489
+ "QArray receives two type arguments: QArray[element_type: QVar, "
490
+ "length: int | CInt]"
491
+ )
492
+ return cast(Tuple[Type[QVar], Any], type_args)
397
493
 
398
- api_element_type = QBit if len(type_args) == 0 else type_args[0]
494
+ @classmethod
495
+ def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
496
+ api_element_type, length = cls._get_attributes(type_hint)
399
497
  api_element_class = get_origin(api_element_type) or api_element_type
400
498
  element_type = api_element_class.to_qmod_quantum_type(api_element_type)
401
499
 
402
500
  length_expr: Optional[Expression] = None
403
- if len(type_args) == 2:
404
- length_expr = Expression(expr=get_type_hint_expr(type_args[1]))
501
+ if length is not None:
502
+ length_expr = Expression(expr=get_type_hint_expr(length))
405
503
 
406
504
  return QuantumBitvector(element_type=element_type, length=length_expr)
407
505
 
408
- def get_qmod_type(self) -> QuantumType:
409
- element_class = get_origin(self._element_type) or self._element_type
410
- length = None
411
- if self._length is not None:
412
- length = Expression(expr=str(self._length))
506
+ @classmethod
507
+ def to_qvar(
508
+ cls,
509
+ origin: Union[str, HandleBinding],
510
+ type_hint: Any,
511
+ expr_str: Optional[str],
512
+ ) -> "QArray":
513
+ return QArray(origin, *cls._get_attributes(type_hint), _expr_str=expr_str)
514
+
515
+ def get_qmod_type(self) -> QuantumBitvector:
516
+ if isinstance(self._element_type, QuantumType):
517
+ element_type = self._element_type
518
+ else:
519
+ element_class = get_origin(self._element_type) or self._element_type
520
+ element_type = element_class.to_qmod_quantum_type(self._element_type)
413
521
  return QuantumBitvector(
414
- element_type=element_class.to_qmod_quantum_type(self._element_type),
415
- length=length,
522
+ element_type=element_type,
523
+ length=self._length,
416
524
  )
417
525
 
418
526
 
419
- class QArraySubscript(QArray, QScalar):
420
- @property
421
- def size(self) -> CParamScalar:
422
- return CParamScalar(f"get_field({self.get_handle_binding()}, 'size')")
527
+ class QStruct(QVar):
528
+ _struct_name: str
529
+ _fields: Mapping[str, QVar]
423
530
 
424
- @property
425
- def fraction_digits(self) -> CParamScalar:
426
- return CParamScalar(
427
- f"get_field({self.get_handle_binding()}, 'fraction_digits')"
531
+ def __init__(
532
+ self,
533
+ name: Union[str, HandleBinding],
534
+ _struct_name: Optional[str] = None,
535
+ _fields: Optional[Mapping[str, QVar]] = None,
536
+ _expr_str: Optional[str] = None,
537
+ ) -> None:
538
+ if _struct_name is None or _fields is None:
539
+ with _no_current_expandable():
540
+ temp_var = QStruct.to_qvar(name, type(self), _expr_str)
541
+ _struct_name = temp_var._struct_name
542
+ _fields = temp_var._fields
543
+ self._struct_name = _struct_name
544
+ self._fields = _fields
545
+ for field_name, var in _fields.items():
546
+ setattr(self, field_name, var)
547
+ super().__init__(name)
548
+ self._add_qmod_qstruct(qmodule=QMODULE)
549
+
550
+ def get_qmod_type(self) -> QuantumType:
551
+ return TypeName(name=self._struct_name)
552
+
553
+ @classmethod
554
+ def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
555
+ with _no_current_expandable():
556
+ type_hint("DUMMY")
557
+ return TypeName(name=type_hint.__name__)
558
+
559
+ @classmethod
560
+ def to_qvar(
561
+ cls,
562
+ origin: Union[str, HandleBinding],
563
+ type_hint: Any,
564
+ expr_str: Optional[str],
565
+ ) -> "QStruct":
566
+ field_types = {
567
+ field_name: (QVar.from_type_hint(field_type), field_type)
568
+ for field_name, field_type in type_hint.__annotations__.items()
569
+ }
570
+ illegal_fields = [
571
+ (field_name, field_type)
572
+ for field_name, (field_class, field_type) in field_types.items()
573
+ if field_class is None
574
+ ]
575
+ if len(illegal_fields) > 0:
576
+ raise ClassiqValueError(
577
+ f"Field {illegal_fields[0][0]!r} of quantum struct "
578
+ f"{type_hint.__name__} has a non-quantum type "
579
+ f"{illegal_fields[0][1].__name__}."
580
+ )
581
+ base_handle = HandleBinding(name=origin) if isinstance(origin, str) else origin
582
+ with _no_current_expandable():
583
+ field_vars = {
584
+ field_name: cast(Type[QVar], field_class).to_qvar(
585
+ FieldHandleBinding(base_handle=base_handle, field=field_name),
586
+ field_type,
587
+ f"get_field({expr_str if expr_str is not None else str(origin)}, '{field_name}')",
588
+ )
589
+ for field_name, (field_class, field_type) in field_types.items()
590
+ }
591
+ return QStruct(
592
+ name=origin,
593
+ _struct_name=type_hint.__name__,
594
+ _fields=field_vars,
595
+ _expr_str=expr_str,
596
+ )
597
+
598
+ def _add_qmod_qstruct(self, *, qmodule: ModelStateContainer) -> None:
599
+ if self._struct_name in qmodule.qstruct_decls:
600
+ return
601
+
602
+ qmodule.qstruct_decls[self._struct_name] = QStructDeclaration(
603
+ name=self._struct_name,
604
+ fields={name: qvar.get_qmod_type() for name, qvar in self._fields.items()},
428
605
  )
429
606
 
430
- @property
431
- def is_signed(self) -> CParamScalar:
432
- return CParamScalar(f"get_field({self.get_handle_binding()}, 'is_signed')")
433
607
 
608
+ def create_qvar_for_port_decl(port: AnonPortDeclaration, name: str) -> QVar:
609
+ return _create_qvar_for_qtype(port.quantum_type, HandleBinding(name=name))
434
610
 
435
- def create_qvar_for_port_decl(port: PortDeclaration, name: str) -> QVar:
611
+
612
+ def _create_qvar_for_qtype(
613
+ qtype: QuantumType, origin: HandleBinding, expr_str: Optional[str] = None
614
+ ) -> QVar:
436
615
  # prevent addition to local handles, since this is used for ports
437
616
  with _no_current_expandable():
438
- if _is_single_qbit_vector(port):
439
- return QBit(name)
440
- elif isinstance(port.quantum_type, QuantumNumeric):
441
- return QNum(name)
442
- return QArray(name)
443
-
444
-
445
- def _is_single_qbit_vector(port: PortDeclaration) -> bool:
446
- return (
447
- isinstance(port.quantum_type, QuantumBit)
448
- or isinstance(port.quantum_type, QuantumBitvector)
449
- and port.size is not None
450
- and port.size.is_evaluated()
451
- and port.size.to_int_value() == 1
452
- )
617
+ if isinstance(qtype, QuantumBit):
618
+ return QBit(origin, _expr_str=expr_str)
619
+ elif isinstance(qtype, QuantumNumeric):
620
+ return QNum(
621
+ origin,
622
+ qtype.size,
623
+ qtype.is_signed,
624
+ qtype.fraction_digits,
625
+ _expr_str=expr_str,
626
+ )
627
+ elif isinstance(qtype, TypeName):
628
+ struct_decl = QMODULE.qstruct_decls[qtype.name]
629
+ return QStruct(
630
+ origin,
631
+ struct_decl.name,
632
+ {
633
+ field_name: _create_qvar_for_qtype(
634
+ field_type,
635
+ FieldHandleBinding(base_handle=origin, field=field_name),
636
+ f"get_field({expr_str if expr_str is not None else str(origin)}, '{field_name}')",
637
+ )
638
+ for field_name, field_type in struct_decl.fields.items()
639
+ },
640
+ _expr_str=expr_str,
641
+ )
642
+ if TYPE_CHECKING:
643
+ assert isinstance(qtype, QuantumBitvector)
644
+ return QArray(origin, qtype.element_type, qtype.length, _expr_str=expr_str)