classiq 0.79.1__py3-none-any.whl → 0.80.1__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 (46) hide show
  1. classiq/__init__.py +7 -0
  2. classiq/_internals/api_wrapper.py +95 -19
  3. classiq/analyzer/show_interactive_hack.py +63 -48
  4. classiq/applications/chemistry/chemistry_model_constructor.py +1 -1
  5. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +3 -3
  6. classiq/interface/_version.py +1 -1
  7. classiq/interface/analyzer/result.py +1 -1
  8. classiq/interface/chemistry/operator.py +3 -8
  9. classiq/interface/executor/quantum_program_params.py +18 -0
  10. classiq/interface/generator/application_apis/chemistry_declarations.py +3 -3
  11. classiq/interface/generator/application_apis/combinatorial_optimization_declarations.py +3 -3
  12. classiq/interface/generator/application_apis/entangler_declarations.py +3 -3
  13. classiq/interface/generator/arith/number_utils.py +8 -0
  14. classiq/interface/generator/expressions/proxies/classical/utils.py +8 -3
  15. classiq/interface/generator/functions/classical_type.py +63 -7
  16. classiq/interface/generator/generated_circuit_data.py +10 -1
  17. classiq/interface/helpers/custom_pydantic_types.py +2 -3
  18. classiq/interface/model/allocate.py +6 -0
  19. classiq/interface/model/quantum_type.py +26 -5
  20. classiq/interface/server/routes.py +6 -0
  21. classiq/model_expansions/atomic_expression_functions_defs.py +9 -1
  22. classiq/model_expansions/capturing/captured_vars.py +1 -1
  23. classiq/model_expansions/evaluators/classical_type_inference.py +39 -27
  24. classiq/model_expansions/evaluators/parameter_types.py +65 -3
  25. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +1 -1
  26. classiq/model_expansions/quantum_operations/allocate.py +121 -34
  27. classiq/model_expansions/quantum_operations/call_emitter.py +1 -1
  28. classiq/model_expansions/scope.py +1 -2
  29. classiq/open_library/functions/__init__.py +2 -0
  30. classiq/open_library/functions/state_preparation.py +86 -3
  31. classiq/qmod/builtins/classical_execution_primitives.py +2 -4
  32. classiq/qmod/builtins/functions/__init__.py +3 -0
  33. classiq/qmod/builtins/functions/arithmetic.py +21 -1
  34. classiq/qmod/builtins/functions/exponentiation.py +24 -0
  35. classiq/qmod/builtins/operations.py +32 -2
  36. classiq/qmod/builtins/structs.py +47 -1
  37. classiq/qmod/native/pretty_printer.py +15 -6
  38. classiq/qmod/pretty_print/pretty_printer.py +15 -6
  39. classiq/qmod/python_classical_type.py +4 -4
  40. classiq/qmod/qmod_parameter.py +5 -1
  41. classiq/qmod/semantics/validation/main_validation.py +5 -0
  42. classiq/quantum_program.py +69 -0
  43. {classiq-0.79.1.dist-info → classiq-0.80.1.dist-info}/METADATA +1 -1
  44. {classiq-0.79.1.dist-info → classiq-0.80.1.dist-info}/RECORD +46 -44
  45. {classiq-0.79.1.dist-info → classiq-0.80.1.dist-info}/WHEEL +1 -1
  46. /classiq/{model_expansions/utils → interface/helpers}/text_utils.py +0 -0
@@ -1,12 +1,14 @@
1
1
  from itertools import chain
2
- from typing import TYPE_CHECKING, Any, Literal
2
+ from typing import TYPE_CHECKING, Any, Literal, Optional
3
3
 
4
4
  import pydantic
5
5
  from pydantic import ConfigDict, PrivateAttr
6
6
  from typing_extensions import Self
7
7
 
8
8
  from classiq.interface.ast_node import HashableASTNode
9
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
9
10
  from classiq.interface.generator.expressions.expression import Expression
11
+ from classiq.interface.generator.expressions.expression_types import ExpressionValue
10
12
  from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
11
13
  AnyClassicalValue,
12
14
  )
@@ -123,7 +125,7 @@ class ClassicalList(ClassicalType):
123
125
  return super().is_purely_generative and self.element_type.is_purely_generative
124
126
 
125
127
  def get_raw_type(self) -> "ConcreteClassicalType":
