classiq 0.86.1__py3-none-any.whl → 0.88.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.

Potentially problematic release.


This version of classiq might be problematic. Click here for more details.

Files changed (131) hide show
  1. classiq/__init__.py +2 -0
  2. classiq/applications/__init__.py +1 -2
  3. classiq/applications/chemistry/hartree_fock.py +5 -1
  4. classiq/applications/chemistry/op_utils.py +2 -2
  5. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +1 -1
  6. classiq/applications/combinatorial_helpers/encoding_mapping.py +11 -15
  7. classiq/applications/combinatorial_helpers/encoding_utils.py +6 -6
  8. classiq/applications/combinatorial_helpers/memory.py +4 -4
  9. classiq/applications/combinatorial_helpers/optimization_model.py +5 -5
  10. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +6 -10
  11. classiq/applications/combinatorial_helpers/pyomo_utils.py +27 -26
  12. classiq/applications/combinatorial_helpers/sympy_utils.py +2 -2
  13. classiq/applications/combinatorial_helpers/transformations/encoding.py +4 -6
  14. classiq/applications/combinatorial_helpers/transformations/fixed_variables.py +4 -4
  15. classiq/applications/combinatorial_helpers/transformations/ising_converter.py +2 -2
  16. classiq/applications/combinatorial_helpers/transformations/penalty_support.py +3 -3
  17. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -0
  18. classiq/applications/hamiltonian/pauli_decomposition.py +34 -2
  19. classiq/evaluators/argument_types.py +15 -6
  20. classiq/evaluators/parameter_types.py +43 -39
  21. classiq/evaluators/qmod_annotated_expression.py +117 -17
  22. classiq/evaluators/qmod_expression_visitors/out_of_place_node_transformer.py +19 -0
  23. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +0 -5
  24. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +66 -16
  25. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +48 -26
  26. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +65 -72
  27. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +13 -6
  28. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +175 -28
  29. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +36 -19
  30. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +17 -5
  31. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +24 -2
  32. classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +97 -0
  33. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +11 -26
  34. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +56 -0
  35. classiq/evaluators/qmod_node_evaluators/piecewise_evaluation.py +40 -0
  36. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +3 -4
  37. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +51 -24
  38. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +53 -9
  39. classiq/evaluators/qmod_node_evaluators/utils.py +28 -6
  40. classiq/evaluators/qmod_type_inference/classical_type_inference.py +188 -0
  41. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +330 -0
  42. classiq/evaluators/quantum_type_utils.py +0 -131
  43. classiq/evaluators/type_type_match.py +1 -1
  44. classiq/execution/execution_session.py +18 -3
  45. classiq/execution/qnn.py +4 -1
  46. classiq/execution/user_budgets.py +1 -1
  47. classiq/interface/_version.py +1 -1
  48. classiq/interface/backend/backend_preferences.py +10 -30
  49. classiq/interface/backend/quantum_backend_providers.py +63 -52
  50. classiq/interface/execution/primitives.py +1 -0
  51. classiq/interface/generator/application_apis/__init__.py +0 -1
  52. classiq/interface/generator/arith/binary_ops.py +107 -115
  53. classiq/interface/generator/arith/extremum_operations.py +33 -45
  54. classiq/interface/generator/arith/number_utils.py +4 -1
  55. classiq/interface/generator/circuit_code/types_and_constants.py +0 -9
  56. classiq/interface/generator/compiler_keywords.py +2 -0
  57. classiq/interface/generator/expressions/atomic_expression_functions.py +0 -2
  58. classiq/interface/generator/function_param_list.py +129 -5
  59. classiq/interface/generator/functions/classical_type.py +67 -2
  60. classiq/interface/generator/functions/qmod_python_interface.py +15 -0
  61. classiq/interface/generator/functions/type_name.py +12 -0
  62. classiq/interface/generator/model/preferences/preferences.py +1 -17
  63. classiq/interface/generator/quantum_program.py +1 -13
  64. classiq/interface/generator/transpiler_basis_gates.py +5 -1
  65. classiq/interface/generator/types/builtin_enum_declarations.py +0 -8
  66. classiq/interface/helpers/model_normalizer.py +2 -2
  67. classiq/interface/helpers/text_utils.py +7 -2
  68. classiq/interface/interface_version.py +1 -1
  69. classiq/interface/model/classical_if.py +48 -0
  70. classiq/interface/model/classical_parameter_declaration.py +4 -0
  71. classiq/interface/model/handle_binding.py +28 -16
  72. classiq/interface/model/port_declaration.py +12 -0
  73. classiq/interface/model/quantum_function_declaration.py +12 -0
  74. classiq/interface/model/quantum_type.py +117 -2
  75. classiq/interface/pretty_print/expression_to_qmod.py +7 -8
  76. classiq/interface/pyomo_extension/__init__.py +0 -4
  77. classiq/interface/pyomo_extension/pyomo_sympy_bimap.py +2 -2
  78. classiq/model_expansions/arithmetic.py +43 -1
  79. classiq/model_expansions/arithmetic_compute_result_attrs.py +255 -0
  80. classiq/model_expansions/capturing/captured_vars.py +2 -5
  81. classiq/model_expansions/quantum_operations/allocate.py +23 -16
  82. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +1 -3
  83. classiq/model_expansions/quantum_operations/assignment_result_processor.py +52 -71
  84. classiq/model_expansions/quantum_operations/bind.py +15 -7
  85. classiq/model_expansions/quantum_operations/call_emitter.py +2 -10
  86. classiq/model_expansions/quantum_operations/classical_var_emitter.py +6 -0
  87. classiq/model_expansions/quantum_operations/handle_evaluator.py +2 -8
  88. classiq/open_library/functions/__init__.py +4 -0
  89. classiq/open_library/functions/lcu.py +117 -0
  90. classiq/open_library/functions/state_preparation.py +47 -5
  91. classiq/qmod/builtins/__init__.py +0 -3
  92. classiq/qmod/builtins/classical_functions.py +0 -28
  93. classiq/qmod/builtins/enums.py +26 -20
  94. classiq/qmod/builtins/functions/__init__.py +0 -5
  95. classiq/qmod/builtins/operations.py +142 -0
  96. classiq/qmod/builtins/structs.py +33 -29
  97. classiq/qmod/native/pretty_printer.py +1 -1
  98. classiq/qmod/pretty_print/expression_to_python.py +1 -6
  99. classiq/qmod/pretty_print/pretty_printer.py +4 -1
  100. classiq/qmod/qmod_variable.py +94 -2
  101. classiq/qmod/semantics/annotation/call_annotation.py +4 -2
  102. classiq/qmod/semantics/annotation/qstruct_annotator.py +20 -5
  103. {classiq-0.86.1.dist-info → classiq-0.88.0.dist-info}/METADATA +5 -5
  104. {classiq-0.86.1.dist-info → classiq-0.88.0.dist-info}/RECORD +106 -124
  105. classiq/applications/finance/__init__.py +0 -15
  106. classiq/interface/finance/finance_modelling_params.py +0 -11
  107. classiq/interface/finance/function_input.py +0 -102
  108. classiq/interface/finance/gaussian_model_input.py +0 -50
  109. classiq/interface/finance/log_normal_model_input.py +0 -40
  110. classiq/interface/finance/model_input.py +0 -22
  111. classiq/interface/generator/amplitude_estimation.py +0 -34
  112. classiq/interface/generator/application_apis/finance_declarations.py +0 -108
  113. classiq/interface/generator/expressions/enums/__init__.py +0 -0
  114. classiq/interface/generator/expressions/enums/finance_functions.py +0 -12
  115. classiq/interface/generator/finance.py +0 -107
  116. classiq/interface/generator/function_param_list_without_self_reference.py +0 -160
  117. classiq/interface/generator/grover_diffuser.py +0 -93
  118. classiq/interface/generator/grover_operator.py +0 -106
  119. classiq/interface/generator/oracles/__init__.py +0 -3
  120. classiq/interface/generator/oracles/arithmetic_oracle.py +0 -82
  121. classiq/interface/generator/oracles/custom_oracle.py +0 -65
  122. classiq/interface/generator/oracles/oracle_abc.py +0 -76
  123. classiq/interface/generator/oracles/oracle_function_param_list.py +0 -6
  124. classiq/interface/generator/piecewise_linear_amplitude_loading.py +0 -165
  125. classiq/interface/generator/qpe.py +0 -169
  126. classiq/interface/grover/__init__.py +0 -0
  127. classiq/interface/grover/grover_modelling_params.py +0 -13
  128. classiq/model_expansions/transformers/var_splitter.py +0 -224
  129. classiq/qmod/builtins/functions/finance.py +0 -34
  130. /classiq/{interface/finance → evaluators/qmod_type_inference}/__init__.py +0 -0
  131. {classiq-0.86.1.dist-info → classiq-0.88.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
@@ -76,6 +84,10 @@ class QuantumType(HashableASTNode):
76
84
  def is_evaluated(self) -> bool:
77
85
  raise NotImplementedError
78
86
 
87
+ @property
88
+ def is_constant(self) -> bool:
89
+ raise NotImplementedError
90
+
79
91
  @property
80
92
  def expressions(self) -> list[Expression]:
81
93
  return []
@@ -101,6 +113,9 @@ class QuantumScalar(QuantumType):
101
113
  def fraction_digits_value(self) -> int:
102
114
  raise NotImplementedError
103
115
 
116
+ def get_bounds(self) -> Optional[tuple[float, float]]:
117
+ return None
118
+
104
119
  def get_effective_bounds(
105
120
  self, machine_precision: Optional[int] = None
106
121
  ) -> tuple[float, float]:
@@ -146,6 +161,10 @@ class QuantumBit(QuantumScalar):
146
161
  def is_evaluated(self) -> bool:
147
162
  return True
148
163
 
164
+ @property
165
+ def is_constant(self) -> bool:
166
+ return True
167
+
149
168
  @property
150
169
  def has_sign(self) -> bool:
151
170
  return True
@@ -167,6 +186,10 @@ class QuantumBit(QuantumScalar):
167
186
  ) -> tuple[float, float]:
