classiq 0.86.0__py3-none-any.whl → 0.87.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 (96) hide show
  1. classiq/__init__.py +2 -0
  2. classiq/applications/chemistry/hartree_fock.py +5 -1
  3. classiq/applications/chemistry/op_utils.py +2 -2
  4. classiq/applications/combinatorial_helpers/encoding_mapping.py +11 -15
  5. classiq/applications/combinatorial_helpers/encoding_utils.py +6 -6
  6. classiq/applications/combinatorial_helpers/memory.py +4 -4
  7. classiq/applications/combinatorial_helpers/optimization_model.py +5 -5
  8. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +6 -10
  9. classiq/applications/combinatorial_helpers/pyomo_utils.py +27 -26
  10. classiq/applications/combinatorial_helpers/sympy_utils.py +2 -2
  11. classiq/applications/combinatorial_helpers/transformations/encoding.py +4 -6
  12. classiq/applications/combinatorial_helpers/transformations/fixed_variables.py +4 -4
  13. classiq/applications/combinatorial_helpers/transformations/ising_converter.py +2 -2
  14. classiq/applications/combinatorial_helpers/transformations/penalty_support.py +3 -3
  15. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -0
  16. classiq/applications/hamiltonian/pauli_decomposition.py +33 -1
  17. classiq/evaluators/argument_types.py +15 -6
  18. classiq/evaluators/parameter_types.py +43 -39
  19. classiq/evaluators/qmod_annotated_expression.py +88 -11
  20. classiq/evaluators/qmod_expression_visitors/out_of_place_node_transformer.py +19 -0
  21. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +54 -11
  22. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +40 -25
  23. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +29 -59
  24. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +12 -5
  25. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +175 -28
  26. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +21 -14
  27. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +9 -5
  28. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +20 -1
  29. classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +97 -0
  30. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +11 -26
  31. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +56 -0
  32. classiq/evaluators/qmod_node_evaluators/piecewise_evaluation.py +40 -0
  33. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +2 -3
  34. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +48 -21
  35. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +53 -9
  36. classiq/evaluators/qmod_node_evaluators/utils.py +27 -5
  37. classiq/evaluators/qmod_type_inference/classical_type_inference.py +188 -0
  38. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +292 -0
  39. classiq/evaluators/quantum_type_utils.py +0 -131
  40. classiq/execution/execution_session.py +1 -1
  41. classiq/execution/qnn.py +4 -1
  42. classiq/execution/user_budgets.py +1 -1
  43. classiq/interface/_version.py +1 -1
  44. classiq/interface/backend/backend_preferences.py +10 -30
  45. classiq/interface/backend/quantum_backend_providers.py +63 -52
  46. classiq/interface/generator/arith/binary_ops.py +107 -115
  47. classiq/interface/generator/arith/extremum_operations.py +33 -45
  48. classiq/interface/generator/arith/number_utils.py +4 -1
  49. classiq/interface/generator/circuit_code/types_and_constants.py +0 -9
  50. classiq/interface/generator/compiler_keywords.py +2 -0
  51. classiq/interface/generator/function_param_list.py +133 -5
  52. classiq/interface/generator/functions/classical_type.py +59 -2
  53. classiq/interface/generator/functions/qmod_python_interface.py +15 -0
  54. classiq/interface/generator/functions/type_name.py +6 -0
  55. classiq/interface/generator/model/preferences/preferences.py +1 -17
  56. classiq/interface/generator/quantum_program.py +1 -13
  57. classiq/interface/helpers/model_normalizer.py +2 -2
  58. classiq/interface/helpers/text_utils.py +7 -2
  59. classiq/interface/interface_version.py +1 -1
  60. classiq/interface/model/classical_if.py +40 -0
  61. classiq/interface/model/handle_binding.py +28 -16
  62. classiq/interface/model/quantum_type.py +61 -2
  63. classiq/interface/pretty_print/expression_to_qmod.py +24 -11
  64. classiq/interface/pyomo_extension/__init__.py +0 -4
  65. classiq/interface/pyomo_extension/pyomo_sympy_bimap.py +2 -2
  66. classiq/model_expansions/arithmetic.py +43 -1
  67. classiq/model_expansions/arithmetic_compute_result_attrs.py +255 -0
  68. classiq/model_expansions/capturing/captured_vars.py +2 -5
  69. classiq/model_expansions/quantum_operations/allocate.py +22 -15
  70. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +1 -3
  71. classiq/model_expansions/quantum_operations/assignment_result_processor.py +52 -71
  72. classiq/model_expansions/quantum_operations/bind.py +15 -7
  73. classiq/model_expansions/quantum_operations/call_emitter.py +2 -10
  74. classiq/model_expansions/quantum_operations/classical_var_emitter.py +6 -0
  75. classiq/open_library/functions/__init__.py +3 -0
  76. classiq/open_library/functions/lcu.py +117 -0
  77. classiq/qmod/builtins/enums.py +2 -2
  78. classiq/qmod/builtins/structs.py +33 -18
  79. classiq/qmod/pretty_print/expression_to_python.py +7 -9
  80. {classiq-0.86.0.dist-info → classiq-0.87.0.dist-info}/METADATA +3 -3
  81. {classiq-0.86.0.dist-info → classiq-0.87.0.dist-info}/RECORD +83 -89
  82. classiq/interface/generator/amplitude_estimation.py +0 -34
  83. classiq/interface/generator/function_param_list_without_self_reference.py +0 -160
  84. classiq/interface/generator/grover_diffuser.py +0 -93
  85. classiq/interface/generator/grover_operator.py +0 -106
  86. classiq/interface/generator/oracles/__init__.py +0 -3
  87. classiq/interface/generator/oracles/arithmetic_oracle.py +0 -82
  88. classiq/interface/generator/oracles/custom_oracle.py +0 -65
  89. classiq/interface/generator/oracles/oracle_abc.py +0 -76
  90. classiq/interface/generator/oracles/oracle_function_param_list.py +0 -6
  91. classiq/interface/generator/piecewise_linear_amplitude_loading.py +0 -165
  92. classiq/interface/generator/qpe.py +0 -169
  93. classiq/interface/grover/grover_modelling_params.py +0 -13
  94. classiq/model_expansions/transformers/var_splitter.py +0 -224
  95. /classiq/{interface/grover → evaluators/qmod_type_inference}/__init__.py +0 -0
  96. {classiq-0.86.0.dist-info → classiq-0.87.0.dist-info}/WHEEL +0 -0