126
- raw_type = ClassicalList(element_type=self.element_type.get_raw_type())
128
+ raw_type = ClassicalArray(element_type=self.element_type.get_raw_type())
127
129
  if self._is_generative:
128
130
  raw_type.set_generative()
129
131
  return raw_type
@@ -144,15 +146,69 @@ class StructMetaType(ClassicalType):
144
146
  class ClassicalArray(ClassicalType):
145
147
  kind: Literal["array"]
146
148
  element_type: "ConcreteClassicalType"
147
- size: int
149
+ size: Optional[int] = None
150
+ length: Optional[Expression] = pydantic.Field(exclude=True, default=None)
148
151
 
149
152
  @pydantic.model_validator(mode="before")
150
153
  @classmethod
151
154
  def _set_kind(cls, values: Any) -> dict[str, Any]:
152
155
  return values_with_discriminator(values, "kind", "array")
153
156
 
157
+ @pydantic.model_validator(mode="before")
158
+ @classmethod
159
+ def _set_length(cls, values: Any) -> Any:
160
+ if isinstance(values, dict):
161
+ size = values.get("size")
162
+ length = values.get("length")
163
+ else:
164
+ size = values.size
165
+ length = values.length
166
+ if size is not None:
167
+ if isinstance(values, dict):
168
+ values["length"] = Expression(expr=str(size))
169
+ else:
170
+ values.length = Expression(expr=str(size))
171
+ elif length is not None:
172
+ if isinstance(length, dict):
173
+ expr = length["expr"]
174
+ else:
175
+ expr = length.expr
176
+ expr_size: Optional[int] = None
177
+ try: # noqa: SIM105
178
+ expr_size = int(expr)
179
+ except ValueError:
180
+ pass
181
+ if expr_size is not None:
182
+ if isinstance(values, dict):
183
+ values["size"] = expr_size
184
+ else:
185
+ values.size = expr_size
186
+ return values
187
+
188
+ @property
189
+ def has_length(self) -> bool:
190
+ return self.length is not None and self.length.is_evaluated()
191
+
192
+ @property
193
+ def length_value(self) -> int:
194
+ if not self.has_length:
195
+ raise ClassiqInternalExpansionError(
196
+ "Tried to access unevaluated length of classical array"
197
+ )
198
+ assert self.length is not None
199
+ return self.length.to_int_value()
200
+
154
201
  def get_classical_proxy(self, handle: HandleBinding) -> ClassicalProxy:
155
- return ClassicalArrayProxy(handle, self.element_type, self.size)
202
+ length: ExpressionValue
203
+ if self.length is None:
204
+ length = AnyClassicalValue(f"get_field({handle}, 'len')")
205
+ elif not self.length.is_evaluated():
206
+ raise ClassiqInternalExpansionError(
207
+ "Classical list length is not evaluated"
208
+ )
209
+ else:
210
+ length = self.length.value.value
211
+ return ClassicalArrayProxy(handle, self.element_type, length)
156
212
 
157
213
  @property
158
214
  def expressions(self) -> list[Expression]:
@@ -167,7 +223,7 @@ class ClassicalArray(ClassicalType):
167
223
  return super().is_purely_generative and self.element_type.is_purely_generative
168
224
 
169
225
  def get_raw_type(self) -> "ConcreteClassicalType":
170
- raw_type = ClassicalList(element_type=self.element_type.get_raw_type())
226
+ raw_type = ClassicalArray(element_type=self.element_type.get_raw_type())
171
227
  if self._is_generative:
172
228
  raw_type.set_generative()
173
229
  return raw_type
@@ -208,13 +264,13 @@ class ClassicalTuple(ClassicalType):
208
264
  def get_raw_type(self) -> "ConcreteClassicalType":
209
265
  if len(self.element_types) == 0:
210
266
  return self
211
- raw_type = ClassicalList(element_type=self.element_types[0].get_raw_type())
267
+ raw_type = ClassicalArray(element_type=self.element_types[0].get_raw_type())
212
268
  if self._is_generative:
213
269
  raw_type.set_generative()
214
270
  return raw_type
215
271
 
216
272
  @property
217
- def size(self) -> int:
273
+ def length(self) -> int:
218
274
  return len(self.element_types)
219
275
 
220
276
 
