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
@@ -28,10 +28,14 @@ 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
+ )
33
+ from classiq.evaluators.qmod_type_inference.classical_type_inference import (
34
+ infer_classical_type,
32
35
  )
33
36
 
34
- MIN_MAX_FUNCTION_NAMES = {"min", "Min", "max", "Max"}
37
+ # These sympy functions are not declared as int funcs for some reason...
38
+ INTEGER_FUNCTION_OVERRIDE = {"floor", "ceiling"}
35
39
 
36
40
 
37
41
  def _check_classical_array_arg_type(
@@ -91,8 +95,8 @@ def _check_classical_arg(
91
95
  ):
92
96
  raise ClassiqExpansionError(
93
97
  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)}"
98
+ f"{param_type.qmod_type_name} argument, but got a "
99
+ f"{arg_type.qmod_type_name}"
96
100
  )
97
101
 
98
102
 
@@ -168,7 +172,9 @@ def eval_function(
168
172
  cast(str, kwarg_name): expr_val.get_value(kwarg_value)
169
173
  for kwarg_name, kwarg_value in kwargs.items()
170
174
  }
171
- expr_val.set_value(node, func(*arg_values, **kwarg_values))
175
+ ret_val = func(*arg_values, **kwarg_values)
176
+ expr_val.set_type(node, infer_classical_type(ret_val))
177
+ expr_val.set_value(node, ret_val)
172
178
 
173
179
 
