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
@@ -1,22 +1,18 @@
1
1
  import ast
2
- from typing import Any, Optional, cast
2
+ from typing import Any, cast
3
3
 
4
4
  import sympy
5
5
 
6
6
  from classiq.interface.exceptions import ClassiqInternalExpansionError
7
- from classiq.interface.generator.arith.arithmetic import compute_arithmetic_result_type
8
- from classiq.interface.generator.expressions.expression import Expression
9
7
  from classiq.interface.model.handle_binding import HandleBinding
10
- from classiq.interface.model.quantum_type import (
11
- QuantumBit,
12
- QuantumNumeric,
13
- QuantumType,
14
- )
15
8
 
16
9
  from classiq.evaluators.qmod_annotated_expression import (
17
10
  QmodAnnotatedExpression,
18
11
  QmodExprNodeId,
19
12
  )
13
+ from classiq.evaluators.qmod_expression_visitors.out_of_place_node_transformer import (
14
+ OutOfPlaceNodeTransformer,
15
+ )
20
16
  from classiq.evaluators.qmod_expression_visitors.sympy_wrappers import (
21
17
  BitwiseAnd,
22
18
  BitwiseNot,
@@ -25,7 +21,7 @@ from classiq.evaluators.qmod_expression_visitors.sympy_wrappers import (
25
21
  LShift,
26
22
  RShift,
27
23
  )
28
- from classiq.evaluators.qmod_node_evaluators.utils import QmodType
24
+ from classiq.model_expansions.atomic_expression_functions_defs import do_div
29
25
 
30
26
  _SYMPY_WRAPPERS = {
31
27
  wrapper.__name__: wrapper
@@ -37,10 +33,12 @@ _SYMPY_WRAPPERS = {
37
33
  LShift,
38
34
  RShift,
39
35
  ]
36
+ } | {
37
+ do_div.__name__: do_div,
40
38
  }
41
39
 
42
40
 
43
- class _VarMaskTransformer(ast.NodeTransformer):
41
+ class _VarMaskTransformer(OutOfPlaceNodeTransformer):
44
42
  def __init__(self, expr_val: QmodAnnotatedExpression) -> None:
45
43
  self._expr_val = expr_val
46
44
  self._mask_id = 0
@@ -68,7 +66,7 @@ class _VarMaskTransformer(ast.NodeTransformer):
68
66
  mask = self._create_mask()
69
67
  self.masks[mask] = id(node)
70
68
  return ast.Name(id=mask)
71
- return self.generic_visit(node)
69
+ return super().visit(node)
72
70
 
73
71
  def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
74
72
  mask = self._create_mask()
@@ -76,7 +74,7 @@ class _VarMaskTransformer(ast.NodeTransformer):
76
74
  return ast.Name(id=mask)
77
75
 
78
76
 
79
- class _InverseVarMaskTransformer(ast.NodeTransformer):
77
+ class _InverseVarMaskTransformer(OutOfPlaceNodeTransformer):
80
78
  def __init__(
81
79
  self, expr_val: QmodAnnotatedExpression, masks: dict[str, QmodExprNodeId]
82
80
  ) -> None:
@@ -93,7 +91,7 @@ class _InverseVarMaskTransformer(ast.NodeTransformer):
93
91
  return node
94
92
 
95
93
 
96
- class _SympyCompatibilityTransformer(ast.NodeTransformer):
94
+ class _SympyCompatibilityTransformer(OutOfPlaceNodeTransformer):
97
95
  def visit_BoolOp(self, node: ast.BoolOp) -> ast.Call:
98
96
  if len(node.values) != 2:
99
97
  raise ClassiqInternalExpansionError
@@ -151,6 +149,12 @@ class _SympyCompatibilityTransformer(ast.NodeTransformer):
151
149
  sympy_func = BitwiseXor.__name__
152
150
  elif isinstance(node.op, ast.BitAnd):
153
151
  sympy_func = BitwiseAnd.__name__
152
+ elif isinstance(node.op, ast.Div):
153
+ return ast.Call(
154
+ func=ast.Name(id=do_div.__name__),
155
+ args=[node.left, node.right],
156
+ keywords=[],
157
+ )
154
158
  else:
155
159
  return node
156
160
  return ast.Call(
@@ -158,7 +162,7 @@ class _SympyCompatibilityTransformer(ast.NodeTransformer):
158
162
  )
159
163
 
160
164
 
161
- class _InverseSympyCompatibilityTransformer(ast.NodeTransformer):
165
+ class _InverseSympyCompatibilityTransformer(OutOfPlaceNodeTransformer):
162
166
  def visit_Call(self, node: ast.Call) -> Any:
163
167
  node = cast(ast.Call, self.generic_visit(node))
164
168
  if not isinstance(node.func, ast.Name):
@@ -183,12 +187,12 @@ class _InverseSympyCompatibilityTransformer(ast.NodeTransformer):
183
187
  and (len(node.args) != 2 or len(node.keywords) > 0)
184
188
  ) or (
185
189
  func in {BitwiseNot.__name__}
186
- and (len(node.args) != 2 or len(node.keywords) > 0)
190
+ and (len(node.args) != 1 or len(node.keywords) > 0)
187
191
  ):