@@ -54,6 +54,8 @@ VISUALIZATION_HIDE_LIST = [
54
54
  "stmt_block",
55
55
  ]
56
56
 
57
+ CONTROLLED_PREFIX = "c-"
58
+
57
59
 
58
60
  def last_name_in_call_hierarchy(name: str) -> str:
59
61
  return name.split(CLASSIQ_HIERARCHY_SEPARATOR)[-1]
@@ -213,7 +215,9 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
213
215
  name = generate_original_function_name(back_ref.func_name).removeprefix(
214
216
  ARITH_ENGINE_PREFIX
215
217
  )
216
- return self.add_suffix_from_generated_name(generated_name, name)
218
+ name_with_suffix = self.add_suffix_from_generated_name(generated_name, name)
219
+ modified_name = self.modify_name_for_controlled_qfunc(name_with_suffix)
220
+ return modified_name
217
221
 
218
222
  statement_kind: str = back_ref.kind
219
223
  if isinstance(back_ref, ArithmeticOperation):
@@ -222,6 +226,11 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
222
226
  generated_name, STATEMENTS_NAME[statement_kind]
223
227
  )
224
228
 
229
+ def modify_name_for_controlled_qfunc(self, generated_name: str) -> str:
230
+ if self.control_variable is None:
231
+ return generated_name
232
+ return f"{CONTROLLED_PREFIX}{generated_name}"
233
+
225
234
  def add_suffix_from_generated_name(self, generated_name: str, name: str) -> str:
226
235
  if part_match := PART_SUFFIX_REGEX.match(generated_name):
227
236
  suffix = f" [{part_match.group(1)}]"
@@ -66,9 +66,8 @@ else:
66
66
  StringConstraints(strip_whitespace=True, min_length=1, pattern="^[IXYZ]+$"),
67
67
  ]
68
68
 
69
- PydanticPauliList = Annotated[
70
- list[tuple[PydanticPauliMonomialStr, ParameterComplexType]], Field(min_length=1)
71
- ]
69
+ PauliTuple = tuple[PydanticPauliMonomialStr, ParameterComplexType]
70
+ PydanticPauliList = Annotated[list[PauliTuple], Field(min_length=1)]
72
71
 
73
72
  if TYPE_CHECKING:
74
73
  PydanticFloatTuple = tuple[float, float]
@@ -9,6 +9,8 @@ from classiq.interface.model.quantum_statement import QuantumOperation
9
9
  class Allocate(QuantumOperation):
10
10
  kind: Literal["Allocate"]
11
11
  size: Optional[Expression] = None
12
+ is_signed: Optional[Expression] = None
13
+ fraction_digits: Optional[Expression] = None
12
14
  target: ConcreteHandleBinding
13
15
 
14
16
  @property
@@ -20,4 +22,8 @@ class Allocate(QuantumOperation):
20
22
  exprs = []
21
23
  if self.size is not None:
22
24
  exprs.append(self.size)
25
+ if self.is_signed is not None:
26
+ exprs.append(self.is_signed)
27
+ if self.fraction_digits is not None:
28
+ exprs.append(self.fraction_digits)
23
29
  return exprs
@@ -6,6 +6,7 @@ from typing_extensions import Self
6
6
 
7
7
  from classiq.interface.ast_node import HashableASTNode
8
8
  from classiq.interface.exceptions import ClassiqValueError