174
180
  def try_eval_sympy_function(
@@ -191,7 +197,9 @@ def try_eval_sympy_function(
191
197
  ret_type = Bool()
192
198
  if args is not None:
193
199
  ret_val = bool(sympy_ret_val)
194
- elif hasattr(sympy_func, "is_Integer") and sympy_func.is_Integer:
200
+ elif (
201
+ hasattr(sympy_func, "is_Integer") and sympy_func.is_Integer
202
+ ) or func_name in INTEGER_FUNCTION_OVERRIDE:
195
203
  ret_type = Integer()
196
204
  if args is not None:
197
205
  ret_val = int(sympy_ret_val)
@@ -241,15 +249,6 @@ def try_eval_builtin_function(
241
249
  )
242
250
  args_have_values = all(expr_val.has_value(arg) for arg in node.args)
243
251
 
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
252
  if func_name == "mod_inverse":
254
253
  _validate_no_kwargs(node)
255
254
  ret_type: QmodType
@@ -267,10 +266,10 @@ def try_eval_builtin_function(
267
266
  *[expr_val.get_value(arg) for arg in node.args]
268
267
  )
269
268
  ret_val: Any
270
- if args_are_int:
271
- ret_val = int(sympy_val)
272
- elif isinstance(sympy_val, sympy.Expr) and sympy_val.is_imaginary:
269
+ if isinstance(sympy_val, sympy.Expr) and sympy_val.is_imaginary:
273
270
  ret_val = complex(sympy_val)
271
+ elif args_are_int:
272
+ ret_val = int(sympy_val)
274
273
  else:
275
274
  ret_val = float(sympy_val)
276
275
  expr_val.set_value(node, ret_val)
@@ -292,9 +291,27 @@ def try_eval_builtin_function(
292
291
  if func_name == "sqrt":
293
292
  _validate_no_kwargs(node)
294
293
  expr_val.set_type(node, Real())
294
+ if args_have_values:
295
+ sympy_val = sympy.sqrt(*[expr_val.get_value(arg) for arg in node.args])
296
+ if isinstance(sympy_val, sympy.Expr) and sympy_val.is_imaginary:
297
+ ret_val = complex(sympy_val)
298
+ elif float(sympy_val) == int(sympy_val):
299
+ ret_val = int(sympy_val)
300
+ else:
301
+ ret_val = float(sympy_val)
302
+ expr_val.set_value(node, ret_val)
303
+ return True
304
+
305
+ if func_name == "abs":
306
+ _validate_no_kwargs(node)
307
+ if len(node.args) > 0 and is_classical_integer(expr_val.get_type(node.args[0])):
308
+ ret_type = Integer()
309
+ else:
310
+ ret_type = Real()
311
+ expr_val.set_type(node, ret_type)
295
312
  if args_have_values:
296
313
  expr_val.set_value(
297
- node, sympy.sqrt(*[expr_val.get_value(arg) for arg in node.args])
314
+ node, abs(*[expr_val.get_value(arg) for arg in node.args])
298
315
  )
299
316
  return True
300
317
 
@@ -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):
@@ -96,12 +100,20 @@ def eval_compare(expr_val: QmodAnnotatedExpression, node: ast.Compare) -> None:
96
100
  elif isinstance(op, ast.NotEq):
97
101
  expr_val.set_value(node, left_value != right_value)
98
102
  elif isinstance(op, ast.Lt):
103
+ if isinstance(left_value, complex) or isinstance(right_value, complex):
104
+ raise ClassiqExpansionError("Inequality with a complex number")
99
105
  expr_val.set_value(node, left_value < right_value)
100
106
  elif isinstance(op, ast.Gt):
107
+ if isinstance(left_value, complex) or isinstance(right_value, complex):
108
+ raise ClassiqExpansionError("Inequality with a complex number")
101
109
  expr_val.set_value(node, left_value > right_value)
102
110
  elif isinstance(op, ast.LtE):
111
+ if isinstance(left_value, complex) or isinstance(right_value, complex):
112
+ raise ClassiqExpansionError("Inequality with a complex number")
103
113
  expr_val.set_value(node, left_value <= right_value)
104
114
  elif isinstance(op, ast.GtE):
115
+ if isinstance(left_value, complex) or isinstance(right_value, complex):
116
+ raise ClassiqExpansionError("Inequality with a complex number")
105
117
  expr_val.set_value(node, left_value >= right_value)
106
118
  else:
107
119
  raise ClassiqExpansionError(f"Unsupported comparison {type(op).__name__!r}")
@@ -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]
@@ -23,7 +33,10 @@ def eval_enum_member(
23
33
  }
24
34
  attr = node.attr
25
35
  if attr not in enum_members:
26
- raise ClassiqExpansionError(f"Enum {enum_name} has no member named {attr!r}")
36
+ raise ClassiqExpansionError(
37
+ f"Enum {enum_name} has no member {attr!r}. Available members: "
38
+ f"{', '.join(enum_members.keys())}"
39
+ )
27
40
 
28
41
  expr_val.set_type(node, Enum(name=enum_name))
29
42
  expr_val.set_value(node, enum_members[attr])
@@ -44,6 +57,15 @@ def eval_constant(expr_val: QmodAnnotatedExpression, node: ast.Constant) -> None
44
57
  expr_val.set_type(node, constant_type)
45
58
 
46
59
 
60
+ def try_eval_qmod_literal(expr_val: QmodAnnotatedExpression, node: ast.Name) -> bool:
61
+ if node.id not in QMOD_LITERALS:
62
+ return False
63
+ lit_type, lit_val = QMOD_LITERALS[node.id]
64
+ expr_val.set_type(node, lit_type)
65
+ expr_val.set_value(node, lit_val)
66
+ return True
67
+
68
+
47
69
  def try_eval_sympy_constant(expr_val: QmodAnnotatedExpression, node: ast.Name) -> bool:
48
70
  sympy_val = SYMPY_SYMBOLS.get(node.id)
49
71
  if not isinstance(sympy_val, sympy.Expr) or not sympy_val.is_constant():
@@ -0,0 +1,97 @@
1
+ import ast
2
+
3
+ from classiq.interface.exceptions import (
4
+ ClassiqExpansionError,
5
+ ClassiqInternalExpansionError,
6
+ )
7
+ from classiq.interface.generator.functions.classical_type import Integer, Real
8
+ from classiq.interface.model.quantum_type import QuantumNumeric
9
+
10
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
11
+ from classiq.evaluators.qmod_node_evaluators.numeric_attrs_utils import (
12
+ get_numeric_attrs,
13
+ )
14
+ from classiq.evaluators.qmod_node_evaluators.utils import (
15
+ QmodType,
16
+ is_classical_integer,
17
+ is_classical_type,
18
+ is_numeric_type,
19
+ )
20
+ from classiq.model_expansions.arithmetic import NumericAttributes
21
+ from classiq.model_expansions.arithmetic_compute_result_attrs import (
22
+ compute_result_attrs_max,
23
+ compute_result_attrs_min,
24
+ )
25
+
26
+
27
+ def _infer_min_max_op_type(
28
+ expr_val: QmodAnnotatedExpression,
29
+ node: ast.Call,
30
+ func_name: str,
31
+ args_types: list[QmodType],
32
+ treat_qnum_as_float: bool,
33
+ machine_precision: int,
34
+ ) -> QmodType:
35
+ if all(is_classical_type(arg_type) for arg_type in args_types):
36
+ if all(is_classical_integer(arg_type) for arg_type in args_types):
37
+ return Integer()
38
+ return Real()
39
+
40
+ args_attrs: list[NumericAttributes] = []
41
+ for arg, arg_type in zip(node.args, args_types):
42
+ attrs = get_numeric_attrs(
43
+ expr_val, arg, arg_type, machine_precision, treat_qnum_as_float
44
+ )
45
+ if attrs is None:
46
+ return QuantumNumeric()
47
+ args_attrs.append(attrs)
48
+
49
+ if func_name == "min":
50
+ result_attrs = compute_result_attrs_min(args_attrs, machine_precision)
51
+ elif func_name == "max":
52
+ result_attrs = compute_result_attrs_max(args_attrs, machine_precision)
53
+ else:
54
+ raise ClassiqInternalExpansionError
55
+
56
+ return result_attrs.to_quantum_numeric()
57
+
58
+
59
+ def eval_min_max_op(
60
+ expr_val: QmodAnnotatedExpression,
61
+ node: ast.Call,
62
+ func_name: str,
63
+ treat_qnum_as_float: bool,
64
+ machine_precision: int,
65
+ ) -> None:
66
+ if len(node.args) < 1:
67
+ raise ClassiqExpansionError(f"{func_name!r} expects at least one argument")
68
+
69
+ args_types = [expr_val.get_type(arg) for arg in node.args]
70
+ if not all(is_numeric_type(arg_type) for arg_type in args_types):
71
+ raise ClassiqExpansionError(
72
+ f"All arguments of {func_name!r} must be scalar values"
73
+ )
74
+
75
+ inferred_type = _infer_min_max_op_type(
76
+ expr_val,
77
+ node,
78
+ func_name,
79
+ args_types,
80
+ treat_qnum_as_float,
81
+ machine_precision,
82
+ )
83
+ expr_val.set_type(node, inferred_type)
84
+
85
+ if all(expr_val.has_value(arg) for arg in node.args):
86
+ values = [expr_val.get_value(arg) for arg in node.args]
87
+ if not all(isinstance(value, (int, float)) for value in values):
88
+ raise ClassiqExpansionError(f"Invalid argument for function {func_name!r}")
89
+
90
+ if func_name == "min":
91
+ result_value = min(*values)
92
+ elif func_name == "max":
93
+ result_value = max(*values)
94
+ else:
95
+ raise ClassiqInternalExpansionError
96
+
97
+ expr_val.set_value(node, result_value)
@@ -1,6 +1,8 @@
1
1
  import ast
2
2
  from typing import Any
3
3
 
4
+ import sympy
5
+
4
6
  from classiq.interface.exceptions import (
5
7
  ClassiqInternalExpansionError,
6
8
  )
@@ -8,42 +10,25 @@ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_insta
8
10
  QmodStructInstance,
9
11
  )
