classiq 0.86.1__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.1.dist-info → classiq-0.87.0.dist-info}/METADATA +3 -3
  81. {classiq-0.86.1.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.1.dist-info → classiq-0.87.0.dist-info}/WHEEL +0 -0
@@ -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_evaluated:
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,7 +15,6 @@ 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(
@@ -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
@@ -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
@@ -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())
@@ -1,5 +1,5 @@
1
1
  import ast
2
- from typing import Any
2
+ from typing import TYPE_CHECKING, Any
3
3
 
4
4
  from classiq.interface.exceptions import (
5
5
  ClassiqExpansionError,
@@ -7,10 +7,23 @@ from classiq.interface.exceptions import (
7
7
  )
8
8
  from classiq.interface.generator.functions.classical_type import Integer, Real
9
9
  from classiq.interface.generator.functions.type_name import TypeName
10
+ from classiq.interface.model.quantum_type import (
11
+ QuantumNumeric,
12
+ QuantumScalar,
13
+ )
10
14
 
11
15
  from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
12
16
  from classiq.evaluators.qmod_node_evaluators.bool_op_evaluation import is_bool_type
13
- from classiq.evaluators.qmod_node_evaluators.utils import QmodType, is_numeric_type
17
+ from classiq.evaluators.qmod_node_evaluators.utils import (
18
+ QmodType,
19
+ is_classical_type,
20
+ is_numeric_type,
21
+ )
22
+ from classiq.model_expansions.arithmetic import NumericAttributes
23
+ from classiq.model_expansions.arithmetic_compute_result_attrs import (
24
+ compute_result_attrs_bitwise_invert,
25
+ compute_result_attrs_negate,
26
+ )
14
27
 
15
28
 
16
29
  def unary_op_allowed(op: ast.AST, operand_type: QmodType) -> bool:
@@ -19,7 +32,42 @@ def unary_op_allowed(op: ast.AST, operand_type: QmodType) -> bool:
19
32
  return is_numeric_type(operand_type)
20
33
 
21
34
 
22
- def eval_unary_op(expr_val: QmodAnnotatedExpression, node: ast.UnaryOp) -> None:
35
+ def _infer_unary_op_type(
36
+ op: ast.AST, operand_type: QmodType, machine_precision: int
37
+ ) -> QmodType:
38
+ if is_classical_type(operand_type):
39
+ if isinstance(operand_type, TypeName):
40
+ # The operand is enum, return as integer
41
+ return Integer()
42
+ return operand_type
43
+
44
+ if TYPE_CHECKING:
45
+ assert isinstance(operand_type, QuantumScalar)
46
+
47
+ if isinstance(op, (ast.Not, ast.UAdd)):
48
+ return operand_type
49
+
50
+ if not operand_type.is_evaluated:
51
+ return QuantumNumeric()
52
+
53
+ operand_attrs = NumericAttributes.from_quantum_scalar(
54
+ operand_type, machine_precision
55
+ )
56
+ if isinstance(op, ast.Invert):
57
+ result_attrs = compute_result_attrs_bitwise_invert(
58
+ operand_attrs, machine_precision
59
+ )
60
+ elif isinstance(op, ast.USub):
61
+ result_attrs = compute_result_attrs_negate(operand_attrs, machine_precision)
62
+ else:
63
+ raise ClassiqInternalExpansionError
64
+
65
+ return result_attrs.to_quantum_numeric()
66
+
67
+
68
+ def eval_unary_op(
69
+ expr_val: QmodAnnotatedExpression, node: ast.UnaryOp, machine_precision: int
70
+ ) -> None:
23
71
  operand = node.operand
24
72
  op = node.op
25
73
 
@@ -34,12 +82,8 @@ def eval_unary_op(expr_val: QmodAnnotatedExpression, node: ast.UnaryOp) -> None:
34
82
  raise ClassiqExpansionError(
35
83
  f"Operation {type(op).__name__!r} on a real value is not supported"
36
84
  )
37
- op_type: QmodType
38
- if isinstance(operand_type, TypeName):
39
- op_type = Integer()
40
- else:
41
- op_type = operand_type
42
- expr_val.set_type(node, op_type)
85
+
86
+ expr_val.set_type(node, _infer_unary_op_type(op, operand_type, machine_precision))
43
87
 
44
88
  if not expr_val.has_value(operand):
45
89
  return
@@ -1,7 +1,8 @@
1
- from typing import Any, Optional, Union, cast
1
+ from typing import Optional, Union, cast
2
2
 
3
3
  import sympy
4
4
 
5
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
5
6
  from classiq.interface.generator.functions.classical_type import (
6
7
  ClassicalArray,
7
8
  ClassicalTuple,
@@ -24,10 +25,6 @@ NumberValueType = Union[IntegerValueType, RealValueType]
24
25
  SYMPY_SYMBOLS = {sym: getattr(sympy, sym) for sym in sympy.__all__}
25
26
 
26
27
 
27
- def get_qmod_type_name(type_: Any) -> str:
28
- return getattr(type_, "type_name", type(type_).__name__)
29
-
30
-
31
28
  def is_classical_type(qmod_type: QmodType) -> bool:
32
29
  if isinstance(qmod_type, TypeName):
33
30
  return qmod_type.has_classical_struct_decl or qmod_type.is_enum
@@ -42,6 +39,31 @@ def qnum_is_qbit(qmod_type: QuantumNumeric) -> bool:
42
39
  )
43
40
 
44
41
 
42
+ def get_numeric_properties(
43
+ qmod_type: QuantumScalar,
44
+ ) -> tuple[Optional[int], Optional[bool], Optional[int]]:
45
+ if isinstance(qmod_type, QuantumBit):
46
+ return 1, False, 0
47
+ if not isinstance(qmod_type, QuantumNumeric):
48
+ raise ClassiqInternalExpansionError
49
+ size = qmod_type.size_in_bits if qmod_type.has_size_in_bits else None
50
+ is_signed_expr = qmod_type.is_signed
51
+ if is_signed_expr is None:
52
+ is_signed = False
53
+ elif is_signed_expr.is_evaluated() and is_signed_expr.is_constant():
54
+ is_signed = is_signed_expr.to_bool_value()
55
+ else:
56
+ is_signed = None
57
+ fraction_digits_expr = qmod_type.fraction_digits
58
+ if fraction_digits_expr is None:
59
+ fraction_digits = 0
60
+ elif fraction_digits_expr.is_evaluated() and fraction_digits_expr.is_constant():
61
+ fraction_digits = fraction_digits_expr.to_int_value()
62
+ else:
63
+ fraction_digits = None
64
+ return size, is_signed, fraction_digits
65
+
66
+
45
67
  def qnum_is_qint(qmod_type: QuantumType) -> bool:
46
68
  return isinstance(qmod_type, QuantumBit) or (
47
69
  isinstance(qmod_type, QuantumNumeric)