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
@@ -1,6 +1,4 @@
1
- from typing import Optional
2
-
3
- from classiq.interface.model.handle_binding import HandleBinding, NestedHandleBinding
1
+ from classiq.interface.model.handle_binding import HandleBinding
4
2
 
5
3
  from classiq.evaluators.qmod_annotated_expression import (
6
4
  QmodAnnotatedExpression,
@@ -8,37 +6,61 @@ from classiq.evaluators.qmod_annotated_expression import (
8
6
  )
9
7
 
10
8
 
11
- def rename_handles_in_expression(
9
+ def replace_expression_vars(
12
10
  expr_val: QmodAnnotatedExpression,
13
11
  renaming: dict[HandleBinding, HandleBinding],
14
- ) -> str:
12
+ ) -> QmodAnnotatedExpression:
13
+ expr_val = expr_val.clone()
15
14
  if len(renaming) == 0:
16
- return expr_val.to_qmod_expr()
17
- all_vars = expr_val.get_classical_vars() | expr_val.get_quantum_vars()
15
+ expr_val.lock()
16
+ return expr_val
17
+ all_vars = dict(expr_val.get_classical_vars()) | dict(expr_val.get_quantum_vars())
18
18
  for node_id, var in all_vars.items():
19
- renamed_var = _rename_var(renaming, var)
20
- if renamed_var is not None:
21
- expr_val.set_var(node_id, renamed_var)
22
- return expr_val.to_qmod_expr()
19
+ renamed_var = var
20
+ for source, target in renaming.items():
21
+ if renamed_var.name == source.name:
22
+ renamed_var = renamed_var.replace_prefix(source, target)
23
+ if renamed_var is var:
24
+ continue
25
+ node_type = expr_val.get_type(node_id)
26
+ expr_val.clear_node_data(node_id)
27
+ expr_val.set_type(node_id, node_type)
28
+ expr_val.set_var(node_id, renamed_var)
29
+ expr_val.lock()
30
+ return expr_val
23
31
 
24
32
 
25
- def _rename_var(
26
- renaming: dict[HandleBinding, HandleBinding], var: HandleBinding
27
- ) -> Optional[HandleBinding]:
28
- if (renamed_var := renaming.get(var)) is not None:
29
- return renamed_var
30
- if not isinstance(var, NestedHandleBinding):
31
- return None
32
- renamed_inner = _rename_var(renaming, var.base_handle)
33
- if renamed_inner is None:
34
- return None
35
- return var.model_copy(update=dict(base_handle=renamed_inner))
33
+ def replace_expression_type_attrs(
34
+ expr_val: QmodAnnotatedExpression,
35
+ renaming: dict[tuple[HandleBinding, str], HandleBinding],
36
+ ) -> QmodAnnotatedExpression:
37
+ expr_val = expr_val.clone()
38
+ if len(renaming) == 0:
39
+ expr_val.lock()
40
+ return expr_val
41
+ type_attrs = dict(expr_val.get_quantum_type_attributes())
42
+ for node_id, ta in type_attrs.items():
43
+ var = expr_val.get_var(ta.value)
44
+ renamed_var = var
45
+ renamed_attr = ta.attr
46
+ for (source, attr), target in renaming.items():
47
+ if renamed_attr == attr and renamed_var.name == source.name:
48
+ renamed_var = renamed_var.replace_prefix(source, target)
49
+ if renamed_var is var:
50
+ continue
51
+ node_type = expr_val.get_type(node_id)
52
+ expr_val.clear_node_data(node_id)
53
+ expr_val.set_type(node_id, node_type)
54
+ expr_val.set_var(node_id, renamed_var)
55
+ expr_val.lock()
56
+ return expr_val
36
57
 
37
58
 
38
- def rename_nodes_in_expression(
59
+ def replace_expression_nodes(
39
60
  expr_val: QmodAnnotatedExpression,
40
- renaming: dict[QmodExprNodeId, HandleBinding],
61
+ renaming: dict[QmodExprNodeId, str],
41
62
  ) -> str:
63
+ expr_val = expr_val.clone()
42
64
  for node_id, renamed_var in renaming.items():
43
- expr_val.set_var(node_id, renamed_var)
44
- return expr_val.to_qmod_expr()
65
+ expr_val.set_var(node_id, HandleBinding(name=f"{renamed_var}"))
66
+ return str(expr_val)
@@ -1,22 +1,20 @@
1
1
  import ast
2
- from typing import Any, Optional, cast
2
+ from typing import Any, Callable, TypeVar, 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
- from classiq.interface.model.handle_binding import HandleBinding
10
- from classiq.interface.model.quantum_type import (
11
- QuantumBit,
12
- QuantumNumeric,
13
- QuantumType,
7
+ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_instance import (
8
+ QmodStructInstance,
14
9
  )
15
10
 
16
11
  from classiq.evaluators.qmod_annotated_expression import (
17
12
  QmodAnnotatedExpression,
18
13
  QmodExprNodeId,
19
14
  )
15
+ from classiq.evaluators.qmod_expression_visitors.out_of_place_node_transformer import (
16
+ OutOfPlaceNodeTransformer,
17
+ )
20
18
  from classiq.evaluators.qmod_expression_visitors.sympy_wrappers import (
21
19
  BitwiseAnd,
22
20
  BitwiseNot,
@@ -25,7 +23,7 @@ from classiq.evaluators.qmod_expression_visitors.sympy_wrappers import (
25
23
  LShift,
26
24
  RShift,
27
25
  )
28
- from classiq.evaluators.qmod_node_evaluators.utils import QmodType
26
+ from classiq.model_expansions.atomic_expression_functions_defs import do_div
29
27
 
30
28
  _SYMPY_WRAPPERS = {
31
29
  wrapper.__name__: wrapper
@@ -37,15 +35,19 @@ _SYMPY_WRAPPERS = {
37
35
  LShift,
38
36
  RShift,
39
37
  ]
38
+ } | {
39
+ do_div.__name__: do_div,
40
40
  }
41
41
 
42
+ _PY_NODE = TypeVar("_PY_NODE", bound=ast.AST)
43
+
42
44
 
43
- class _VarMaskTransformer(ast.NodeTransformer):
45
+ class _VarMaskTransformer(OutOfPlaceNodeTransformer):
44
46
  def __init__(self, expr_val: QmodAnnotatedExpression) -> None:
45
47
  self._expr_val = expr_val
46
48
  self._mask_id = 0
47
49
  self.masks: dict[str, QmodExprNodeId] = {}
48
- self._assigned_masks: dict[HandleBinding, str] = {}
50
+ self._assigned_masks: dict[Any, str] = {}
49
51
 
50
52
  def _create_mask(self) -> str:
51
53
  mask = f"x{self._mask_id}"
@@ -54,29 +56,46 @@ class _VarMaskTransformer(ast.NodeTransformer):
54
56
 
55
57
  def visit(self, node: ast.AST) -> ast.AST:
56
58
  if self._expr_val.has_value(node):
57
- return ast.parse(str(self._expr_val.get_value(node)))
59
+ val = self._expr_val.get_value(node)
60
+ if not isinstance(val, (list, QmodStructInstance)):
61
+ return ast.Constant(value=val)
62
+ mask = self._create_mask()
63
+ self.masks[mask] = id(node)
64
+ return ast.Name(id=mask)
58
65
  if self._expr_val.has_var(node):
59
66
  var = self._expr_val.get_var(node)
60
- if var in self._assigned_masks:
61
- mask = self._assigned_masks[var]
62
- else:
63
- mask = self._create_mask()
64
- self._assigned_masks[var] = mask
65
- self.masks[mask] = id(node)
66
- return ast.Name(id=mask)
67
- if self._expr_val.has_quantum_subscript(node):
67
+ var_str = str(var.collapse())
68
+ if var_str in self._assigned_masks:
69
+ return ast.Name(id=self._assigned_masks[var_str])
68
70
  mask = self._create_mask()
71
+ self._assigned_masks[var_str] = mask
69
72
  self.masks[mask] = id(node)
70
73
  return ast.Name(id=mask)
71
- return self.generic_visit(node)
72
-
73
- def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
74
- mask = self._create_mask()
74
+ return super().visit(node)
75
+
76
+ def _reduce_node(
77
+ self, node: _PY_NODE, inner_node: ast.AST, key: Callable[[_PY_NODE], str]
78
+ ) -> ast.AST:
79
+ new_value = self.visit(inner_node)
80
+ if isinstance(new_value, ast.Name):
81
+ mask_key = (new_value.id, key(node))
82
+ if mask_key in self._assigned_masks:
83
+ return ast.Name(self._assigned_masks[mask_key])
84
+ mask = self._create_mask()
85
+ self._assigned_masks[mask_key] = mask
86
+ else:
87
+ mask = self._create_mask()
75
88
  self.masks[mask] = id(node)
76
89
  return ast.Name(id=mask)
77
90
 
91
+ def visit_Attribute(self, node: ast.Attribute) -> ast.AST:
92
+ return self._reduce_node(node, node.value, lambda n: n.attr)
78
93
 
79
- class _InverseVarMaskTransformer(ast.NodeTransformer):
94
+ def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
95
+ return self._reduce_node(node, node.value, lambda n: ast.unparse(node.slice))
96
+
97
+
98
+ class _InverseVarMaskTransformer(OutOfPlaceNodeTransformer):
80
99
  def __init__(
81
100
  self, expr_val: QmodAnnotatedExpression, masks: dict[str, QmodExprNodeId]
82
101
  ) -> None:
@@ -93,7 +112,7 @@ class _InverseVarMaskTransformer(ast.NodeTransformer):
93
112
  return node
94
113
 
95
114
 
96
- class _SympyCompatibilityTransformer(ast.NodeTransformer):
115
+ class _SympyCompatibilityTransformer(OutOfPlaceNodeTransformer):
97
116
  def visit_BoolOp(self, node: ast.BoolOp) -> ast.Call:
98
117
  if len(node.values) != 2:
99
118
  raise ClassiqInternalExpansionError
@@ -151,6 +170,12 @@ class _SympyCompatibilityTransformer(ast.NodeTransformer):
151
170
  sympy_func = BitwiseXor.__name__
152
171
  elif isinstance(node.op, ast.BitAnd):
153
172
  sympy_func = BitwiseAnd.__name__
173
+ elif isinstance(node.op, ast.Div):
174
+ return ast.Call(
175
+ func=ast.Name(id=do_div.__name__),
176
+ args=[node.left, node.right],
177
+ keywords=[],
178
+ )
154
179
  else:
155
180
  return node
156
181
  return ast.Call(
@@ -158,7 +183,7 @@ class _SympyCompatibilityTransformer(ast.NodeTransformer):
158
183
  )
159
184
 
160
185
 
161
- class _InverseSympyCompatibilityTransformer(ast.NodeTransformer):
186
+ class _InverseSympyCompatibilityTransformer(OutOfPlaceNodeTransformer):
162
187
  def visit_Call(self, node: ast.Call) -> Any:
163
188
  node = cast(ast.Call, self.generic_visit(node))
164
189
  if not isinstance(node.func, ast.Name):
@@ -183,12 +208,12 @@ class _InverseSympyCompatibilityTransformer(ast.NodeTransformer):
183
208
  and (len(node.args) != 2 or len(node.keywords) > 0)
184
209
  ) or (
185
210
  func in {BitwiseNot.__name__}
186
- and (len(node.args) != 2 or len(node.keywords) > 0)
211
+ and (len(node.args) != 1 or len(node.keywords) > 0)
187
212
  ):
188
213
  raise ClassiqInternalExpansionError
189
214
 
190
215
  if func == BitwiseNot.__name__:
191
- return ast.UnaryOp(op=ast.Invert, operand=node.args[0])
216
+ return ast.UnaryOp(op=ast.Invert(), operand=node.args[0])
192
217
 
193
218
  if func == "Eq":
194
219
  return ast.Compare(
@@ -226,6 +251,15 @@ class _InverseSympyCompatibilityTransformer(ast.NodeTransformer):
226
251
  if func == BitwiseAnd.__name__:
227
252
  return ast.BinOp(left=node.args[0], op=ast.BitAnd(), right=node.args[1])
228
253
 
254
+ if func == "Abs":
255
+ node.func.id = "abs"
256
+ if func == "Max":
257
+ node.func.id = "max"
258
+ elif func == "Min":
259
+ node.func.id = "min"
260
+ if func == "Mod":
261
+ return ast.BinOp(left=node.args[0], op=ast.Mod(), right=node.args[1])
262
+
229
263
  return node
230
264
 
231
265
  def visit_UnaryOp(self, node: ast.UnaryOp) -> Any:
@@ -247,46 +281,12 @@ class _InverseSympyCompatibilityTransformer(ast.NodeTransformer):
247
281
  return node
248
282
 
249
283
 
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]]:
284
+ def simplify_qmod_expression(expr_val: QmodAnnotatedExpression) -> str:
284
285
  if expr_val.has_value(expr_val.root):
285
286
  raise ClassiqInternalExpansionError(
286
287
  "This expression is a constant value. No need for simplification"
287
288
  )
288
289
  var_mask_transformer = _VarMaskTransformer(expr_val)
289
- masks = var_mask_transformer.masks
290
290
  mask_expr = var_mask_transformer.visit(expr_val.root)
291
291
  sympy_expr = _SympyCompatibilityTransformer().visit(mask_expr)
292
292
  simplified_expr = str(
@@ -295,14 +295,7 @@ def simplify_qmod_expression(
295
295
  restored_expr = _InverseSympyCompatibilityTransformer().visit(
296
296
  ast.parse(simplified_expr, mode="eval")
297
297
  )
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
298
  restored_expr = _InverseVarMaskTransformer(
306
299
  expr_val, var_mask_transformer.masks
307
300
  ).visit(restored_expr)
308
- return ast.unparse(restored_expr), expr_type
301
+ 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(
@@ -42,7 +42,7 @@ def _eval_type_attribute(
42
42
  return
43
43
  if isinstance(subject_type, (ClassicalArray, QuantumBitvector)) and attr == "len":
44
44
  expr_val.set_type(node, Integer())
45
- if subject_type.has_length:
45
+ if subject_type.has_constant_length:
46
46
  expr_val.set_value(node, subject_type.length_value)
47
47
  elif isinstance(subject_type, QuantumType):
48
48
  expr_val.set_quantum_type_attr(node, subject, attr)
@@ -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)