168
187
  return (0, 1)
169
188
 
189
+ @property
190
+ def minimal_size_in_bits(self) -> int:
191
+ return 1
192
+
170
193
 
171
194
  class QuantumBitvector(QuantumType):
172
195
  element_type: "ConcreteQuantumType" = Field(
@@ -182,7 +205,7 @@ class QuantumBitvector(QuantumType):
182
205
 
183
206
  def _update_size_in_bits_from_declaration(self) -> None:
184
207
  self.element_type._update_size_in_bits_from_declaration()
185
- if self.element_type.has_size_in_bits and self.has_length:
208
+ if self.element_type.has_size_in_bits and self.has_constant_length:
186
209
  assert self.length is not None
187
210
  self._size_in_bits = (
188
211
  self.element_type.size_in_bits * self.length.to_int_value()
@@ -192,6 +215,14 @@ class QuantumBitvector(QuantumType):
192
215
  def has_length(self) -> bool:
193
216
  return self.length is not None and self.length.is_evaluated()
194
217
 
218
+ @property
219
+ def has_constant_length(self) -> bool:
220
+ return (
221
+ self.length is not None
222
+ and self.length.is_evaluated()
223
+ and self.length.is_constant()
224
+ )
225
+
195
226
  @property
196
227
  def length_value(self) -> int:
197
228
  if not self.has_length:
@@ -217,6 +248,10 @@ class QuantumBitvector(QuantumType):
217
248
  length = [self.length.expr] if self.length is not None else []
218
249
  return f"QArray[{', '.join(element_type + length)}]"
219
250
 
251
+ @property
252
+ def raw_qmod_type_name(self) -> str:
253
+ return "QArray"
254
+
220
255
  @property
221
256
  def type_name(self) -> str:
222
257
  return "Quantum array"
@@ -233,6 +268,15 @@ class QuantumBitvector(QuantumType):
233
268
  and self.element_type.is_evaluated
234
269
  )
235
270
 
271
+ @property
272
+ def is_constant(self) -> bool:
273
+ return (
274
+ self.length is not None
275
+ and self.length.is_evaluated()
276
+ and self.length.is_constant()
277
+ and self.element_type.is_constant
278
+ )
279
+
236
280
  @property
237
281
  def expressions(self) -> list[Expression]:
238
282
  exprs = self.element_type.expressions
@@ -240,6 +284,14 @@ class QuantumBitvector(QuantumType):
240
284
  exprs.append(self.length)
241
285
  return exprs
242
286
 
287
+ @property
288
+ def minimal_size_in_bits(self) -> int:
289
+ if self.has_constant_length:
290
+ length = self.length_value
291
+ else:
292
+ length = 1
293
+ return length * self.element_type.minimal_size_in_bits
294
+
243
295
 
244
296
  class QuantumNumeric(QuantumScalar):
245
297
  kind: Literal["qnum"]
@@ -279,6 +331,14 @@ class QuantumNumeric(QuantumScalar):
279
331
  def has_sign(self) -> bool:
280
332
  return self.is_signed is not None
281
333
 
334
+ @property
335
+ def has_constant_sign(self) -> bool:
336
+ return (
337
+ self.is_signed is not None
338
+ and self.is_signed.is_evaluated()
339
+ and self.is_signed.is_constant()
340
+ )
341
+
282
342
  @property
283
343
  def sign_value(self) -> bool:
284
344
  return False if self.is_signed is None else self.is_signed.to_bool_value()
@@ -287,6 +347,14 @@ class QuantumNumeric(QuantumScalar):
287
347
  def has_fraction_digits(self) -> bool:
288
348
  return self.fraction_digits is not None
289
349
 
350
+ @property
351
+ def has_constant_fraction_digits(self) -> bool:
352
+ return (
353
+ self.fraction_digits is not None
354
+ and self.fraction_digits.is_evaluated()
355
+ and self.fraction_digits.is_constant()
356
+ )
357
+
290
358
  @property
291
359
  def fraction_digits_value(self) -> int:
292
360
  return (
@@ -294,7 +362,11 @@ class QuantumNumeric(QuantumScalar):
294
362
  )
295
363
 
296
364
  def _update_size_in_bits_from_declaration(self) -> None:
297
- if self.size is not None and self.size.is_evaluated():
365
+ if (
366
+ self.size is not None
367
+ and self.size.is_evaluated()
368
+ and self.size.is_constant()
369
+ ):
298
370
  self._size_in_bits = self.size.to_int_value()
299
371
 
300
372
  def get_proxy(self, handle: "HandleBinding") -> QmodQNumProxy:
@@ -307,6 +379,21 @@ class QuantumNumeric(QuantumScalar):
307
379
 
308
380
  @property
309
381
  def qmod_type_name(self) -> str:
382
+ if (
383
+ self.size is not None
384
+ and (
385
+ self.is_signed is None
386
+ or (self.is_signed.is_evaluated() and not self.is_signed.value.value)
387
+ )
388
+ and (
389
+ self.fraction_digits is None
390
+ or (
391
+ self.fraction_digits.is_evaluated()
392
+ and self.fraction_digits.value.value == 0
393
+ )
394
+ )
395
+ ):
396
+ return f"QNum[{self.size.expr}]"
310
397
  if (
311
398
  self.size is not None
312
399
  and self.is_signed is not None
@@ -315,6 +402,10 @@ class QuantumNumeric(QuantumScalar):
315
402
  return f"QNum[{self.size.expr}, {self.is_signed.expr}, {self.fraction_digits.expr}]"
316
403
  return "QNum"
317
404
 
405
+ @property
406
+ def raw_qmod_type_name(self) -> str:
407
+ return "QNum"
408
+
318
409
  @property
319
410
  def type_name(self) -> str:
320
411
  return "Quantum numeric"
@@ -333,6 +424,26 @@ class QuantumNumeric(QuantumScalar):
333
424
  self.fraction_digits is not None and not self.fraction_digits.is_evaluated()
334
425
  )
335
426
 
427
+ @property
428
+ def is_constant(self) -> bool:
429
+ if (
430
+ self.size is None
431
+ or not self.size.is_evaluated()
432
+ or not self.size.is_constant()
433
+ ):
434
+ return False
435
+ if self.is_signed is not None and (
436
+ not self.is_signed.is_evaluated() or not self.is_signed.is_constant()
437
+ ):
438
+ return False
439
+ return not (
440
+ self.fraction_digits is not None
441
+ and (
442
+ not self.fraction_digits.is_evaluated()
443
+ or not self.fraction_digits.is_constant()
444
+ )
445
+ )
446
+
336
447
  @property
337
448
  def expressions(self) -> list[Expression]:
338
449
  exprs = []
@@ -372,6 +483,10 @@ class QuantumNumeric(QuantumScalar):
372
483
  number_utils.limit_fraction_places(bounds[1], machine_precision),
373
484
  )
374
485
 
486
+ @property
487
+ def minimal_size_in_bits(self) -> int:
488
+ return self.size_in_bits if self.has_size_in_bits else 1
489
+
375
490
 
376
491
  class RegisterQuantumType(BaseModel):
377
492
  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):
@@ -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