10
12
  from classiq.interface.generator.functions.classical_type import (
11
- Bool,
12
- ClassicalTuple,
13
13
  ClassicalType,
14
- Integer,
15
- Real,
16
14
  )
17
- from classiq.interface.generator.functions.type_name import Struct
18
15
  from classiq.interface.model.handle_binding import HandleBinding
19
16
  from classiq.interface.model.quantum_type import QuantumType
20
17
 
21
18
  from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
22
-
23
-
24
- def _infer_classical_type(value: Any) -> ClassicalType:
25
- if isinstance(value, bool):
26
- return Bool()
27
- if isinstance(value, int):
28
- return Integer()
29
- if isinstance(value, (float, complex)):
30
- return Real()
31
- if isinstance(value, list):
32
- return ClassicalTuple(
33
- element_types=[_infer_classical_type(item) for item in value]
34
- )
35
- if isinstance(value, QmodStructInstance):
36
- classical_type = Struct(name=value.struct_declaration.name)
37
- classical_type.set_classical_struct_decl(value.struct_declaration)
38
- return classical_type
39
- raise ClassiqInternalExpansionError
19
+ from classiq.evaluators.qmod_type_inference.classical_type_inference import (
20
+ infer_classical_type,
21
+ )
40
22
 
41
23
 