@@ -57,6 +57,10 @@ class QuantumType(HashableASTNode):
57
57
  def set_size_in_bits(self, val: int) -> None:
58
58
  self._size_in_bits = val
59
59
 
60
+ @property
61
+ def minimal_size_in_bits(self) -> int:
62
+ raise NotImplementedError
63
+
60
64
  def get_proxy(self, handle: "HandleBinding") -> QmodSizedProxy:
61
65
  return QmodSizedProxy(handle=handle, size=self.size_in_bits)
62
66
 
@@ -64,6 +68,10 @@ class QuantumType(HashableASTNode):
64
68
  def qmod_type_name(self) -> str:
65
69
  raise NotImplementedError
66
70
 
71
+ @property
72
+ def raw_qmod_type_name(self) -> str:
73
+ return self.qmod_type_name
74
+
67
75
  @property
68
76
  def type_name(self) -> str:
69
77
  raise NotImplementedError
@@ -167,6 +175,10 @@ class QuantumBit(QuantumScalar):
167
175
  ) -> tuple[float, float]:
168
176
  return (0, 1)
169
177
 
178
+ @property
179
+ def minimal_size_in_bits(self) -> int:
180
+ return 1
181
+
170
182
 
171
183
  class QuantumBitvector(QuantumType):
172
184
  element_type: "ConcreteQuantumType" = Field(
@@ -182,7 +194,7 @@ class QuantumBitvector(QuantumType):
182
194
 
183
195
  def _update_size_in_bits_from_declaration(self) -> None:
184
196
  self.element_type._update_size_in_bits_from_declaration()
185
- if self.element_type.has_size_in_bits and self.has_length:
197
+ if self.element_type.has_size_in_bits and self.has_constant_length:
186
198
  assert self.length is not None
187
199
  self._size_in_bits = (
188
200
  self.element_type.size_in_bits * self.length.to_int_value()
@@ -192,6 +204,14 @@ class QuantumBitvector(QuantumType):
192
204
  def has_length(self) -> bool:
193
205
  return self.length is not None and self.length.is_evaluated()
194
206
 
207
+ @property
208
+ def has_constant_length(self) -> bool:
209
+ return (
210
+ self.length is not None
211
+ and self.length.is_evaluated()
212
+ and self.length.is_constant()
213
+ )
214
+
195
215
  @property
196
216
  def length_value(self) -> int:
197
217
  if not self.has_length:
@@ -217,6 +237,10 @@ class QuantumBitvector(QuantumType):
217
237
  length = [self.length.expr] if self.length is not None else []
218
238
  return f"QArray[{', '.join(element_type + length)}]"
219
239
 
240
+ @property
241
+ def raw_qmod_type_name(self) -> str:
242
+ return "QArray"
243
+
220
244
  @property
221
245
  def type_name(self) -> str:
222
246
  return "Quantum array"
@@ -240,6 +264,14 @@ class QuantumBitvector(QuantumType):
240
264
  exprs.append(self.length)
241
265
  return exprs
242
266
 
267
+ @property
268
+ def minimal_size_in_bits(self) -> int:
269
+ if self.has_constant_length:
270
+ length = self.length_value
271
+ else:
272
+ length = 1
273
+ return length * self.element_type.minimal_size_in_bits
274
+
243
275
 
244
276
  class QuantumNumeric(QuantumScalar):
245
277
  kind: Literal["qnum"]
@@ -294,7 +326,11 @@ class QuantumNumeric(QuantumScalar):
294
326
  )
295
327
 
296
328
  def _update_size_in_bits_from_declaration(self) -> None:
297
- if self.size is not None and self.size.is_evaluated():
329
+ if (
330
+ self.size is not None
331
+ and self.size.is_evaluated()
332
+ and self.size.is_constant()
333
+ ):
298
334
  self._size_in_bits = self.size.to_int_value()
299
335
 
300
336
  def get_proxy(self, handle: "HandleBinding") -> QmodQNumProxy:
@@ -307,6 +343,21 @@ class QuantumNumeric(QuantumScalar):
307
343
 
308
344
  @property
309
345
  def qmod_type_name(self) -> str:
346
+ if (
347
+ self.size is not None
348
+ and (
349
+ self.is_signed is None
350
+ or (self.is_signed.is_evaluated() and not self.is_signed.value.value)
351
+ )
352
+ and (
353
+ self.fraction_digits is None
354
+ or (
355
+ self.fraction_digits.is_evaluated()
356
+ and self.fraction_digits.value.value == 0
357
+ )
358
+ )
359
+ ):
360
+ return f"QNum[{self.size.expr}]"
310
361
  if (
311
362
  self.size is not None
312
363
  and self.is_signed is not None
@@ -315,6 +366,10 @@ class QuantumNumeric(QuantumScalar):
315
366
  return f"QNum[{self.size.expr}, {self.is_signed.expr}, {self.fraction_digits.expr}]"
316
367
  return "QNum"
317
368
 
369
+ @property
370
+ def raw_qmod_type_name(self) -> str:
371
+ return "QNum"
372
+
318
373
  @property
319
374
  def type_name(self) -> str:
320
375
  return "Quantum numeric"
@@ -372,6 +427,10 @@ class QuantumNumeric(QuantumScalar):
372
427
  number_utils.limit_fraction_places(bounds[1], machine_precision),
373
428
  )
374
429
 
430
+ @property
431
+ def minimal_size_in_bits(self) -> int:
432
+ return self.size_in_bits if self.has_size_in_bits else 1
433
+
375
434
 
376
435
  class RegisterQuantumType(BaseModel):
377
436
  quantum_types: "ConcreteQuantumType"
@@ -61,15 +61,14 @@ class ASTToQMODCode:
61
61
  if isinstance(node, ast.Module):
62
62
  return self.indent.join(self.ast_to_code(child) for child in node.body)
63
63
  elif isinstance(node, ast.Attribute):
64
- # Enum attribute access
65
- if not isinstance(node.value, ast.Name) or not isinstance(node.attr, str):
66
- raise PrettyPrinterError("Error parsing enum attribute access")
67
- if not (IDENTIFIER.match(node.value.id) and IDENTIFIER.match(node.attr)):
68
- raise PrettyPrinterError("Error parsing enum attribute access")
69
64
  # FIXME: identify enum member accesses by type name (CLS-2858)
70
- if len(node.value.id) > 0 and node.value.id[0].isupper():
71
- return f"{node.value.id!s}::{node.attr!s}"
72
- return f"{node.value.id!s}.{node.attr!s}"
65
+ if (
66
+ isinstance(node.value, ast.Name)
67
+ and len(node.value.id) > 0
68
+ and node.value.id[0].isupper()
69
+ ):
70
+ return f"{node.value.id}::{node.attr}"
71
+ return f"{self.visit(node.value)}.{node.attr}"
73
72
  elif isinstance(node, ast.Name):
74
73
  return node.id
75
74
  elif isinstance(node, ast.Num):
@@ -142,10 +141,24 @@ class ASTToQMODCode:
142
141
  if len(node.args) != 2:
143
142
  raise PrettyPrinterError("Error parsing array access.")
144
143
  return f"{self.ast_to_code(node.args[0])}[{self.ast_to_code(node.args[1])}]"
145
- else:
146
- return "{}({})".format(
147
- func, ", ".join(self._cleaned_ast_to_code(arg) for arg in node.args)
144
+ elif len(node.keywords) > 0:
145
+ # struct instance
146
+ keywords = node.keywords
147
+ initializer_list = self.indent_items(
148
+ lambda: [
149
+ f"{keyword.arg}={self._cleaned_ast_to_code(keyword.value)}"
150
+ for keyword in keywords
151
+ if keyword.arg is not None
152
+ ]
148
153
  )
154
+ return f"{func} {{{initializer_list}}}"
155
+ else:
156
+ args = [self._cleaned_ast_to_code(arg) for arg in node.args]
157
+ kwargs = [
158
+ f"{kwarg.arg}={self._cleaned_ast_to_code(kwarg.value)}"
159
+ for kwarg in node.keywords
160
+ ]
161
+ return "{}({})".format(func, ", ".join(args + kwargs))
149
162
  elif isinstance(node, ast.Expr):
150
163
  return self._cleaned_ast_to_code(node.value)
151
164
  else:
@@ -17,10 +17,6 @@ pyomo.core.expr.relational_expr.EqualityExpression.getname = equality_expression
17
17
 
18
18
  pyomo.core.base.set.Set._pprint_members = staticmethod(set_pprint.pprint_members)
19
19
 
20
- pyomo.core.expr.sympy_tools._operatorMap.update({sympy.LessThan: lambda x, y: x <= y})
21
- pyomo.core.expr.sympy_tools._operatorMap.update(
22
- {sympy.GreaterThan: lambda x, y: x >= y}
23
- )
24
20
  pyomo.core.expr.sympy_tools._pyomo_operator_map.update({EqualityExpression: sympy.Eq})
25
21
 
26
22
  pyomo.core.expr.sympy_tools.PyomoSympyBimap.getSympySymbol = (
@@ -1,7 +1,7 @@
1
1
  from typing import Any
2
2
 
3
3
  import sympy
4
- from pyomo.core.base import _GeneralVarData
4
+ from pyomo.core.base.var import VarData
5
5
  from pyomo.core.expr.sympy_tools import PyomoSympyBimap
6
6
 
7
7
 
@@ -25,5 +25,5 @@ def get_sympy_symbol(self: PyomoSympyBimap, pyomo_object: Any) -> sympy.Symbol:
25
25
  # The name for the new sympy object is derived
26
26
  # from the pyomo object, instead of using generic name with serial number.
27
27
  # This is intended to have better corelation between the the pyomo variables and sympy variables.
28
- def _get_sympy_name(pyomo_object: _GeneralVarData) -> str:
28
+ def _get_sympy_name(pyomo_object: VarData) -> str:
29
29
  return pyomo_object.name.replace("[", "").replace("]", "")
@@ -3,7 +3,9 @@ from typing import Optional, Union
3
3
 
4
4
  from classiq.interface.generator.arith import number_utils
5
5
  from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
6
+ from classiq.interface.generator.expressions.expression import Expression
6
7
  from classiq.interface.model.quantum_type import (
8
+ QuantumNumeric,
7
9
  QuantumScalar,
8
10
  register_info_to_quantum_type,
9
11
  )
@@ -48,9 +50,48 @@ class NumericAttributes:
48
50
  def ub(self) -> float:
49
51
  return self.bounds[1]
50
52
 
53
+ @property
54
+ def integer_digits(self) -> int:
55
+ return self.size - self.fraction_digits
56
+
57
+ def to_quantum_numeric(self) -> QuantumNumeric:
58
+ quantum_numeric = QuantumNumeric(
59
+ size=Expression(expr=str(self.size)),
60
+ is_signed=Expression(expr=str(self.is_signed)),
61
+ fraction_digits=Expression(expr=str(self.fraction_digits)),
62
+ )
63
+ quantum_numeric.set_bounds(self.bounds)
64
+ return quantum_numeric
65
+
66
+ def to_register(self) -> RegisterArithmeticInfo:
67
+ return RegisterArithmeticInfo(
68
+ size=self.size,
69
+ is_signed=self.is_signed,
70
+ fraction_places=self.fraction_digits,
71
+ bounds=self.bounds,
72
+ )
73
+
74
+ def trim_fraction_digits(self, machine_precision: int) -> "NumericAttributes":
75
+ trimmed_digits = self.fraction_digits - machine_precision
76
+ if trimmed_digits < 0:
77
+ return self
78
+
79
+ return NumericAttributes(
80
+ size=self.size - trimmed_digits,
81
+ is_signed=self.is_signed,
82
+ fraction_digits=self.fraction_digits - trimmed_digits,
83
+ bounds=self.bounds,
84
+ trim_bounds=True,
85
+ )
86
+
51
87
  @classmethod
52
88
  def from_bounds(
53
- cls, lb: float, ub: float, fraction_places: int, machine_precision: int
89
+ cls,
90
+ lb: float,
91
+ ub: float,
92
+ fraction_places: int,
93
+ machine_precision: int,
94
+ trim_bounds: bool = False,
54
95
  ) -> "NumericAttributes":
55
96
  size, is_signed, fraction_digits = number_utils.bounds_to_attributes(
56
97
  lb, ub, fraction_places, machine_precision
@@ -60,6 +101,7 @@ class NumericAttributes:
60
101
  is_signed=is_signed,
61
102
  fraction_digits=fraction_digits,
62
103
  bounds=(lb, ub),
104
+ trim_bounds=trim_bounds,
63
105
  )
64
106
 
65
107
  @classmethod
@@ -1,3 +1,9 @@
1
+ import math
2
+ from collections.abc import Sequence
3
+ from typing import Union
4
+
5
+ from classiq.interface.exceptions import ClassiqValueError
6
+
1
7
  from classiq.model_expansions.arithmetic import NumericAttributes
2
8
 
3
9
 
@@ -51,6 +57,62 @@ def compute_result_attrs_negate(
51
57
  )
52
58
 
53
59
 
60
+ def compute_result_attrs_bitwise_and(
61
+ left: NumericAttributes,
62
+ right: NumericAttributes,
63
+ machine_precision: int,
64
+ ) -> NumericAttributes:
65
+ if left.fraction_digits > 0 or right.fraction_digits > 0:
66
+ raise ClassiqValueError("Bitwise AND is only defined for integers")
67
+
68
+ # we comply with python, which uses arbitrary precision, so a positive number can
69
+ # always be represented by "0..." and a negative number by "1...", thus their
70
+ # bitwise AND is always non-negative
71
+ return NumericAttributes(
72
+ size=max(left.size, right.size),
73
+ is_signed=left.is_signed and right.is_signed,
74
+ fraction_digits=0,
75
+ )
76
+
77
+
78
+ def compute_result_attrs_bitwise_or(
79
+ left: NumericAttributes,
80
+ right: NumericAttributes,
81
+ machine_precision: int,
82
+ ) -> NumericAttributes:
83
+ if left.fraction_digits > 0 or right.fraction_digits > 0:
84
+ raise ClassiqValueError("Bitwise OR is only defined for integers")
85
+
86
+ # we comply with python, which uses arbitrary precision, so a positive number can
87
+ # always be represented by "0..." and a negative number by "1...", thus their
88
+ # bitwise OR is always negative
89
+
90
+ if left.is_signed and not right.is_signed:
91
+ # we need to extend right so its MSB is always 0
92
+ size = max(left.size, right.size + 1)
93
+ elif not left.is_signed and right.is_signed:
94
+ # we need to extend left so its MSB is always 0
95
+ size = max(left.size + 1, right.size)
96
+ else:
97
+ size = max(left.size, right.size)
98
+
99
+ return NumericAttributes(
100
+ size=size,
101
+ is_signed=left.is_signed or right.is_signed,
102
+ fraction_digits=0,
103
+ )
104
+
105
+
106
+ def compute_result_attrs_bitwise_xor(
107
+ left: NumericAttributes,
108
+ right: NumericAttributes,
109
+ machine_precision: int,
110
+ ) -> NumericAttributes:
111
+ if left.fraction_digits > 0 or right.fraction_digits > 0:
112
+ raise ClassiqValueError("Bitwise XOR is only defined for integers")
113
+ return compute_result_attrs_bitwise_or(left, right, machine_precision)
114
+
115
+
54
116
  def compute_result_attrs_add(
55
117
  left: NumericAttributes,
56
118
  right: NumericAttributes,
@@ -69,3 +131,196 @@ def compute_result_attrs_subtract(
69
131
  ) -> NumericAttributes:
70
132
  tmp = compute_result_attrs_negate(right, machine_precision)
71
133
  return compute_result_attrs_add(left, tmp, machine_precision)
134
+
135
+
136
+ def compute_result_attrs_multiply(
137
+ left: NumericAttributes,
138
+ right: NumericAttributes,
139
+ machine_precision: int,
140
+ ) -> NumericAttributes:
141
+ extremal_values = [
142
+ left_val * right_val for left_val in left.bounds for right_val in right.bounds
143
+ ]
144
+ fraction_places = left.fraction_digits + right.fraction_digits
145
+ return NumericAttributes.from_bounds(
146
+ min(extremal_values),
147
+ max(extremal_values),
148
+ fraction_places,
149
+ machine_precision,
150
+ trim_bounds=True,
151
+ )
152
+
153
+
154
+ def compute_result_attrs_power(
155
+ left: NumericAttributes,
156
+ right: Union[int, float],
157
+ machine_precision: int,
158
+ ) -> NumericAttributes:
159
+ if not float(right).is_integer() or right <= 0:
160
+ raise ClassiqValueError("Power must be a positive integer")
161
+ right = int(right)
162
+
163
+ bounds: tuple[float, float]
164
+ if (right % 2 == 0) and (left.lb < 0 < left.ub):
165
+ bounds = (0, max(left.lb**right, left.ub**right))
166
+ else:
167
+ extremal_values = (left.lb**right, left.ub**right)
168
+ bounds = (min(extremal_values), max(extremal_values))
169
+ fraction_places = left.fraction_digits * right
170
+ return NumericAttributes.from_bounds(
171
+ bounds[0], bounds[1], fraction_places, machine_precision, trim_bounds=True
172
+ )
173
+
174
+
175
+ def compute_result_attrs_lshift(
176
+ left: NumericAttributes,
177
+ right: Union[int, float],
178
+ machine_precision: int,
179
+ ) -> NumericAttributes:
180
+ if not float(right).is_integer() or right < 0:
181
+ raise ClassiqValueError("Shift must be a non-negative integer")
182
+ right = int(right)
183
+
184
+ scale = 1 << left.fraction_digits
185
+ lb = (int(left.lb * scale) << right) / scale
186
+ ub = (int(left.ub * scale) << right) / scale
187
+ fraction_digits = max(left.fraction_digits - right, 0)
188
+ integer_digits = left.integer_digits + right
189
+ return NumericAttributes(
190
+ size=integer_digits + fraction_digits,
191
+ is_signed=left.is_signed,
192
+ fraction_digits=fraction_digits,
193
+ bounds=(lb, ub),
194
+ )
195
+
196
+
197
+ def compute_result_attrs_rshift(
198
+ left: NumericAttributes,
199
+ right: Union[int, float],
200
+ machine_precision: int,
201
+ ) -> NumericAttributes:
202
+ if not float(right).is_integer() or right < 0:
203
+ raise ClassiqValueError("Shift must be a non-negative integer")
204
+ right = int(right)
205
+
206
+ scale = 1 << left.fraction_digits
207
+ lb = (int(left.lb * scale) >> right) / scale
208
+ ub = (int(left.ub * scale) >> right) / scale
209
+ fraction_digits = (
210
+ 0 if (right >= left.size and not left.is_signed) else left.fraction_digits
211
+ )
212
+ return NumericAttributes(
213
+ size=max(left.size - right, fraction_digits, 1),
214
+ is_signed=left.is_signed,
215
+ fraction_digits=fraction_digits,
216
+ bounds=(lb, ub),
217
+ )
218
+
219
+
220
+ def compute_result_attrs_modulo(
221
+ left: NumericAttributes,
222
+ right: Union[int, float],
223
+ machine_precision: int,
224
+ ) -> NumericAttributes:
225
+ if not float(right).is_integer() or right < 2:
226
+ raise ClassiqValueError("Modulus must be a positive power of two")
227
+ right = int(right)
228
+ if right & (right - 1) != 0:
229
+ raise ClassiqValueError("Modulus must be a positive power of two")
230
+
231
+ if left.fraction_digits > 0:
232
+ raise ClassiqValueError("Modulo is supported for integers only")
233
+
234
+ size = int(math.log2(right))
235
+ if not left.is_signed and size >= left.size:
236
+ return left
237
+
238
+ return NumericAttributes(
239
+ size=size,
240
+ fraction_digits=0,
241
+ is_signed=False,
242
+ )
243
+
244
+
245
+ def compute_result_attrs_min(
246
+ args: Sequence[NumericAttributes],
247
+ machine_precision: int,
248
+ ) -> NumericAttributes:
249
+ if len(args) < 1:
250
+ raise ClassiqValueError("Min expects at least one argument")
251
+
252
+ args = [arg.trim_fraction_digits(machine_precision) for arg in args]
253
+
254
+ result_attrs = args[0]
255
+ for attrs in args[1:]:
256
+ if result_attrs.lb == result_attrs.ub == attrs.lb == attrs.ub:
257
+ if attrs.size < result_attrs.size:
258
+ result_attrs = attrs
259
+ elif result_attrs.ub <= attrs.lb:
260
+ pass
261
+ elif attrs.ub <= result_attrs.lb:
262
+ result_attrs = attrs
263
+ else:
264
+ integer_digits = max(result_attrs.integer_digits, attrs.integer_digits)
265
+ fraction_digits = max(result_attrs.fraction_digits, attrs.fraction_digits)
266
+ bounds = (min(result_attrs.lb, attrs.lb), min(result_attrs.ub, attrs.ub))
267
+ result_attrs = NumericAttributes(
268
+ size=integer_digits + fraction_digits,
269
+ is_signed=bounds[0] < 0,
270
+ fraction_digits=fraction_digits,
271
+ bounds=bounds,
272
+ )
273
+
274
+ return result_attrs
275
+
276
+
277
+ def compute_result_attrs_max(
278
+ args: Sequence[NumericAttributes],
279
+ machine_precision: int,
280
+ ) -> NumericAttributes:
281
+ if len(args) < 1:
282
+ raise ClassiqValueError("Max expects at least one argument")
283
+
284
+ args = [arg.trim_fraction_digits(machine_precision) for arg in args]
285
+
286
+ result_attrs = args[0]
287
+ for attrs in args[1:]:
288
+ if result_attrs.lb == result_attrs.ub == attrs.lb == attrs.ub:
289
+ if attrs.size < result_attrs.size:
290
+ result_attrs = attrs
291
+ elif result_attrs.lb >= attrs.ub:
292
+ pass
293
+ elif attrs.lb >= result_attrs.ub:
294
+ result_attrs = attrs
295
+ else:
296
+ integer_digits = max(result_attrs.integer_digits, attrs.integer_digits)
297
+ fraction_digits = max(result_attrs.fraction_digits, attrs.fraction_digits)
298
+ bounds = (max(result_attrs.lb, attrs.lb), max(result_attrs.ub, attrs.ub))
299
+ result_attrs = NumericAttributes(
300
+ size=integer_digits + fraction_digits,
301
+ is_signed=bounds[0] < 0,
302
+ fraction_digits=fraction_digits,
303
+ bounds=bounds,
304
+ )
305
+
306
+ return result_attrs
307
+
308
+
309
+ def compute_result_attrs_quantum_subscript(
310
+ values: Sequence[float],
311
+ machine_precision: int,
312
+ ) -> NumericAttributes:
313
+ if len(values) < 1:
314
+ raise ClassiqValueError("Quantum subscript expects at least one argument")
315
+
316
+ values_attrs = [
317
+ NumericAttributes.from_constant(val, machine_precision) for val in values
318
+ ]
319
+ values = [attrs.lb for attrs in values_attrs]
320
+ fraction_digits = max(attrs.fraction_digits for attrs in values_attrs)
321
+ return NumericAttributes.from_bounds(
322
+ min(values),
323
+ max(values),
324
+ fraction_digits,
325
+ machine_precision,
326
+ )
@@ -49,7 +49,6 @@ from classiq.model_expansions.transformers.model_renamer import (
49
49
  HandleRenaming,
50
50
  SymbolRenaming,
51
51
  )
52
- from classiq.model_expansions.transformers.var_splitter import SymbolPart
53
52
 
54
53
  if TYPE_CHECKING:
55
54
  from classiq.model_expansions.closure import FunctionClosure
@@ -603,10 +602,9 @@ class CapturedVars:
603
602
  def _get_immediate_captured_mapping(self) -> SymbolRenaming:
604
603
  return {
605
604
  captured_handle.handle: [
606
- SymbolPart(
605
+ HandleRenaming(
607
606
  source_handle=captured_handle.handle,
608
607
  target_var_name=captured_handle.mangled_name,
609
- target_var_type=captured_handle.quantum_type,
610
608
  )
611
609
  ]
612
610
  for captured_handle in self._captured_handles
@@ -616,10 +614,9 @@ class CapturedVars:
616
614
  def _get_propagated_captured_mapping(self) -> SymbolRenaming:
617
615
  return {
618
616
  captured_handle.mangled_handle: [
619
- SymbolPart(
617
+ HandleRenaming(
620
618
  source_handle=captured_handle.mangled_handle,
621
619
  target_var_name=captured_handle.mangled_name,
622
- target_var_type=captured_handle.quantum_type,
623
620
  )
624
621
  ]
625
622
  for captured_handle in self._captured_handles
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Union
3
3
  import sympy
4
4
 
5
5
  from classiq.interface.debug_info.debug_info import FunctionDebugInfo
6
- from classiq.interface.exceptions import ClassiqValueError
6
+ from classiq.interface.exceptions import ClassiqExpansionError, ClassiqValueError
7
7
  from classiq.interface.generator.expressions.expression import Expression
8
8
  from classiq.interface.model.allocate import Allocate
9
9
  from classiq.interface.model.handle_binding import NestedHandleBinding
@@ -12,7 +12,9 @@ from classiq.interface.model.quantum_type import (
12
12
  QuantumNumeric,
13
13
  )
14
14
 
15
- from classiq.evaluators.quantum_type_utils import copy_type_information
15
+ from classiq.evaluators.qmod_type_inference.quantum_type_inference import (
16
+ inject_quantum_type_attributes_inplace,
17
+ )
16
18
  from classiq.model_expansions.quantum_operations.emitter import Emitter
17
19
  from classiq.model_expansions.scope import QuantumSymbol
18
20
 
@@ -95,11 +97,14 @@ class AllocateEmitter(Emitter[Allocate]):
95
97
  size_value = self._interpret_size(size, str(target.handle))
96
98
  op_update_dict["size"] = Expression(expr=str(size_value))
97
99
 
98
- if not isinstance(size_value, sympy.Basic):
99
- copy_type_information(
100
- QuantumBitvector(length=op_update_dict["size"]),
101
- target.quantum_type,
102
- str(target.handle),
100
+ if not isinstance(
101
+ size_value, sympy.Basic
102
+ ) and not inject_quantum_type_attributes_inplace(
103
+ QuantumBitvector(length=op_update_dict["size"]), target.quantum_type
104
+ ):
105
+ raise ClassiqExpansionError(
106
+ f"Cannot allocate {op_update_dict['size']} qubits for variable "
107
+ f"{str(target)!r} of type {target.quantum_type.qmod_type_name}"
103
108
  )
104
109
 
105
110
  def _handle_with_numeric_attrs(
@@ -127,15 +132,17 @@ class AllocateEmitter(Emitter[Allocate]):
127
132
  isinstance(size_value, sympy.Basic)
128
133
  or isinstance(is_signed_value, sympy.Basic)
129
134
  or isinstance(fraction_digits_value, sympy.Basic)
135
+ ) and not inject_quantum_type_attributes_inplace(
136
+ QuantumNumeric(
137
+ size=op_update_dict["size"],
138
+ is_signed=op_update_dict["is_signed"],
139
+ fraction_digits=op_update_dict["fraction_digits"],
140
+ ),
141
+ target.quantum_type,
130
142
  ):
131
- copy_type_information(
132
- QuantumNumeric(
133
- size=op_update_dict["size"],
134
- is_signed=op_update_dict["is_signed"],
135
- fraction_digits=op_update_dict["fraction_digits"],
136
- ),
137
- target.quantum_type,
138
- var_name,
143
+ raise ClassiqExpansionError(
144
+ f"Cannot allocate {op_update_dict['size']} qubits for variable "
145
+ f"{var_name!r} of type {target.quantum_type.qmod_type_name}"
139
146
  )
140
147
 
141
148
  def _interpret_size(
@@ -44,9 +44,7 @@ def convert_inplace_op_bool_expression(
44
44
  if not is_bool(op.expression.expr):
45
45
  return
46
46
  _validate_target_type(target, op.expression.expr, op.operation_kind)
47
- op.expression = op.expression.model_copy(
48
- update=dict(expr="1" if op.expression.expr == "True" else "0")
49
- )
47
+ op.expression = Expression(expr="1" if op.expression.expr == "True" else "0")
50
48
 
51
49
 
52
50
  def _supported_types() -> tuple[str, ...]: