classiq 0.84.0__py3-none-any.whl → 0.86.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 (87) hide show
  1. classiq/applications/combinatorial_optimization/combinatorial_problem.py +24 -45
  2. classiq/evaluators/classical_expression.py +32 -15
  3. classiq/evaluators/qmod_annotated_expression.py +207 -0
  4. classiq/evaluators/qmod_expression_visitors/__init__.py +0 -0
  5. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +134 -0
  6. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +232 -0
  7. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +44 -0
  8. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +308 -0
  9. classiq/evaluators/qmod_node_evaluators/__init__.py +0 -0
  10. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +112 -0
  11. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +132 -0
  12. classiq/evaluators/qmod_node_evaluators/bool_op_evaluation.py +70 -0
  13. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +311 -0
  14. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +107 -0
  15. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +67 -0
  16. classiq/evaluators/qmod_node_evaluators/list_evaluation.py +107 -0
  17. classiq/evaluators/qmod_node_evaluators/measurement_evaluation.py +25 -0
  18. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +50 -0
  19. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +66 -0
  20. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +225 -0
  21. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +58 -0
  22. classiq/evaluators/qmod_node_evaluators/utils.py +80 -0
  23. classiq/execution/execution_session.py +53 -6
  24. classiq/interface/_version.py +1 -1
  25. classiq/interface/analyzer/analysis_params.py +1 -1
  26. classiq/interface/analyzer/result.py +1 -1
  27. classiq/interface/debug_info/debug_info.py +0 -4
  28. classiq/interface/executor/quantum_code.py +2 -2
  29. classiq/interface/generator/arith/arithmetic_expression_validator.py +5 -1
  30. classiq/interface/generator/arith/binary_ops.py +43 -51
  31. classiq/interface/generator/arith/number_utils.py +3 -2
  32. classiq/interface/generator/arith/register_user_input.py +15 -0
  33. classiq/interface/generator/arith/unary_ops.py +32 -28
  34. classiq/interface/generator/expressions/atomic_expression_functions.py +5 -0
  35. classiq/interface/generator/expressions/expression_types.py +2 -2
  36. classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
  37. classiq/interface/generator/functions/builtins/internal_operators.py +2 -0
  38. classiq/interface/generator/functions/classical_function_declaration.py +0 -4
  39. classiq/interface/generator/functions/classical_type.py +0 -32
  40. classiq/interface/generator/functions/concrete_types.py +20 -0
  41. classiq/interface/generator/generated_circuit_data.py +7 -10
  42. classiq/interface/generator/quantum_program.py +6 -1
  43. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +29 -0
  44. classiq/interface/ide/operation_registry.py +45 -0
  45. classiq/interface/ide/visual_model.py +84 -2
  46. classiq/interface/model/bounds.py +12 -2
  47. classiq/interface/model/quantum_expressions/arithmetic_operation.py +7 -4
  48. classiq/interface/model/quantum_type.py +67 -33
  49. classiq/interface/model/variable_declaration_statement.py +33 -6
  50. classiq/model_expansions/arithmetic.py +115 -0
  51. classiq/model_expansions/arithmetic_compute_result_attrs.py +71 -0
  52. classiq/model_expansions/atomic_expression_functions_defs.py +10 -6
  53. classiq/model_expansions/function_builder.py +4 -1
  54. classiq/model_expansions/generative_functions.py +15 -2
  55. classiq/model_expansions/interpreters/base_interpreter.py +7 -0
  56. classiq/model_expansions/interpreters/generative_interpreter.py +18 -1
  57. classiq/model_expansions/quantum_operations/assignment_result_processor.py +63 -21
  58. classiq/model_expansions/quantum_operations/bounds.py +7 -1
  59. classiq/model_expansions/quantum_operations/call_emitter.py +5 -2
  60. classiq/model_expansions/quantum_operations/classical_var_emitter.py +16 -0
  61. classiq/model_expansions/quantum_operations/variable_decleration.py +30 -10
  62. classiq/model_expansions/scope.py +7 -0
  63. classiq/model_expansions/scope_initialization.py +2 -0
  64. classiq/model_expansions/sympy_conversion/sympy_to_python.py +1 -1
  65. classiq/model_expansions/transformers/type_modifier_inference.py +5 -0
  66. classiq/model_expansions/transformers/var_splitter.py +1 -1
  67. classiq/model_expansions/visitors/boolean_expression_transformers.py +1 -1
  68. classiq/open_library/functions/__init__.py +0 -2
  69. classiq/open_library/functions/qaoa_penalty.py +8 -1
  70. classiq/open_library/functions/state_preparation.py +1 -32
  71. classiq/qmod/__init__.py +2 -0
  72. classiq/qmod/builtins/operations.py +66 -2
  73. classiq/qmod/classical_variable.py +74 -0
  74. classiq/qmod/declaration_inferrer.py +5 -3
  75. classiq/qmod/native/pretty_printer.py +18 -14
  76. classiq/qmod/pretty_print/pretty_printer.py +34 -15
  77. classiq/qmod/qfunc.py +2 -19
  78. classiq/qmod/qmod_variable.py +5 -8
  79. classiq/qmod/quantum_expandable.py +1 -1
  80. classiq/qmod/quantum_function.py +42 -2
  81. classiq/qmod/symbolic_type.py +2 -1
  82. classiq/qmod/write_qmod.py +3 -1
  83. {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/METADATA +1 -1
  84. {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/RECORD +86 -62
  85. classiq/interface/model/quantum_variable_declaration.py +0 -7
  86. /classiq/{model_expansions/sympy_conversion/arithmetics.py → evaluators/qmod_expression_visitors/sympy_wrappers.py} +0 -0
  87. {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,132 @@
1
+ import ast
2
+ from typing import TYPE_CHECKING
3
+
4
+ from classiq.interface.exceptions import (
5
+ ClassiqExpansionError,
6
+ ClassiqInternalExpansionError,
7
+ )
8
+ from classiq.interface.generator.functions.classical_type import Integer, Real
9
+ from classiq.interface.model.quantum_type import QuantumNumeric
10
+
11
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
12
+ from classiq.evaluators.qmod_node_evaluators.utils import (
13
+ IntegerValueType,
14
+ NumberValueType,
15
+ QmodType,
16
+ is_classical_integer,
17
+ is_classical_type,
18
+ is_numeric_type,
19
+ )
20
+
21
+
22
+ def _binary_op_allowed(left: QmodType, right: QmodType) -> bool:
23
+ return is_numeric_type(left) and is_numeric_type(right)
24
+
25
+
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):
28
+ raise ClassiqExpansionError(
29
+ f"Both sides of the binary operator {type(op).__name__!r} must be "
30
+ f"scalar values"
31
+ )
32
+ if isinstance(op, (ast.LShift, ast.RShift)) and (
33
+ isinstance(left_type, Real) or isinstance(right_type, Real)
34
+ ):
35
+ raise ClassiqExpansionError(
36
+ f"Bitshift operation {type(op).__name__!r} on real values is not "
37
+ f"supported"
38
+ )
39
+ if isinstance(op, (ast.BitOr, ast.BitXor, ast.BitAnd)) and (
40
+ isinstance(left_type, Real) or isinstance(right_type, Real)
41
+ ):
42
+ raise ClassiqExpansionError(
43
+ f"Bitwise operation {type(op).__name__!r} on real values is not supported"
44
+ )
45
+
46
+ if isinstance(op, ast.MatMult):
47
+ raise ClassiqExpansionError(
48
+ f"Binary operation {type(op).__name__!r} is not supported"
49
+ )
50
+
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)
54
+ )
55
+ ):
56
+ raise ClassiqExpansionError(
57
+ f"Right-hand side of binary operation {type(op).__name__!r} must be classical numeric value"
58
+ )
59
+
60
+
61
+ def _eval_binary_op_constant(
62
+ op: ast.AST, left_value: NumberValueType, right_value: NumberValueType
63
+ ) -> NumberValueType:
64
+ if isinstance(op, ast.Add):
65
+ return left_value + right_value
66
+ if isinstance(op, ast.Sub):
67
+ return left_value - right_value
68
+ if isinstance(op, ast.Mult):
69
+ return left_value * right_value
70
+ if isinstance(op, ast.Div):
71
+ if right_value == 0:
72
+ raise ClassiqExpansionError("Division by zero")
73
+ return left_value / right_value
74
+ if isinstance(op, ast.FloorDiv):
75
+ if right_value == 0:
76
+ raise ClassiqExpansionError("Integer division by zero")
77
+ if isinstance(left_value, complex) or isinstance(right_value, complex):
78
+ raise ClassiqExpansionError("Integer division with a complex number")
79
+ return left_value // right_value
80
+ if isinstance(op, ast.Mod):
81
+ if right_value == 0:
82
+ raise ClassiqExpansionError("Integer modulu by zero")
83
+ if isinstance(left_value, complex) or isinstance(right_value, complex):
84
+ raise ClassiqExpansionError("Integer modulu with a complex number")
85
+ return left_value % right_value
86
+ if isinstance(op, ast.Pow):
87
+ return left_value**right_value
88
+
89
+ if TYPE_CHECKING:
90
+ assert isinstance(left_value, IntegerValueType)
91
+ assert isinstance(right_value, IntegerValueType)
92
+
93
+ if isinstance(op, ast.LShift):
94
+ return left_value << right_value
95
+ if isinstance(op, ast.RShift):
96
+ return left_value >> right_value
97
+ if isinstance(op, ast.BitAnd):
98
+ return left_value & right_value
99
+ if isinstance(op, ast.BitOr):
100
+ return left_value | right_value
101
+ if isinstance(op, ast.BitXor):
102
+ return left_value ^ right_value
103
+
104
+ raise ClassiqInternalExpansionError
105
+
106
+
107
+ def eval_binary_op(expr_val: QmodAnnotatedExpression, node: ast.BinOp) -> None:
108
+ left = node.left
109
+ right = node.right
110
+ op = node.op
111
+
112
+ left_type = expr_val.get_type(left)
113
+ 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)
128
+
129
+ if expr_val.has_value(left) and expr_val.has_value(right):
130
+ left_value = expr_val.get_value(left)
131
+ right_value = expr_val.get_value(right)
132
+ expr_val.set_value(node, _eval_binary_op_constant(op, left_value, right_value))
@@ -0,0 +1,70 @@
1
+ import ast
2
+ from functools import reduce
3
+ from operator import and_, or_
4
+
5
+ from classiq.interface.exceptions import (
6
+ ClassiqExpansionError,
7
+ ClassiqInternalExpansionError,
8
+ )
9
+ from classiq.interface.generator.functions.classical_type import Bool
10
+ from classiq.interface.model.quantum_type import QuantumBit, QuantumNumeric
11
+
12
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
13
+ from classiq.evaluators.qmod_node_evaluators.utils import (
14
+ QmodType,
15
+ is_classical_type,
16
+ qnum_is_qbit,
17
+ )
18
+
19
+
20
+ def bool_op_allowed(left: QmodType, right: QmodType) -> bool:
21
+ return is_bool_type(left) and is_bool_type(right)
22
+
23
+
24
+ def is_bool_type(qmod_type: QmodType) -> bool:
25
+ if isinstance(qmod_type, QuantumNumeric):
26
+ return qnum_is_qbit(qmod_type)
27
+ elif not isinstance(qmod_type, (QuantumBit, Bool)):
28
+ return False
29
+ return True
30
+
31
+
32
+ def eval_bool_op(expr_val: QmodAnnotatedExpression, node: ast.BoolOp) -> None:
33
+ if len(node.values) < 2:
34
+ raise ClassiqInternalExpansionError
35
+
36
+ left = node.values[0]
37
+ rights = node.values[0:]
38
+ op = node.op
39
+
40
+ left_type = expr_val.get_type(left)
41
+ right_types = [expr_val.get_type(right) for right in rights]
42
+ if not all(bool_op_allowed(left_type, right_type) for right_type in right_types):
43
+ raise ClassiqExpansionError(
44
+ f"Both sides of the Boolean operator {type(op).__name__!r} must be "
45
+ f"Boolean values"
46
+ )
47
+ qmod_type: QmodType
48
+ if not is_classical_type(left_type) or not all(
49
+ is_classical_type(right_type) for right_type in right_types
50
+ ):
51
+ qmod_type = QuantumBit()
52
+ else:
53
+ qmod_type = Bool()
54
+ expr_val.set_type(node, qmod_type)
55
+
56
+ if not expr_val.has_value(left) or not all(
57
+ expr_val.has_value(right) for right in rights
58
+ ):
59
+ return
60
+
61
+ left_value = expr_val.get_value(left)
62
+ right_values = [expr_val.get_value(right) for right in rights]
63
+ if isinstance(op, ast.And):
64
+ operator = and_
65
+ elif isinstance(op, ast.Or):
66
+ operator = or_
67
+ else:
68
+ raise ClassiqInternalExpansionError
69
+ constant_value = reduce(operator, [left_value, *right_values])
70
+ expr_val.set_value(node, constant_value)
@@ -0,0 +1,311 @@
1
+ import ast
2
+ from typing import Any, Callable, Optional, Union, cast
3
+
4
+ import sympy
5
+
6
+ from classiq.interface.exceptions import (
7
+ ClassiqExpansionError,
8
+ ClassiqInternalExpansionError,
9
+ )
10
+ from classiq.interface.generator.functions.classical_function_declaration import (
11
+ ClassicalFunctionDeclaration,
12
+ )
13
+ from classiq.interface.generator.functions.classical_type import (
14
+ Bool,
15
+ ClassicalArray,
16
+ ClassicalTuple,
17
+ ClassicalType,
18
+ Integer,
19
+ Real,
20
+ )
21
+ from classiq.interface.generator.functions.type_name import TypeName
22
+ from classiq.interface.helpers.backward_compatibility import zip_strict
23
+ from classiq.interface.helpers.pydantic_model_helpers import nameables_to_dict
24
+
25
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
26
+ from classiq.evaluators.qmod_node_evaluators.utils import (
27
+ SYMPY_SYMBOLS,
28
+ QmodType,
29
+ array_len,
30
+ element_types,
31
+ get_qmod_type_name,
32
+ )
33
+
34
+ MIN_MAX_FUNCTION_NAMES = {"min", "Min", "max", "Max"}
35
+
36
+
37
+ def _check_classical_array_arg_type(
38
+ param_type: Union[ClassicalArray, ClassicalTuple], arg_type: ClassicalType
39
+ ) -> bool:
40
+ if not isinstance(arg_type, (ClassicalArray, ClassicalTuple)):
41
+ return False
42
+ param_len = array_len(param_type)
43
+ arg_len = array_len(arg_type)
44
+ if param_len is not None and arg_len is not None and param_len != arg_len:
45
+ return False
46
+ if isinstance(param_type, ClassicalArray):
47
+ if isinstance(arg_type, ClassicalArray):
48
+ return check_classical_arg_type(
49
+ param_type.element_type, arg_type.element_type
50
+ )
51
+ return all(
52
+ check_classical_arg_type(param_type.element_type, arg_element_type)
53
+ for arg_element_type in arg_type.element_types
54
+ )
55
+ if isinstance(arg_type, ClassicalArray):
56
+ return all(
57
+ check_classical_arg_type(param_element_type, arg_type.element_type)
58
+ for param_element_type in param_type.element_types
59
+ )
60
+ return all(
61
+ check_classical_arg_type(param_element_type, arg_element_type)
62
+ for param_element_type, arg_element_type in zip_strict(
63
+ param_type.element_types, arg_type.element_types, strict=True
64
+ )
65
+ )
66
+
67
+
68
+ def check_classical_arg_type(
69
+ param_type: ClassicalType, arg_type: ClassicalType
70
+ ) -> bool:
71
+ if isinstance(param_type, (Integer, Real)):
72
+ return isinstance(arg_type, (Integer, Real))
73
+ if isinstance(param_type, Bool):
74
+ return isinstance(arg_type, Bool)
75
+ if isinstance(param_type, (ClassicalArray, ClassicalTuple)):
76
+ return _check_classical_array_arg_type(param_type, arg_type)
77
+ if not isinstance(param_type, TypeName):
78
+ raise ClassiqInternalExpansionError
79
+ if not isinstance(arg_type, TypeName):
80
+ return False
81
+ if param_type.is_enum or param_type.has_classical_struct_decl:
82
+ return arg_type.name == param_type.name
83
+ raise ClassiqInternalExpansionError
84
+
85
+
86
+ def _check_classical_arg(
87
+ func_name: str, param_name: str, param_type: ClassicalType, arg_type: QmodType
88
+ ) -> None:
89
+ if not isinstance(arg_type, ClassicalType) or not check_classical_arg_type(
90
+ param_type, arg_type
91
+ ):
92
+ raise ClassiqExpansionError(
93
+ 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)}"
96
+ )
97
+
98
+
99
+ def check_classical_func_call(
100
+ expr_val: QmodAnnotatedExpression,
101
+ node: ast.Call,
102
+ decl: ClassicalFunctionDeclaration,
103
+ ) -> None:
104
+ expected_num_args = len(decl.param_decls)
105
+ actual_num_args = len(node.args) + len(node.keywords)
106
+ if expected_num_args != actual_num_args:
107
+ raise ClassiqExpansionError(
108
+ f"Function {decl.name!r} takes {expected_num_args} arguments but "
109
+ f"{actual_num_args} were given"
110
+ )
111
+ params = list(decl.param_decls)
112
+ assigned_params: set[str] = set()
113
+ for arg in node.args:
114
+ param = params.pop(0)
115
+ assigned_params.add(param.name)
116
+ _check_classical_arg(
117
+ decl.name, param.name, param.classical_type, expr_val.get_type(arg)
118
+ )
119
+ remaining_params = nameables_to_dict(params)
120
+ for kwarg in node.keywords:
121
+ if kwarg.arg is None:
122
+ raise ClassiqExpansionError("Star argument syntax is not supported")
123
+ if kwarg.arg not in remaining_params:
124
+ if kwarg.arg in assigned_params:
125
+ raise ClassiqExpansionError(
126
+ f"Function {decl.name!r} got multiple values for parameter "
127
+ f"{kwarg.arg!r}"
128
+ )
129
+ raise ClassiqExpansionError(
130
+ f"Function {decl.name!r} has no parameter named {kwarg.arg}"
131
+ )
132
+ assigned_params.add(kwarg.arg)
133
+ param = remaining_params.pop(kwarg.arg)
134
+ _check_classical_arg(
135
+ decl.name, param.name, param.classical_type, expr_val.get_type(kwarg.value)
136
+ )
137
+
138
+
139
+ def eval_symbolic_function(
140
+ expr_val: QmodAnnotatedExpression,
141
+ node: ast.Call,
142
+ decl: ClassicalFunctionDeclaration,
143
+ ) -> None:
144
+ check_classical_func_call(expr_val, node, decl)
145
+ if decl.return_type is None:
146
+ raise ClassiqInternalExpansionError
147
+ expr_val.set_type(node, decl.return_type)
148
+
149
+
150
+ def eval_function(
151
+ expr_val: QmodAnnotatedExpression,
152
+ node: ast.Call,
153
+ decl: ClassicalFunctionDeclaration,
154
+ func: Callable,
155
+ ) -> None:
156
+ eval_symbolic_function(expr_val, node, decl)
157
+
158
+ args = node.args
159
+ kwargs = {kwarg.arg: kwarg.value for kwarg in node.keywords}
160
+ if None in kwargs:
161
+ raise ClassiqInternalExpansionError
162
+ if not all(expr_val.has_value(arg) for arg in args) or not all(
163
+ expr_val.has_value(kwarg) for kwarg in kwargs.values()
164
+ ):
165
+ return
166
+ arg_values = [expr_val.get_value(arg) for arg in args]
167
+ kwarg_values = {
168
+ cast(str, kwarg_name): expr_val.get_value(kwarg_value)
169
+ for kwarg_name, kwarg_value in kwargs.items()
170
+ }
171
+ expr_val.set_value(node, func(*arg_values, **kwarg_values))
172
+
173
+
174
+ def try_eval_sympy_function(
175
+ expr_val: QmodAnnotatedExpression, node: ast.Call, func_name: str
176
+ ) -> bool:
177
+ sympy_func = SYMPY_SYMBOLS.get(func_name)
178
+ if not isinstance(sympy_func, sympy.FunctionClass):
179
+ return False
180
+ _validate_no_kwargs(node)
181
+ args: Optional[list[Any]] = None
182
+ sympy_ret_val: Any = None
183
+ if all(expr_val.has_value(arg) for arg in node.args):
184
+ args = [expr_val.get_value(arg) for arg in node.args]
185
+ sympy_ret_val = sympy_func(*args)
186
+ if not isinstance(sympy_ret_val, sympy.Expr):
187
+ raise ClassiqInternalExpansionError
188
+ ret_type: QmodType
189
+ ret_val: Any = None
190
+ if hasattr(sympy_func, "is_Boolean") and sympy_func.is_Boolean:
191
+ ret_type = Bool()
192
+ if args is not None:
193
+ ret_val = bool(sympy_ret_val)
194
+ elif hasattr(sympy_func, "is_Integer") and sympy_func.is_Integer:
195
+ ret_type = Integer()
196
+ if args is not None:
197
+ ret_val = int(sympy_ret_val)
198
+ else:
199
+ ret_type = Real()
200
+ if sympy_ret_val is not None:
201
+ if sympy_ret_val.is_imaginary:
202
+ ret_val = complex(sympy_ret_val)
203
+ elif sympy_ret_val.is_real:
204
+ ret_val = float(sympy_ret_val)
205
+ else:
206
+ raise ClassiqInternalExpansionError
207
+ expr_val.set_type(node, ret_type)
208
+ if ret_val is not None:
209
+ expr_val.set_value(node, ret_val)
210
+ return True
211
+
212
+
213
+ def try_eval_builtin_function(
214
+ expr_val: QmodAnnotatedExpression, node: ast.Call, func_name: str
215
+ ) -> bool:
216
+ args_are_int = all(isinstance(expr_val.get_type(arg), Integer) for arg in node.args)
217
+ args_are_real = all(
218
+ isinstance(expr_val.get_type(arg), (Integer, Real)) for arg in node.args
219
+ )
220
+ arg_is_int_list = (
221
+ len(node.args) == 1
222
+ and isinstance(
223
+ arg_type := expr_val.get_type(node.args[0]),
224
+ (ClassicalArray, ClassicalTuple),
225
+ )
226
+ and all(
227
+ isinstance(element_type, Integer)
228
+ for element_type in element_types(arg_type)
229
+ )
230
+ )
231
+ arg_is_real_list = (
232
+ len(node.args) == 1
233
+ and isinstance(
234
+ arg_type := expr_val.get_type(node.args[0]),
235
+ (ClassicalArray, ClassicalTuple),
236
+ )
237
+ and all(
238
+ isinstance(element_type, (Integer, Real))
239
+ for element_type in element_types(arg_type)
240
+ )
241
+ )
242
+ args_have_values = all(expr_val.has_value(arg) for arg in node.args)
243
+
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
+ if func_name == "mod_inverse":
254
+ _validate_no_kwargs(node)
255
+ ret_type: QmodType
256
+ if args_are_int:
257
+ ret_type = Integer()
258
+ elif args_are_real:
259
+ ret_type = Real()
260
+ else:
261
+ raise ClassiqExpansionError(
262
+ "Function 'mod_inverse' expects numeric arguments"
263
+ )
264
+ expr_val.set_type(node, ret_type)
265
+ if args_have_values:
266
+ sympy_val = sympy.mod_inverse(
267
+ *[expr_val.get_value(arg) for arg in node.args]
268
+ )
269
+ 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:
273
+ ret_val = complex(sympy_val)
274
+ else:
275
+ ret_val = float(sympy_val)
276
+ expr_val.set_value(node, ret_val)
277
+ return True
278
+
279
+ if func_name == "sum":
280
+ _validate_no_kwargs(node)
281
+ if arg_is_int_list:
282
+ ret_type = Integer()
283
+ elif arg_is_real_list:
284
+ ret_type = Real()
285
+ else:
286
+ raise ClassiqExpansionError("Function 'sum' expects numeric arguments")
287
+ expr_val.set_type(node, ret_type)
288
+ if args_have_values:
289
+ expr_val.set_value(node, sum(expr_val.get_value(node.args[0])))
290
+ return True
291
+
292
+ if func_name == "sqrt":
293
+ _validate_no_kwargs(node)
294
+ expr_val.set_type(node, Real())
295
+ if args_have_values:
296
+ expr_val.set_value(
297
+ node, sympy.sqrt(*[expr_val.get_value(arg) for arg in node.args])
298
+ )
299
+ return True
300
+
301
+ return False
302
+
303
+
304
+ def _validate_no_kwargs(node: ast.Call) -> None:
305
+ if not isinstance(node.func, ast.Name):
306
+ raise ClassiqInternalExpansionError
307
+ if len(node.keywords) > 0:
308
+ raise ClassiqExpansionError(
309
+ f"Keyword argument syntax is not supported for built-in function "
310
+ f"{node.func.id!r}"
311
+ )
@@ -0,0 +1,107 @@
1
+ import ast
2
+
3
+ from classiq.interface.exceptions import (
4
+ ClassiqExpansionError,
5
+ ClassiqInternalExpansionError,
6
+ )
7
+ from classiq.interface.generator.functions.classical_type import (
8
+ Bool,
9
+ ClassicalArray,
10
+ ClassicalTuple,
11
+ Integer,
12
+ Real,
13
+ )
14
+ from classiq.interface.generator.functions.type_name import TypeName
15
+ from classiq.interface.model.quantum_type import (
16
+ QuantumBit,
17
+ QuantumNumeric,
18
+ QuantumScalar,
19
+ )
20
+
21
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
22
+ from classiq.evaluators.qmod_node_evaluators.list_evaluation import list_allowed
23
+ from classiq.evaluators.qmod_node_evaluators.utils import (
24
+ QmodType,
25
+ element_types,
26
+ get_qmod_type_name,
27
+ is_classical_integer,
28
+ is_classical_type,
29
+ qnum_is_qbit,
30
+ )
31
+
32
+
33
+ def comparison_allowed(
34
+ left: QmodType, right: QmodType, inequality: bool = False
35
+ ) -> bool:
36
+ if isinstance(left, Bool):
37
+ return isinstance(right, (Bool, QuantumBit)) or (
38
+ isinstance(right, QuantumNumeric) and qnum_is_qbit(right)
39
+ )
40
+ if isinstance(left, Real) or is_classical_integer(left):
41
+ return isinstance(right, (Real, QuantumScalar)) or is_classical_integer(right)
42
+ if isinstance(left, (ClassicalArray, ClassicalTuple)):
43
+ if inequality:
44
+ return False
45
+ if not isinstance(right, (ClassicalArray, ClassicalTuple)):
46
+ return False
47
+ return list_allowed(element_types(left) + element_types(right))
48
+ if isinstance(left, TypeName):
49
+ return (
50
+ left.has_classical_struct_decl
51
+ and not inequality
52
+ and isinstance(right, TypeName)
53
+ and left.name == right.name
54
+ ) or (
55
+ left.is_enum
56
+ and (
57
+ isinstance(right, (Integer, Real, QuantumScalar))
58
+ or (isinstance(right, TypeName) and right.is_enum)
59
+ )
60
+ )
61
+ if isinstance(left, QuantumScalar):
62
+ return isinstance(right, (Bool, Real, QuantumScalar)) or is_classical_integer(
63
+ right
64
+ )
65
+ raise ClassiqInternalExpansionError
66
+
67
+
68
+ def eval_compare(expr_val: QmodAnnotatedExpression, node: ast.Compare) -> None:
69
+ left = node.left
70
+ if len(node.ops) != 1 or len(node.comparators) != 1:
71
+ raise ClassiqExpansionError("Multi-compare expressions are not supported")
72
+ right = node.comparators[0]
73
+ op = node.ops[0]
74
+
75
+ left_type = expr_val.get_type(left)
76
+ right_type = expr_val.get_type(right)
77
+ if not comparison_allowed(left_type, right_type, not isinstance(op, ast.Eq)):
78
+ 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}"
81
+ )
82
+ qmod_type: QmodType
83
+ if not is_classical_type(left_type) or not is_classical_type(right_type):
84
+ qmod_type = QuantumBit()
85
+ else:
86
+ qmod_type = Bool()
87
+ expr_val.set_type(node, qmod_type)
88
+
89
+ if not expr_val.has_value(left) or not expr_val.has_value(right):
90
+ return
91
+
92
+ left_value = expr_val.get_value(left)
93
+ right_value = expr_val.get_value(right)
94
+ if isinstance(op, ast.Eq):
95
+ expr_val.set_value(node, left_value == right_value)
96
+ elif isinstance(op, ast.NotEq):
97
+ expr_val.set_value(node, left_value != right_value)
98
+ elif isinstance(op, ast.Lt):
99
+ expr_val.set_value(node, left_value < right_value)
100
+ elif isinstance(op, ast.Gt):
101
+ expr_val.set_value(node, left_value > right_value)
102
+ elif isinstance(op, ast.LtE):
103
+ expr_val.set_value(node, left_value <= right_value)
104
+ elif isinstance(op, ast.GtE):
105
+ expr_val.set_value(node, left_value >= right_value)
106
+ else:
107
+ raise ClassiqExpansionError(f"Unsupported comparison {type(op).__name__!r}")
@@ -0,0 +1,67 @@
1
+ import ast
2
+ from enum import IntEnum
3
+ from typing import Any
4
+
5
+ import sympy
6
+
7
+ from classiq.interface.exceptions import (
8
+ ClassiqExpansionError,
9
+ )
10
+ from classiq.interface.generator.functions.classical_type import Bool, Integer, Real
11
+ from classiq.interface.generator.functions.type_name import Enum
12
+
13
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
14
+ from classiq.evaluators.qmod_node_evaluators.utils import SYMPY_SYMBOLS, QmodType
15
+
16
+
17
+ def eval_enum_member(
18
+ expr_val: QmodAnnotatedExpression, node: ast.Attribute, enum: type[IntEnum]
19
+ ) -> None:
20
+ enum_name = enum.__name__
21
+ enum_members = { # type: ignore[var-annotated]
22
+ member.name: member for member in list(enum)
23
+ }
24
+ attr = node.attr
25
+ if attr not in enum_members:
26
+ raise ClassiqExpansionError(f"Enum {enum_name} has no member named {attr!r}")
27
+
28
+ expr_val.set_type(node, Enum(name=enum_name))
29
+ expr_val.set_value(node, enum_members[attr])
30
+
31
+
32
+ def eval_constant(expr_val: QmodAnnotatedExpression, node: ast.Constant) -> None:
33
+ value = node.value
34
+ expr_val.set_value(node, value)
35
+ constant_type: QmodType
36
+ if isinstance(value, bool):
37
+ constant_type = Bool()
38
+ elif isinstance(value, int):
39
+ constant_type = Integer()
40
+ elif isinstance(value, (float, complex)):
41
+ constant_type = Real()
42
+ else:
43
+ raise ClassiqExpansionError(f"Unsupported constant {str(value)!r}")
44
+ expr_val.set_type(node, constant_type)
45
+
46
+
47
+ def try_eval_sympy_constant(expr_val: QmodAnnotatedExpression, node: ast.Name) -> bool:
48
+ sympy_val = SYMPY_SYMBOLS.get(node.id)
49
+ if not isinstance(sympy_val, sympy.Expr) or not sympy_val.is_constant():
50
+ return False
51
+ constant_type: QmodType
52
+ constant_value: Any
53
+ if sympy_val.is_Integer:
54
+ constant_type = Integer()
55
+ constant_value = int(sympy_val)
56
+ elif sympy_val.is_Boolean:
57
+ constant_type = Bool()
58
+ constant_value = bool(sympy_val)
59
+ elif sympy_val.is_imaginary:
60
+ constant_type = Real()
61
+ constant_value = complex(sympy_val)
62
+ else:
63
+ constant_type = Real()
64
+ constant_value = float(sympy_val)
65
+ expr_val.set_type(node, constant_type)
66
+ expr_val.set_value(node, constant_value)
67
+ return True