188
192
  raise ClassiqInternalExpansionError
189
193
 
190
194
  if func == BitwiseNot.__name__:
191
- return ast.UnaryOp(op=ast.Invert, operand=node.args[0])
195
+ return ast.UnaryOp(op=ast.Invert(), operand=node.args[0])
192
196
 
193
197
  if func == "Eq":
194
198
  return ast.Compare(
@@ -226,6 +230,13 @@ class _InverseSympyCompatibilityTransformer(ast.NodeTransformer):
226
230
  if func == BitwiseAnd.__name__:
227
231
  return ast.BinOp(left=node.args[0], op=ast.BitAnd(), right=node.args[1])
228
232
 
233
+ if func == "Mod":
234
+ return ast.BinOp(left=node.args[0], op=ast.Mod(), right=node.args[1])
235
+ if func == "Max":
236
+ node.func.id = "max"
237
+ elif func == "Min":
238
+ node.func.id = "min"
239
+
229
240
  return node
230
241
 
231
242
  def visit_UnaryOp(self, node: ast.UnaryOp) -> Any:
@@ -247,46 +258,12 @@ class _InverseSympyCompatibilityTransformer(ast.NodeTransformer):
247
258
  return node
248
259
 
249
260
 
250
- def _get_numeric_type(
251
- expr_val: QmodAnnotatedExpression,
252
- expr_type: QmodType,
253
- simplified_expr: str,
254
- masks: dict[str, QmodExprNodeId],
255
- machine_precision: int,
256
- ) -> Optional[QuantumNumeric]:
257
- if isinstance(expr_type, QuantumBit):
258
- return QuantumNumeric(
259
- size=Expression(expr="1"),
260
- is_signed=Expression(expr="False"),
261
- fraction_digits=Expression(expr="0"),
262
- )
263
- if not isinstance(expr_type, QuantumNumeric):
264
- return None
265
- if expr_type.is_evaluated:
266
- return expr_type
267
- var_types = {
268
- var_name: expr_val.get_type(node_id) for var_name, node_id in masks.items()
269
- }
270
- if not all(
271
- isinstance(var_type, QuantumBit)
272
- or (isinstance(var_type, QuantumNumeric) and var_type.is_instantiated)
273
- for var_type in var_types.values()
274
- ):
275
- return None
276
- return compute_arithmetic_result_type(
277
- simplified_expr, cast(dict[str, QuantumType], var_types), machine_precision
278
- )
279
-
280
-
281
- def simplify_qmod_expression(
282
- expr_val: QmodAnnotatedExpression, machine_precision: int
283
- ) -> tuple[str, Optional[QuantumNumeric]]:
261
+ def simplify_qmod_expression(expr_val: QmodAnnotatedExpression) -> str:
284
262
  if expr_val.has_value(expr_val.root):
285
263
  raise ClassiqInternalExpansionError(
286
264
  "This expression is a constant value. No need for simplification"
287
265
  )
288
266
  var_mask_transformer = _VarMaskTransformer(expr_val)
289
- masks = var_mask_transformer.masks
290
267
  mask_expr = var_mask_transformer.visit(expr_val.root)
291
268
  sympy_expr = _SympyCompatibilityTransformer().visit(mask_expr)
292
269
  simplified_expr = str(
@@ -295,14 +272,7 @@ def simplify_qmod_expression(
295
272
  restored_expr = _InverseSympyCompatibilityTransformer().visit(
296
273
  ast.parse(simplified_expr, mode="eval")
297
274
  )
298
- expr_type = _get_numeric_type(
299
- expr_val,
300
- expr_val.get_type(expr_val.root),
301
- ast.unparse(restored_expr),
302
- masks,
303
- machine_precision,
304
- )
305
275
  restored_expr = _InverseVarMaskTransformer(
306
276
  expr_val, var_mask_transformer.masks
307
277
  ).visit(restored_expr)
308
- return ast.unparse(restored_expr), expr_type
278
+ return ast.unparse(restored_expr)
@@ -23,7 +23,7 @@ from classiq.interface.model.quantum_type import (
23
23
  )
24
24
 
25
25
  from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
26
- from classiq.evaluators.qmod_node_evaluators.utils import QmodType, get_qmod_type_name
26
+ from classiq.evaluators.qmod_node_evaluators.utils import QmodType
27
27
 
28
28
 
29
29
  def _eval_type_attribute(
@@ -71,7 +71,7 @@ def _eval_type_attribute(
71
71
  expr_val.set_quantum_type_attr(node, subject, attr)
72
72
  return
73
73
  raise ClassiqExpansionError(
74
- f"{get_qmod_type_name(subject_type)} has no attribute {attr!r}"
74
+ f"{subject_type.raw_qmod_type_name} has no attribute {attr!r}"
75
75
  )
76
76
 
77
77
 
@@ -94,7 +94,7 @@ def eval_attribute(expr_val: QmodAnnotatedExpression, node: ast.Attribute) -> No
94
94
  raise ClassiqInternalExpansionError
95
95
  if attr not in subject_fields:
96
96
  raise ClassiqExpansionError(
97
- f"{get_qmod_type_name(subject_type)} has no field {attr!r}"
97
+ f"{subject_type.raw_qmod_type_name} has no field {attr!r}"
98
98
  )
99
99
  expr_val.set_type(node, subject_fields[attr])
100
100
 
@@ -103,9 +103,16 @@ def eval_attribute(expr_val: QmodAnnotatedExpression, node: ast.Attribute) -> No
103
103
  if (
104
104
  not isinstance(subject_value, QmodStructInstance)
105
105
  or attr not in subject_value.fields
106
- ):
106
+ ) and (not isinstance(subject_value, dict) or attr not in subject_value):
107
107
  raise ClassiqInternalExpansionError
108
- expr_val.set_value(node, subject_value.fields[attr])
108
+ if isinstance(subject_value, QmodStructInstance):
109
+ attr_value = subject_value.fields[attr]
110
+ else:
111
+ # dicts are supported because our foreign funcs return dicts instead of
112
+ # QmodStructInstances
113
+ # FIXME: Remove (CLS-3241)
114
+ attr_value = subject_value[attr]
115
+ expr_val.set_value(node, attr_value)
109
116
  elif expr_val.has_var(subject):
110
117
  subject_var = expr_val.get_var(subject)
111
118
  expr_val.set_var(node, FieldHandleBinding(base_handle=subject_var, field=attr))
@@ -5,10 +5,14 @@ from classiq.interface.exceptions import (
5
5
  ClassiqExpansionError,
6
6
  ClassiqInternalExpansionError,
7
7
  )
8
- from classiq.interface.generator.functions.classical_type import Integer, Real
8
+ from classiq.interface.generator.functions.classical_type import Bool, Integer, Real
9
9
  from classiq.interface.model.quantum_type import QuantumNumeric
10
10
 
11
11
  from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
12
+ from classiq.evaluators.qmod_node_evaluators.numeric_attrs_utils import (
13
+ get_classical_value_for_arithmetic,
14
+ get_numeric_attrs,
15
+ )
12
16
  from classiq.evaluators.qmod_node_evaluators.utils import (
13
17
  IntegerValueType,
14
18
  NumberValueType,
@@ -17,14 +21,38 @@ from classiq.evaluators.qmod_node_evaluators.utils import (
17
21
  is_classical_type,
18
22
  is_numeric_type,
19
23
  )
24
+ from classiq.model_expansions.arithmetic import NumericAttributes
25
+ from classiq.model_expansions.arithmetic_compute_result_attrs import (
26
+ compute_result_attrs_add,
27
+ compute_result_attrs_bitwise_and,
28
+ compute_result_attrs_bitwise_or,
29
+ compute_result_attrs_bitwise_xor,
30
+ compute_result_attrs_lshift,
31
+ compute_result_attrs_modulo,
32
+ compute_result_attrs_multiply,
33
+ compute_result_attrs_power,
34
+ compute_result_attrs_rshift,
35
+ compute_result_attrs_subtract,
36
+ )
20
37
 
21
38
 
22
- def _binary_op_allowed(left: QmodType, right: QmodType) -> bool:
23
- return is_numeric_type(left) and is_numeric_type(right)
39
+ def _binary_op_allowed(left: QmodType, right: QmodType, op: ast.AST) -> bool:
40
+ left_numeric = is_numeric_type(left)
41
+ right_numeric = is_numeric_type(right)
42
+ if isinstance(op, (ast.BitOr, ast.BitAnd, ast.BitXor)):
43
+ return (left_numeric or isinstance(left, Bool)) and (
44
+ right_numeric or isinstance(right, Bool)
45
+ )
46
+ return left_numeric and right_numeric
24
47
 
25
48
 
26
- def _validate_binary_op(op: ast.AST, left_type: QmodType, right_type: QmodType) -> None:
27
- if not _binary_op_allowed(left_type, right_type):
49
+ def _validate_binary_op(
50
+ op: ast.AST,
51
+ left_type: QmodType,
52
+ right_type: QmodType,
53
+ treat_qnum_as_float: bool,
54
+ ) -> None:
55
+ if not _binary_op_allowed(left_type, right_type, op):
28
56
  raise ClassiqExpansionError(
29
57
  f"Both sides of the binary operator {type(op).__name__!r} must be "
30
58
  f"scalar values"
@@ -48,15 +76,132 @@ def _validate_binary_op(op: ast.AST, left_type: QmodType, right_type: QmodType)
48
76
  f"Binary operation {type(op).__name__!r} is not supported"
49
77
  )
50
78
 
51
- if not is_classical_type(right_type) and (
52
- isinstance(
53
- op, (ast.Div, ast.FloorDiv, ast.Mod, ast.Pow, ast.LShift, ast.RShift)
79
+ if not treat_qnum_as_float:
80
+ if isinstance(op, ast.FloorDiv) and (
81
+ not is_classical_type(left_type) or not is_classical_type(right_type)
82
+ ):
83
+ raise ClassiqExpansionError(
84
+ f"{type(op).__name__!r} with quantum variables is not supported"
85
+ )
86
+
87
+ if not is_classical_type(right_type) and isinstance(
88
+ op, (ast.Div, ast.Mod, ast.Pow, ast.LShift, ast.RShift)
89
+ ):
90
+ raise ClassiqExpansionError(
91
+ f"Right-hand side of binary operation {type(op).__name__!r} must be classical numeric value"
92
+ )
93
+
94
+
95
+ def _infer_binary_op_type(
96
+ expr_val: QmodAnnotatedExpression,
97
+ node: ast.BinOp,
98
+ left_type: QmodType,
99
+ right_type: QmodType,
100
+ machine_precision: int,
101
+ treat_qnum_as_float: bool,
102
+ ) -> QmodType:
103
+ op = node.op
104
+
105
+ if is_classical_type(left_type) and is_classical_type(right_type):
106
+ if isinstance(left_type, Bool) and isinstance(right_type, Bool):
107
+ return Bool()
108
+ if (
109
+ not isinstance(op, ast.Div)
110
+ and (is_classical_integer(left_type) or isinstance(left_type, Bool))
111
+ and (is_classical_integer(right_type) or isinstance(right_type, Bool))
112
+ ):
113
+ return Integer()
114
+ return Real()
115
+
116
+ left_attrs = get_numeric_attrs(
117
+ expr_val, node.left, left_type, machine_precision, treat_qnum_as_float
118
+ )
119
+ right_attrs = get_numeric_attrs(
120
+ expr_val, node.right, right_type, machine_precision, treat_qnum_as_float
121
+ )
122
+
123
+ if left_attrs is None or right_attrs is None:
124
+ return QuantumNumeric()
125
+
126
+ right_value = get_classical_value_for_arithmetic(
127
+ expr_val, node.right, right_type, treat_qnum_as_float
128
+ )
129
+
130
+ if isinstance(op, ast.Add):
131
+ result_attrs = compute_result_attrs_add(
132
+ left_attrs, right_attrs, machine_precision
54
133
  )
55
- ):
56
- raise ClassiqExpansionError(
57
- f"Right-hand side of binary operation {type(op).__name__!r} must be classical numeric value"
134
+
135
+ elif isinstance(op, ast.Sub):
136
+ result_attrs = compute_result_attrs_subtract(
137
+ left_attrs, right_attrs, machine_precision
58
138
  )
59
139
 
140
+ elif isinstance(op, ast.Mult):
141
+ result_attrs = compute_result_attrs_multiply(
142
+ left_attrs, right_attrs, machine_precision
143
+ )
144
+
145
+ elif isinstance(op, ast.Div):
146
+ if right_value is None:
147
+ return QuantumNumeric()
148
+ if right_value == 0:
149
+ raise ClassiqExpansionError("Division by zero")
150
+ right_attrs = NumericAttributes.from_constant(
151
+ 1 / right_value, machine_precision
152
+ )
153
+ result_attrs = compute_result_attrs_multiply(
154
+ left_attrs, right_attrs, machine_precision
155
+ )
156
+ elif isinstance(op, ast.FloorDiv):
157
+ return QuantumNumeric()
158
+
159
+ elif isinstance(op, ast.Mod):
160
+ if right_value is None or treat_qnum_as_float:
161
+ return QuantumNumeric()
162
+ result_attrs = compute_result_attrs_modulo(
163
+ left_attrs, right_value, machine_precision
164
+ )
165
+
166
+ elif isinstance(op, ast.Pow):
167
+ if right_value is None or treat_qnum_as_float:
168
+ return QuantumNumeric()
169
+ result_attrs = compute_result_attrs_power(
170
+ left_attrs, right_value, machine_precision
171
+ )
172
+
173
+ elif isinstance(op, ast.LShift):
174
+ if right_value is None:
175
+ return QuantumNumeric()
176
+ result_attrs = compute_result_attrs_lshift(
177
+ left_attrs, right_value, machine_precision
178
+ )
179
+
180
+ elif isinstance(op, ast.RShift):
181
+ if right_value is None:
182
+ return QuantumNumeric()
183
+ result_attrs = compute_result_attrs_rshift(
184
+ left_attrs, right_value, machine_precision
185
+ )
186
+
187
+ elif isinstance(op, ast.BitAnd):
188
+ result_attrs = compute_result_attrs_bitwise_and(
189
+ left_attrs, right_attrs, machine_precision
190
+ )
191
+ elif isinstance(op, ast.BitOr):
192
+ result_attrs = compute_result_attrs_bitwise_or(
193
+ left_attrs, right_attrs, machine_precision
194
+ )
195
+
196
+ elif isinstance(op, ast.BitXor):
197
+ result_attrs = compute_result_attrs_bitwise_xor(
198
+ left_attrs, right_attrs, machine_precision
199
+ )
200
+ else:
201
+ raise ClassiqInternalExpansionError
202
+
203
+ return result_attrs.to_quantum_numeric()
204
+
60
205
 
61
206
  def _eval_binary_op_constant(
62
207
  op: ast.AST, left_value: NumberValueType, right_value: NumberValueType
@@ -79,9 +224,9 @@ def _eval_binary_op_constant(
79
224
  return left_value // right_value
80
225
  if isinstance(op, ast.Mod):
81
226
  if right_value == 0:
82
- raise ClassiqExpansionError("Integer modulu by zero")
227
+ raise ClassiqExpansionError("Integer modulo by zero")
83
228
  if isinstance(left_value, complex) or isinstance(right_value, complex):
84
- raise ClassiqExpansionError("Integer modulu with a complex number")
229
+ raise ClassiqExpansionError("Integer modulo with a complex number")
85
230
  return left_value % right_value
86
231
  if isinstance(op, ast.Pow):
87
232
  return left_value**right_value
@@ -104,27 +249,29 @@ def _eval_binary_op_constant(
104
249
  raise ClassiqInternalExpansionError
105
250
 
106
251
 
107
- def eval_binary_op(expr_val: QmodAnnotatedExpression, node: ast.BinOp) -> None:
252
+ def eval_binary_op(
253
+ expr_val: QmodAnnotatedExpression,
254
+ node: ast.BinOp,
255
+ treat_qnum_as_float: bool,
256
+ machine_precision: int,
257
+ ) -> None:
108
258
  left = node.left
109
259
  right = node.right
110
260
  op = node.op
111
261
 
112
262
  left_type = expr_val.get_type(left)
113
263
  right_type = expr_val.get_type(right)
114
- _validate_binary_op(op, left_type, right_type)
115
-
116
- node_type: QmodType
117
- if not is_classical_type(left_type) or not is_classical_type(left_type):
118
- node_type = QuantumNumeric()
119
- elif (
120
- not isinstance(op, ast.Div)
121
- and is_classical_integer(left_type)
122
- and is_classical_integer(right_type)
123
- ):
124
- node_type = Integer()
125
- else:
126
- node_type = Real()
127
- expr_val.set_type(node, node_type)
264
+ _validate_binary_op(op, left_type, right_type, treat_qnum_as_float)
265
+
266
+ inferred_type = _infer_binary_op_type(
267
+ expr_val,
268
+ node,
269
+ left_type,
270
+ right_type,
271
+ machine_precision,
272
+ treat_qnum_as_float,
273
+ )
274
+ expr_val.set_type(node, inferred_type)
128
275
 
129
276
  if expr_val.has_value(left) and expr_val.has_value(right):
130
277
  left_value = expr_val.get_value(left)
@@ -28,10 +28,11 @@ from classiq.evaluators.qmod_node_evaluators.utils import (
28
28
  QmodType,
29
29
  array_len,
30
30
  element_types,
31
- get_qmod_type_name,
31
+ is_classical_integer,
32
32
  )
33
33
 
34
- MIN_MAX_FUNCTION_NAMES = {"min", "Min", "max", "Max"}
34
+ # These sympy functions are not declared as int funcs for some reason...
35
+ INTEGER_FUNCTION_OVERRIDE = {"floor", "ceiling"}
35
36
 
36
37
 
37
38
  def _check_classical_array_arg_type(
@@ -91,8 +92,8 @@ def _check_classical_arg(
91
92
  ):
92
93
  raise ClassiqExpansionError(
93
94
  f"Parameter {param_name!r} of function {func_name!r} expects a "
94
- f"{get_qmod_type_name(param_type)} argument, but got a "
95
- f"{get_qmod_type_name(arg_type)}"
95
+ f"{param_type.qmod_type_name} argument, but got a "
96
+ f"{arg_type.qmod_type_name}"
96
97
  )
97
98
 
98
99
 
@@ -191,7 +192,9 @@ def try_eval_sympy_function(
191
192
  ret_type = Bool()
192
193
  if args is not None:
193
194
  ret_val = bool(sympy_ret_val)
194
- elif hasattr(sympy_func, "is_Integer") and sympy_func.is_Integer:
195
+ elif (
196
+ hasattr(sympy_func, "is_Integer") and sympy_func.is_Integer
197
+ ) or func_name in INTEGER_FUNCTION_OVERRIDE:
195
198
  ret_type = Integer()
196
199
  if args is not None:
197
200
  ret_val = int(sympy_ret_val)
@@ -241,15 +244,6 @@ def try_eval_builtin_function(
241
244
  )
242
245
  args_have_values = all(expr_val.has_value(arg) for arg in node.args)
243
246
 
244
- if func_name in MIN_MAX_FUNCTION_NAMES:
245
- if not try_eval_sympy_function(expr_val, node, func_name.capitalize()):
246
- return False
247
- if args_are_int:
248
- expr_val.set_type(node, Integer())
249
- if expr_val.has_value(node):
250
- expr_val.set_value(node, int(expr_val.get_value(node)))
251
- return True
252
-
253
247
  if func_name == "mod_inverse":
254
248
  _validate_no_kwargs(node)
255
249
  ret_type: QmodType
@@ -298,6 +292,19 @@ def try_eval_builtin_function(
298
292
  )
299
293
  return True
300
294
 
295
+ if func_name == "abs":
296
+ _validate_no_kwargs(node)
297
+ if len(node.args) > 0 and is_classical_integer(expr_val.get_type(node.args[0])):
298
+ ret_type = Integer()
299
+ else:
300
+ ret_type = Real()
301
+ expr_val.set_type(node, ret_type)
302
+ if args_have_values:
303
+ expr_val.set_value(
304
+ node, sympy.Abs(*[expr_val.get_value(arg) for arg in node.args])
305
+ )
306
+ return True
307
+
301
308
  return False
302
309
 
303
310
 
@@ -14,6 +14,7 @@ from classiq.interface.generator.functions.classical_type import (
14
14
  from classiq.interface.generator.functions.type_name import TypeName
15
15
  from classiq.interface.model.quantum_type import (
16
16
  QuantumBit,
17
+ QuantumBitvector,
17
18
  QuantumNumeric,
18
19
  QuantumScalar,
19
20
  )
@@ -23,7 +24,6 @@ from classiq.evaluators.qmod_node_evaluators.list_evaluation import list_allowed
23
24
  from classiq.evaluators.qmod_node_evaluators.utils import (
24
25
  QmodType,
25
26
  element_types,
26
- get_qmod_type_name,
27
27
  is_classical_integer,
28
28
  is_classical_type,
29
29
  qnum_is_qbit,
@@ -34,11 +34,13 @@ def comparison_allowed(
34
34
  left: QmodType, right: QmodType, inequality: bool = False
35
35
  ) -> bool:
36
36
  if isinstance(left, Bool):
37
- return isinstance(right, (Bool, QuantumBit)) or (
37
+ return isinstance(right, (Bool, Integer, QuantumBit)) or (
38
38
  isinstance(right, QuantumNumeric) and qnum_is_qbit(right)
39
39
  )
40
40
  if isinstance(left, Real) or is_classical_integer(left):
41
- return isinstance(right, (Real, QuantumScalar)) or is_classical_integer(right)
41
+ return isinstance(right, (Real, Bool, QuantumScalar)) or is_classical_integer(
42
+ right
43
+ )
42
44
  if isinstance(left, (ClassicalArray, ClassicalTuple)):
43
45
  if inequality:
44
46
  return False
@@ -62,6 +64,8 @@ def comparison_allowed(
62
64
  return isinstance(right, (Bool, Real, QuantumScalar)) or is_classical_integer(
63
65
  right
64
66
  )
67
+ if isinstance(left, QuantumBitvector):
68
+ return False
65
69
  raise ClassiqInternalExpansionError
66
70
 
67
71
 
@@ -76,8 +80,8 @@ def eval_compare(expr_val: QmodAnnotatedExpression, node: ast.Compare) -> None:
76
80
  right_type = expr_val.get_type(right)
77
81
  if not comparison_allowed(left_type, right_type, not isinstance(op, ast.Eq)):
78
82
  raise ClassiqExpansionError(
79
- f"Cannot compare {get_qmod_type_name(left_type)} {ast.unparse(left)!r} "
80
- f"and {get_qmod_type_name(right_type)} {ast.unparse(right)!r}"
83
+ f"Cannot compare {left_type.qmod_type_name} {ast.unparse(left)!r} "
84
+ f"and {right_type.qmod_type_name} {ast.unparse(right)!r}"
81
85
  )
82
86
  qmod_type: QmodType
83
87
  if not is_classical_type(left_type) or not is_classical_type(right_type):
@@ -7,12 +7,22 @@ import sympy
7
7
  from classiq.interface.exceptions import (
8
8
  ClassiqExpansionError,
9
9
  )
10
- from classiq.interface.generator.functions.classical_type import Bool, Integer, Real
10
+ from classiq.interface.generator.functions.classical_type import (
11
+ Bool,
12
+ ClassicalType,
13
+ Integer,
14
+ Real,
15
+ )
11
16
  from classiq.interface.generator.functions.type_name import Enum
12
17
 
13
18
  from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
14
19
  from classiq.evaluators.qmod_node_evaluators.utils import SYMPY_SYMBOLS, QmodType
15
20
 
21
+ QMOD_LITERALS: dict[str, tuple[ClassicalType, Any]] = {
22
+ "false": (Bool(), False),
23
+ "true": (Bool(), True),
24
+ }
25
+
16
26
 
17
27
  def eval_enum_member(
18
28
  expr_val: QmodAnnotatedExpression, node: ast.Attribute, enum: type[IntEnum]
@@ -44,6 +54,15 @@ def eval_constant(expr_val: QmodAnnotatedExpression, node: ast.Constant) -> None
44
54
  expr_val.set_type(node, constant_type)
45
55
 
46
56
 
57
+ def try_eval_qmod_literal(expr_val: QmodAnnotatedExpression, node: ast.Name) -> bool:
58
+ if node.id not in QMOD_LITERALS:
59
+ return False
60
+ lit_type, lit_val = QMOD_LITERALS[node.id]
61
+ expr_val.set_type(node, lit_type)
62
+ expr_val.set_value(node, lit_val)
63
+ return True
64
+
65
+
47
66
  def try_eval_sympy_constant(expr_val: QmodAnnotatedExpression, node: ast.Name) -> bool:
48
67
  sympy_val = SYMPY_SYMBOLS.get(node.id)
49
68
  if not isinstance(sympy_val, sympy.Expr) or not sympy_val.is_constant():