9
+ from classiq.interface.generator.arith import number_utils
9
10
  from classiq.interface.generator.arith.register_user_input import (
10
11
  RegisterArithmeticInfo,
11
12
  RegisterUserInput,
@@ -290,8 +291,20 @@ class QuantumNumeric(QuantumScalar):
290
291
  exprs.append(self.fraction_digits)
291
292
  return exprs
292
293
 
293
- def get_bounds(self) -> Optional[tuple[float, float]]:
294
- return self._bounds
294
+ def get_bounds(
295
+ self, machine_precision: Optional[int] = None
296
+ ) -> Optional[tuple[float, float]]:
297
+ if (
298
+ self._bounds is None
299
+ or machine_precision is None
300
+ or self.fraction_digits_value <= machine_precision
301
+ ):
302
+ return self._bounds
303
+
304
+ return (
305
+ number_utils.limit_fraction_places(self._bounds[0], machine_precision),
306
+ number_utils.limit_fraction_places(self._bounds[1], machine_precision),
307
+ )
295
308
 
296
309
  def set_bounds(self, bounds: Optional[tuple[float, float]]) -> None:
297
310
  self._bounds = bounds
@@ -299,11 +312,19 @@ class QuantumNumeric(QuantumScalar):
299
312
  def reset_bounds(self) -> None:
300
313
  self.set_bounds(None)
301
314
 
302
- def get_maximal_bounds(self) -> tuple[float, float]:
315
+ def get_maximal_bounds(
316
+ self, machine_precision: Optional[int] = None
317
+ ) -> tuple[float, float]:
318
+ size = self.size_in_bits
319
+ fraction_digits = self.fraction_digits_value
320
+ if machine_precision is not None and fraction_digits > machine_precision:
321
+ size -= fraction_digits - machine_precision
322
+ fraction_digits = machine_precision
323
+
303
324
  return RegisterArithmeticInfo.get_maximal_bounds(
304
- size=self.size_in_bits,
325
+ size=size,
305
326
  is_signed=self.sign_value,
306
- fraction_places=self.fraction_digits_value,
327
+ fraction_places=fraction_digits,
307
328
  )
308
329
 
309
330
 
@@ -57,6 +57,12 @@ TASKS_VISUAL_MODEL_FULL_PATH = ANALYZER_PREFIX + TASKS_VISUAL_MODEL_SUFFIX
57
57
  EXECUTION_JOBS_SUFFIX = "/jobs"
58
58
  EXECUTION_JOBS_FULL_PATH = EXECUTION_PREFIX + EXECUTION_JOBS_SUFFIX
59
59
 
60
+ QUANTUM_PROGRAM_PREFIX = "/quantum_program"
61
+ TRANSPILATION_SUFFIX = "/transpilation"
62
+ TRANSPILATION_FULL_PATH = QUANTUM_PROGRAM_PREFIX + TRANSPILATION_SUFFIX
63
+ ASSIGN_PARAMETERS_SUFFIX = "/assign_parameters"
64
+ ASSIGN_PARAMETERS_FULL_PATH = QUANTUM_PROGRAM_PREFIX + ASSIGN_PARAMETERS_SUFFIX
65
+
60
66
  ANALYZER_FULL_PATH = ANALYZER_PREFIX + TASKS_SUFFIX
61
67
  ANALYZER_RB_FULL_PATH = ANALYZER_PREFIX + TASK_RB_SUFFIX
62
68
  GENERATE_RESOURCE_ESTIMATOR_REPORT = "/resource_estimator_report"
@@ -147,11 +147,19 @@ def python_val_to_qmod(val: Any, qmod_type: ClassicalType) -> ExpressionValue:
147
147
  }
148
148
  return QmodStructInstance(struct_decl.model_copy(), qmod_dict)
149
149
 
150
- if isinstance(qmod_type, ClassicalList):
150
+ if isinstance(qmod_type, (ClassicalList, ClassicalArray)):
151
151
  if not isinstance(val, list):
152
152
  raise ClassiqInternalExpansionError("Bad value for list")
153
153
  return [python_val_to_qmod(elem, qmod_type.element_type) for elem in val]
154
154
 
155
+ if isinstance(qmod_type, ClassicalTuple):
156
+ if not isinstance(val, list):
157
+ raise ClassiqInternalExpansionError("Bad value for list")
158
+ return [
159
+ python_val_to_qmod(elem, elem_type)
160
+ for elem, elem_type in zip_strict(val, qmod_type.element_types, strict=True)
161
+ ]
162
+
155
163
  if isinstance(qmod_type, OpaqueHandle):
156
164
  if not isinstance(val, QmodPyObject):
157
165
  raise ClassiqInternalExpansionError("Bad value opaque handle")
@@ -16,6 +16,7 @@ from classiq.interface.generator.functions.port_declaration import (
16
16
  PortDeclarationDirection,
17
17
  )
18
18
  from classiq.interface.generator.functions.type_qualifier import TypeQualifier
19
+ from classiq.interface.helpers.text_utils import are, readable_list, s, they
19
20
  from classiq.interface.model.classical_parameter_declaration import (
20
21
  ClassicalParameterDeclaration,
21
22
  )