42
24
  def eval_name(expr_val: QmodAnnotatedExpression, node: ast.Name, value: Any) -> None:
43
- if isinstance(value, (bool, int, float, complex, list, QmodStructInstance)):
44
- expr_val.set_type(node, _infer_classical_type(value))
25
+ # FIXME: Remove sympy compatibility (CLS-3214)
26
+ if isinstance(
27
+ value, (bool, int, float, complex, list, QmodStructInstance, sympy.Basic)
28
+ ):
29
+ expr_val.set_type(node, infer_classical_type(value))
45
30
  expr_val.set_value(node, value)
46
- elif isinstance(value, (ClassicalType, QuantumType)):
31
+ elif isinstance(value, (ClassicalType, QuantumType)): # type:ignore[unreachable]
47
32
  expr_val.set_type(node, value)
48
33
  expr_val.set_var(node, HandleBinding(name=node.id))
49
34
  else:
@@ -0,0 +1,56 @@
1
+ import ast
2
+ from typing import TYPE_CHECKING, Optional
3
+
4
+ from classiq.interface.exceptions import ClassiqExpansionError
5
+ from classiq.interface.generator.functions.classical_type import Bool
6
+ from classiq.interface.model.quantum_type import QuantumScalar
7
+
8
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
9
+ from classiq.evaluators.qmod_node_evaluators.utils import QmodType, is_classical_type
10
+ from classiq.model_expansions.arithmetic import NumericAttributes
11
+
12
+
13
+ def get_numeric_attrs(
14
+ expr_val: QmodAnnotatedExpression,
15
+ node: ast.AST,
16
+ qmod_type: QmodType,
17
+ machine_precision: int,
18
+ treat_qnum_as_float: bool,
19
+ ) -> Optional[NumericAttributes]:
20
+ if isinstance(qmod_type, Bool):
21
+ return NumericAttributes.from_bounds(0, 1, 0, machine_precision)
22
+ if is_classical_type(qmod_type):
23
+ value = get_classical_value_for_arithmetic(
24
+ expr_val, node, qmod_type, treat_qnum_as_float
25
+ )
26
+ if value is None:
27
+ return None
28
+ return NumericAttributes.from_constant(value, machine_precision)
29
+
30
+ if TYPE_CHECKING:
31
+ assert isinstance(qmod_type, QuantumScalar)
32
+ if not qmod_type.is_constant:
33
+ return None
34
+ return NumericAttributes.from_quantum_scalar(qmod_type, machine_precision)
35
+
36
+
37
+ def get_classical_value_for_arithmetic(
38
+ expr_val: QmodAnnotatedExpression,
39
+ node: ast.AST,
40
+ qmod_type: QmodType,
41
+ treat_qnum_as_float: bool,
42
+ ) -> Optional[float]:
43
+ if not is_classical_type(qmod_type):
44
+ return None
45
+ if not expr_val.has_value(node):
46
+ return None
47
+
48
+ value = expr_val.get_value(node)
49
+ if not isinstance(value, (int, float)):
50
+ if treat_qnum_as_float and isinstance(value, complex):
51
+ return None
52
+ raise ClassiqExpansionError(
53
+ "Arithmetic of quantum variables and non-real values is not supported"
54
+ )
55
+
56
+ return float(value)
@@ -0,0 +1,40 @@
1
+ import ast
2
+ from collections.abc import Sequence
3
+
4
+ import sympy
5
+
6
+ from classiq.interface.exceptions import ClassiqExpansionError
7
+ from classiq.interface.generator.functions.classical_type import Bool, Integer, Real
8
+
9
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
10
+
11
+
12
+ def eval_piecewise(
13
+ expr_val: QmodAnnotatedExpression,
14
+ node: ast.Call,
15
+ args: Sequence[tuple[ast.AST, ast.AST]],
16
+ ) -> None:
17
+ cond_types = [expr_val.get_type(cond) for _, cond in args]
18
+ if not all(isinstance(cond_type, Bool) for cond_type in cond_types):
19
+ raise ClassiqExpansionError(
20
+ "Piecewise conditions must be classical Boolean values"
21
+ )
22
+
23
+ value_types = [expr_val.get_type(value) for value, _ in args]
24
+ if not all(isinstance(value_type, (Integer, Real)) for value_type in value_types):
25
+ raise ClassiqExpansionError("Piecewise values must be classical numeric values")
26
+
27
+ if all(isinstance(value_type, Integer) for value_type in value_types):
28
+ expr_val.set_type(node, Integer())
29
+ else:
30
+ expr_val.set_type(node, Real())
31
+
32
+ if not all(
33
+ expr_val.has_value(value) and expr_val.has_value(cond) for value, cond in args
34
+ ):
35
+ return
36
+
37
+ values = [
38
+ (expr_val.get_value(value), expr_val.get_value(cond)) for value, cond in args
39
+ ]
40
+ expr_val.set_value(node, sympy.Piecewise(*values))
@@ -15,13 +15,12 @@ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
15
15
  from classiq.evaluators.qmod_node_evaluators.compare_evaluation import (
16
16
  comparison_allowed,
17
17
  )
18
- from classiq.evaluators.qmod_node_evaluators.utils import get_qmod_type_name
19
18
 
20
19
 
21
20
  def eval_struct_instantiation(
22
21
  expr_val: QmodAnnotatedExpression, node: ast.Call, decl: StructDeclaration
23
22
  ) -> None:
24
- if len(node.args) > 0 or any(kwarg.arg is None for kwarg in node.keywords):
23
+ if len(node.args) != 1 or any(kwarg.arg is None for kwarg in node.keywords):
25
24
  raise ClassiqExpansionError(
26
25
  "Classical structs must be instantiated using keyword arguments"
27
26
  )
@@ -42,8 +41,8 @@ def eval_struct_instantiation(
42
41
  expected_type = decl.variables[field_name]
43
42
  if not comparison_allowed(assignment_type, expected_type):
44
43
  raise ClassiqExpansionError(
45
- f"Cannot assign value of type {get_qmod_type_name(assignment_type)} "
46
- f"to field {field_name!r} of type {get_qmod_type_name(expected_type)}"
44
+ f"Cannot assign value of type {assignment_type.qmod_type_name} "
45
+ f"to field {field_name!r} of type {expected_type.qmod_type_name}"
47
46
  )
48
47
 
49
48
  classical_type = Struct(name=decl.name)
@@ -4,10 +4,7 @@ from typing import Optional, cast
4
4
  from classiq.interface.exceptions import (
5
5
  ClassiqExpansionError,
6
6
  ClassiqInternalExpansionError,
7
- )
8
- from classiq.interface.generator.arith.arithmetic import (
9
- aggregate_numeric_types,
10
- compute_arithmetic_result_type,
7
+ ClassiqValueError,
11
8
  )
12
9
  from classiq.interface.generator.expressions.expression import Expression
13
10
  from classiq.interface.generator.functions.classical_type import (
@@ -16,6 +13,7 @@ from classiq.interface.generator.functions.classical_type import (
16
13
  Integer,
17
14
  Real,
18
15
  )
16
+ from classiq.interface.helpers.text_utils import s
19
17
  from classiq.interface.model.handle_binding import (
20
18
  SlicedHandleBinding,
21
19
  SubscriptHandleBinding,
@@ -23,16 +21,19 @@ from classiq.interface.model.handle_binding import (
23
21
  from classiq.interface.model.quantum_type import (
24
22
  QuantumBitvector,
25
23
  QuantumNumeric,
24
+ QuantumScalar,
26
25
  QuantumType,
27
- register_info_to_quantum_type,
28
26
  )
29
27
 
30
28
  from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
31
29
  from classiq.evaluators.qmod_node_evaluators.utils import (
32
30
  QmodType,
31
+ array_len,
33
32
  element_types,
34
- get_qmod_type_name,
35
- qnum_is_qint,
33
+ get_numeric_properties,
34
+ )
35
+ from classiq.model_expansions.arithmetic_compute_result_attrs import (
36
+ compute_result_attrs_quantum_subscript,
36
37
  )
37
38
 
38
39
 
@@ -49,7 +50,7 @@ def _eval_slice(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> None:
49
50
  for index_type in (start_type, stop_type):
50
51
  if not isinstance(index_type, Integer):
51
52
  raise ClassiqExpansionError(
52
- f"Slice indices must be integers, not {get_qmod_type_name(index_type)!r}"
53
+ f"Slice indices must be integers, not {index_type.raw_qmod_type_name}"
53
54
  )
54
55
 
55
56
  start_val: Optional[int] = None
@@ -68,7 +69,7 @@ def _eval_slice(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> None:
68
69
  subject_type = expr_val.get_type(subject)
69
70
  slice_type: QmodType
70
71
  if isinstance(subject_type, ClassicalArray):
71
- if subject_type.has_length and (
72
+ if subject_type.has_constant_length and (
72
73
  (start_val is not None and start_val >= subject_type.length_value)
73
74
  or (stop_val is not None and stop_val > subject_type.length_value)
74
75
  ):
@@ -92,7 +93,7 @@ def _eval_slice(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> None:
92
93
  slice_type = subject_type.get_raw_type()
93
94
  elif isinstance(subject_type, QuantumBitvector):
94
95
  if start_val is not None and stop_val is not None:
95
- if subject_type.has_length and (
96
+ if subject_type.has_constant_length and (
96
97
  start_val >= subject_type.length_value
97
98
  or stop_val > subject_type.length_value
98
99
  ):
@@ -105,7 +106,7 @@ def _eval_slice(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> None:
105
106
  )
106
107
  else:
107
108
  raise ClassiqExpansionError(
108
- f"{get_qmod_type_name(subject_type)} is not subscriptable"
109
+ f"{subject_type.raw_qmod_type_name} is not subscriptable"
109
110
  )
110
111
  expr_val.set_type(node, slice_type)
111
112
 
@@ -134,7 +135,8 @@ def _eval_subscript(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> N
134
135
  index_type = expr_val.get_type(subscript)
135
136
  if not isinstance(index_type, Integer):
136
137
  raise ClassiqExpansionError(
137
- f"Array indices must be integers or slices, not {get_qmod_type_name(index_type)}"
138
+ f"Array indices must be integers or slices, not "
139
+ f"{index_type.raw_qmod_type_name}"
138
140
  )
139
141
 
140
142
  sub_val: Optional[int] = None
@@ -148,7 +150,7 @@ def _eval_subscript(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> N
148
150
  if isinstance(subject_type, (ClassicalArray, QuantumBitvector)):
149
151
  if (
150
152
  sub_val is not None
151
- and subject_type.has_length
153
+ and subject_type.has_constant_length
152
154
  and sub_val >= subject_type.length_value
153
155
  ):
154
156
  raise ClassiqExpansionError("Array index out of range")
@@ -165,7 +167,7 @@ def _eval_subscript(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> N
165
167
  sub_type = raw_subject_type.element_type
166
168
  else:
167
169
  raise ClassiqExpansionError(
168
- f"{get_qmod_type_name(subject_type)} is not subscriptable"
170
+ f"{subject_type.raw_qmod_type_name} is not subscriptable"
169
171
  )
170
172
  expr_val.set_type(node, sub_type)
171
173
 
@@ -192,15 +194,38 @@ def eval_subscript(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> No
192
194
  _eval_subscript(expr_val, node)
193
195
 
194
196
 
197
+ def validate_quantum_subscript_index_properties(
198
+ index_size: Optional[int],
199
+ index_sign: Optional[bool],
200
+ index_fraction_digits: Optional[int],
201
+ array_len: Optional[int],
202
+ ) -> None:
203
+ if index_sign or (index_fraction_digits is not None and index_fraction_digits > 0):
204
+ raise ClassiqValueError("Quantum index must be an unsigned integer")
205
+ if array_len == 0:
206
+ raise ClassiqValueError(
207
+ "Classical arrays indexed by a quantum variable must not be empty"
208
+ )
209
+ if array_len is None or index_size is None:
210
+ return
211
+ if 2**index_size > array_len:
212
+ adjective = "short"
213
+ elif 2**index_size < array_len:
214
+ adjective = "long"
215
+ else:
216
+ return
217
+ raise ClassiqValueError(
218
+ f"Array is too {adjective}. It has {array_len} item{s(array_len)}, but its "
219
+ f"quantum index has {index_size} bit{s(index_size)}"
220
+ )
221
+
222
+
195
223
  def eval_quantum_subscript(
196
224
  expr_val: QmodAnnotatedExpression, node: ast.Subscript, machine_precision: int
197
225
  ) -> None:
198
226
  subject = node.value
199
227
  subscript = node.slice
200
228
 
201
- index_type = cast(QuantumType, expr_val.get_type(subscript))
202
- if not qnum_is_qint(index_type):
203
- raise ClassiqExpansionError("Quantum indices must be unsigned quantum numerics")
204
229
  subject_type = expr_val.get_type(subject)
205
230
  if not isinstance(subject_type, (ClassicalArray, ClassicalTuple)) or not all(
206
231
  isinstance(element_type, (Integer, Real))
@@ -210,16 +235,18 @@ def eval_quantum_subscript(
210
235
  "Only classical numeric arrays may have quantum subscripts"
211
236
  )
212
237
 
238
+ index_type = cast(QuantumType, expr_val.get_type(subscript))
239
+ if not isinstance(index_type, QuantumScalar):
240
+ raise ClassiqExpansionError("Quantum index must be an unsigned integer")
241
+ validate_quantum_subscript_index_properties(
242
+ *get_numeric_properties(index_type), array_len(subject_type)
243
+ )
244
+
213
245
  expr_val.set_quantum_subscript(node, subject, subscript)
214
246
  if not expr_val.has_value(subject):
215
247
  expr_val.set_type(node, QuantumNumeric())
216
248
  return
217
249
 
218
250
  items = expr_val.get_value(subject)
219
- numeric_types = [
220
- compute_arithmetic_result_type(str(num), {}, machine_precision) for num in items
221
- ]
222
- unified_numeric_type = register_info_to_quantum_type(
223
- aggregate_numeric_types(numeric_types)
224
- )
225
- expr_val.set_type(node, unified_numeric_type)
251
+ result_attrs = compute_result_attrs_quantum_subscript(items, machine_precision)
252
+ expr_val.set_type(node, result_attrs.to_quantum_numeric())