@@ -42,7 +43,6 @@ from classiq.model_expansions.transformers.model_renamer import (
42
43
  SymbolRenaming,
43
44
  )
44
45
  from classiq.model_expansions.transformers.var_splitter import SymbolPart
45
- from classiq.model_expansions.utils.text_utils import are, readable_list, s, they
46
46
 
47
47
  if TYPE_CHECKING:
48
48
  from classiq.model_expansions.closure import FunctionClosure
@@ -1,6 +1,7 @@
1
1
  from typing import TYPE_CHECKING, Any, Union
2
2
 
3
3
  from classiq.interface.exceptions import ClassiqExpansionError
4
+ from classiq.interface.generator.expressions.expression import Expression
4
5
  from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
5
6
  AnyClassicalValue,
6
7
  )
@@ -67,15 +68,22 @@ def _infer_classical_array_type(
67
68
  return classical_type
68
69
  else:
69
70
  raise ClassiqExpansionError(f"Array expected, got {str(val)!r}")
70
- if (
71
- isinstance(classical_type, (ClassicalArray, ClassicalTuple))
72
- and isinstance(val_length, int)
73
- and isinstance(classical_type.size, int)
74
- and val_length != classical_type.size
71
+ if isinstance(val_length, int) and (
72
+ (
73
+ isinstance(classical_type, ClassicalArray)
74
+ and classical_type.length is not None
75
+ and classical_type.length.is_evaluated()
76
+ and _is_int(classical_type.length.value.value)
77
+ and val_length != (type_length := int(classical_type.length.value.value))
78
+ )
79
+ or (
80
+ isinstance(classical_type, ClassicalTuple)
81
+ and val_length != (type_length := classical_type.length)
82
+ )
75
83
  ):
76
84
  raise ClassiqExpansionError(
77
85
  f"Type mismatch: Argument has {val_length} items but "
78
- f"{classical_type.size} expected"
86
+ f"{type_length} expected"
79
87
  )
80
88
  new_classical_type = _infer_inner_array_types(classical_type, val, val_length)
81
89
  if classical_type.is_generative:
@@ -86,25 +94,29 @@ def _infer_classical_array_type(
86
94
  def _infer_inner_array_types(
87
95
  classical_type: ClassicalType, val: Any, val_length: Any
88
96
  ) -> ClassicalType:
89
- if isinstance(classical_type, (ClassicalArray, ClassicalList)):
90
- if _is_int(val_length) and val_length != 0:
91
- return ClassicalTuple(
92
- element_types=(
93
- infer_classical_type(val[i], classical_type.element_type)
94
- for i in range(int(val_length))
95
- ),
96
- )
97
- element_type: ClassicalType
98
- if val_length == 0:
99
- element_type = classical_type.element_type
100
- else:
101
- element_type = infer_classical_type(val[0], classical_type.element_type)
102
- return ClassicalArray(element_type=element_type, size=val_length)
97
+ if isinstance(classical_type, ClassicalTuple):
98
+ return ClassicalTuple(
99
+ element_types=(
100
+ infer_classical_type(val[idx], element_type)
101
+ for idx, element_type in enumerate(classical_type.element_types)
102
+ ),
103
+ )
103
104
  if TYPE_CHECKING:
104
- assert isinstance(classical_type, ClassicalTuple)
105
- return ClassicalTuple(
106
- element_types=(
107
- infer_classical_type(val[idx], element_type)
108
- for idx, element_type in enumerate(classical_type.element_types)
109
- ),
110
- )
105
+ assert isinstance(classical_type, (ClassicalList, ClassicalArray))
106
+ if _is_int(val_length) and val_length != 0:
107
+ return ClassicalTuple(
108
+ element_types=(
109
+ infer_classical_type(val[i], classical_type.element_type)
110
+ for i in range(int(val_length))
111
+ ),
112
+ )
113
+ element_type: ClassicalType
114
+ if val_length == 0:
115
+ element_type = classical_type.element_type
116
+ else:
117
+ element_type = infer_classical_type(val[0], classical_type.element_type)
118
+ if _is_int(val_length):
119
+ length = Expression(expr=str(int(val_length)))
120
+ else:
121
+ length = None
122
+ return ClassicalArray(element_type=element_type, length=length)
@@ -10,6 +10,12 @@ from classiq.interface.generator.expressions.expression import Expression
10
10
  from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
11
11
  AnyClassicalValue,
12
12
  )
13
+ from classiq.interface.generator.functions.classical_type import (
14
+ ClassicalArray,
15
+ ClassicalList,
16
+ ClassicalTuple,
17
+ ClassicalType,
18
+ )
13
19
  from classiq.interface.generator.functions.concrete_types import ConcreteQuantumType
14
20
  from classiq.interface.generator.functions.port_declaration import (
15
21
  PortDeclarationDirection,
@@ -139,11 +145,12 @@ def _evaluate_type_from_arg(
139
145
  parameter: PositionalArg, argument: Evaluated, inner_scope: Scope
140
146
  ) -> PositionalArg:
141
147
  if isinstance(parameter, ClassicalParameterDeclaration):
148
+ updated_classical_type = evaluate_type_in_classical_symbol(
149
+ parameter.classical_type.model_copy(), inner_scope, parameter.name
150
+ )
142
151
  return ClassicalParameterDeclaration(
143
152
  name=parameter.name,
144
- classical_type=infer_classical_type(
145
- argument.value, parameter.classical_type
146
- ),
153
+ classical_type=infer_classical_type(argument.value, updated_classical_type),
147
154
  )
148
155
 
149
156
  if not isinstance(parameter, PortDeclaration):
@@ -297,3 +304,58 @@ def _inject_quantum_arg_info_to_type(
297
304
  param_name,
298
305
  )
299
306
  return parameter_type
307
+
308
+
309
+ def evaluate_type_in_classical_symbol(
310
+ type_to_update: ClassicalType, scope: Scope, param_name: str
311
+ ) -> ClassicalType:
312
+ updated_type: ClassicalType
313
+ if isinstance(type_to_update, ClassicalList):
314
+ updated_type = ClassicalArray(
315
+ element_type=evaluate_type_in_classical_symbol(
316
+ type_to_update.element_type, scope, param_name
317
+ )
318
+ )
319
+ elif isinstance(type_to_update, ClassicalArray):
320
+ length = type_to_update.length
321
+ if length is not None:
322
+ new_length = _eval_expr(
323
+ length, scope, int, "classical array", "length", param_name
324
+ )
325
+ if new_length is not None:
326
+ length = Expression(expr=str(new_length))
327
+ updated_type = ClassicalArray(
328
+ element_type=evaluate_type_in_classical_symbol(
329
+ type_to_update.element_type, scope, param_name
330
+ ),
331
+ length=length,
332
+ )
333
+ elif isinstance(type_to_update, ClassicalTuple):
334
+ updated_type = ClassicalTuple(
335
+ element_types=[
336
+ evaluate_type_in_classical_symbol(element_type, scope, param_name)
337
+ for element_type in type_to_update.element_types
338
+ ],
339
+ )
340
+ elif (
341
+ isinstance(type_to_update, TypeName)
342
+ and type_to_update.has_classical_struct_decl
343
+ ):
344
+ updated_type = TypeName(name=type_to_update.name)
345
+ updated_type.set_classical_struct_decl(
346
+ type_to_update.classical_struct_decl.model_copy(
347
+ update=dict(
348
+ variables={
349
+ field_name: evaluate_type_in_classical_symbol(
350
+ field_type, scope, param_name
351
+ )
352
+ for field_name, field_type in type_to_update.classical_struct_decl.variables.items()
353
+ }
354
+ )
355
+ )
356
+ )
357
+ else:
358
+ updated_type = type_to_update
359
+ if type_to_update.is_generative:
360
+ updated_type.set_generative()
361
+ return updated_type
@@ -24,7 +24,7 @@ from classiq.qmod.model_state_container import QMODULE
24
24
 
25
25
  class FrontendGenerativeInterpreter(GenerativeInterpreter):
26
26
  def emit_allocate(self, allocate: Allocate) -> None:
27
- AllocateEmitter(self, allow_symbolic_size=True).emit(allocate)
27
+ AllocateEmitter(self, allow_symbolic_attrs=True).emit(allocate)
28
28
 
29
29
  def emit_bind(self, bind: BindOperation) -> None:
30
30
  BindEmitter(self, allow_symbolic_size=True).emit(bind)
@@ -1,16 +1,16 @@
1
- from typing import TYPE_CHECKING, Optional
1
+ from typing import TYPE_CHECKING, Union
2
2
 
3
3
  import sympy
4
4
 
5
5
  from classiq.interface.debug_info.debug_info import FunctionDebugInfo
6
6
  from classiq.interface.exceptions import ClassiqValueError
7
7
  from classiq.interface.generator.expressions.expression import Expression
8
- from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
9
- AnyClassicalValue,
10
- )
11
8
  from classiq.interface.model.allocate import Allocate
12
9
  from classiq.interface.model.handle_binding import NestedHandleBinding
13
- from classiq.interface.model.quantum_type import QuantumBitvector, QuantumNumeric
10
+ from classiq.interface.model.quantum_type import (
11
+ QuantumBitvector,
12
+ QuantumNumeric,
13
+ )
14
14
 
15
15
  from classiq.model_expansions.evaluators.quantum_type_utils import copy_type_information
16
16
  from classiq.model_expansions.quantum_operations.emitter import Emitter
@@ -22,10 +22,10 @@ if TYPE_CHECKING:
22
22
 
23
23
  class AllocateEmitter(Emitter[Allocate]):
24
24
  def __init__(
25
- self, interpreter: "BaseInterpreter", allow_symbolic_size: bool = False
25
+ self, interpreter: "BaseInterpreter", allow_symbolic_attrs: bool = False
26
26
  ) -> None:
27
27
  super().__init__(interpreter)
28
- self._allow_symbolic_size = allow_symbolic_size
28
+ self._allow_symbolic_attrs = allow_symbolic_attrs
29
29
 
30
30
  def emit(self, allocate: Allocate, /) -> bool:
31
31
  target: QuantumSymbol = self._interpreter.evaluate(allocate.target).as_type(
@@ -37,45 +37,132 @@ class AllocateEmitter(Emitter[Allocate]):
37
37
  f"Cannot allocate partial quantum variable {str(target.handle)!r}"
38
38
  )
39
39
 
40
- size_expr = self._get_var_size(target, allocate.size)
40
+ op_update_dict: dict[str, Expression] = {}
41
+
42
+ if allocate.size is None:
43
+ if allocate.is_signed is not None or allocate.fraction_digits is not None:
44
+ raise ClassiqValueError(
45
+ "Numeric attributes cannot be specified without size"
46
+ )
47
+ self._handle_without_size(target, op_update_dict)
48
+
49
+ elif allocate.is_signed is None and allocate.fraction_digits is None:
50
+ self._handle_with_size(target, allocate.size, op_update_dict)
51
+
52
+ elif allocate.is_signed is not None and allocate.fraction_digits is not None:
53
+ self._handle_with_numeric_attrs(
54
+ target,
55
+ allocate.size,
56
+ allocate.is_signed,
57
+ allocate.fraction_digits,
58
+ op_update_dict,
59
+ )
60
+
61
+ else:
62
+ raise ClassiqValueError(
63
+ "Sign and fraction digits must be specified together"
64
+ )
65
+
41
66
  if isinstance(target.quantum_type, QuantumNumeric):
42
67
  target.quantum_type.set_bounds((0, 0))
43
- allocate = allocate.model_copy(
44
- update=dict(
45
- size=Expression(expr=size_expr),
46
- target=target.handle,
47
- back_ref=allocate.uuid,
48
- )
49
- )
68
+
69
+ allocate = allocate.model_copy(update=op_update_dict)
50
70
  self._register_debug_info(allocate)
51
71
  self.emit_statement(allocate)
52
72
  return True
53
73
 
54
- def _get_var_size(self, target: QuantumSymbol, size: Optional[Expression]) -> str:
55
- if size is None:
56
- if not target.quantum_type.is_evaluated:
57
- raise ClassiqValueError(
58
- f"Could not infer the size of variable {str(target.handle)!r}"
59
- )
60
- return str(target.quantum_type.size_in_bits)
74
+ def _handle_without_size(
75
+ self,
76
+ target: QuantumSymbol,
77
+ op_update_dict: dict[str, Expression],
78
+ ) -> None:
79
+ if not target.quantum_type.is_evaluated:
80
+ raise ClassiqValueError(
81
+ f"Could not infer the size of variable {str(target.handle)!r}"
82
+ )
83
+ op_update_dict["size"] = Expression(expr=str(target.quantum_type.size_in_bits))
61
84
 
62
- size_value = self._interpreter.evaluate(size).value
63
- if self._allow_symbolic_size and isinstance(
64
- size_value, (sympy.Basic, AnyClassicalValue)
85
+ def _handle_with_size(
86
+ self,
87
+ target: QuantumSymbol,
88
+ size: Expression,
89
+ op_update_dict: dict[str, Expression],
90
+ ) -> None:
91
+ size_value = self._interpret_size(size)
92
+ op_update_dict["size"] = Expression(expr=str(size_value))
93
+
94
+ if not isinstance(size_value, sympy.Basic):
95
+ copy_type_information(
96
+ QuantumBitvector(length=op_update_dict["size"]),
97
+ target.quantum_type,
98
+ str(target.handle),
99
+ )
100
+
101
+ def _handle_with_numeric_attrs(
102
+ self,
103
+ target: QuantumSymbol,
104
+ size: Expression,
105
+ is_signed: Expression,
106
+ fraction_digits: Expression,
107
+ op_update_dict: dict[str, Expression],
108
+ ) -> None:
109
+ if not isinstance(target.quantum_type, QuantumNumeric):
110
+ raise ClassiqValueError(
111
+ f"Non-numeric variable {str(target.handle)!r} cannot be allocated with numeric attributes"
112
+ )
113
+
114
+ size_value = self._interpret_size(size)
115
+ op_update_dict["size"] = Expression(expr=str(size_value))
116
+ is_signed_value = self._interpret_is_signed(is_signed)
117
+ op_update_dict["is_signed"] = Expression(expr=str(is_signed_value))
118
+ fraction_digits_value = self._interpret_fraction_digits(fraction_digits)
119
+ op_update_dict["fraction_digits"] = Expression(expr=str(fraction_digits_value))
120
+
121
+ if not (
122
+ isinstance(size_value, sympy.Basic)
123
+ or isinstance(is_signed_value, sympy.Basic)
124
+ or isinstance(fraction_digits_value, sympy.Basic)
65
125
  ):
66
- return str(size_value)
67
- if not isinstance(size_value, (int, float)):
126
+ copy_type_information(
127
+ QuantumNumeric(
128
+ size=op_update_dict["size"],
129
+ is_signed=op_update_dict["is_signed"],
130
+ fraction_digits=op_update_dict["fraction_digits"],
131
+ ),
132
+ target.quantum_type,
133
+ str(target.handle),
134
+ )
135
+
136
+ def _interpret_size(self, size: Expression) -> Union[int, float, sympy.Basic]:
137
+ size_value = self._interpreter.evaluate(size).value
138
+ if not self._allow_symbolic_attrs and not isinstance(size_value, (int, float)):
68
139
  raise ClassiqValueError(
69
140
  f"The number of allocated qubits must be an integer. Got "
70
141
  f"{str(size_value)!r}"
71
142
  )
72
- size_expr = str(size_value)
73
- copy_type_information(
74
- QuantumBitvector(length=Expression(expr=size_expr)),
75
- target.quantum_type,
76
- str(target.handle),
77
- )
78
- return size_expr
143
+ return size_value
144
+
145
+ def _interpret_is_signed(self, is_signed: Expression) -> Union[bool, sympy.Basic]:
146
+ is_signed_value = self._interpreter.evaluate(is_signed).value
147
+ if not self._allow_symbolic_attrs and not isinstance(is_signed_value, bool):
148
+ raise ClassiqValueError(
149
+ f"The sign of a variable must be boolean. Got "
150
+ f"{str(is_signed_value)!r}"
151
+ )
152
+ return is_signed_value
153
+
154
+ def _interpret_fraction_digits(
155
+ self, fraction_digits: Expression
156
+ ) -> Union[int, float, sympy.Basic]:
157
+ fraction_digits_value = self._interpreter.evaluate(fraction_digits).value
158
+ if not self._allow_symbolic_attrs and not isinstance(
159
+ fraction_digits_value, (int, float)
160
+ ):
161
+ raise ClassiqValueError(
162
+ f"The fraction digits of a variable must be an integer. Got "
163
+ f"{str(fraction_digits_value)!r}"
164
+ )
165
+ return fraction_digits_value
79
166
 
80
167
  def _register_debug_info(self, allocate: Allocate) -> None:
81
168
  if (