classiq 0.85.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 (59) hide show
  1. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -3
  2. classiq/evaluators/qmod_annotated_expression.py +207 -0
  3. classiq/evaluators/qmod_expression_visitors/__init__.py +0 -0
  4. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +134 -0
  5. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +232 -0
  6. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +44 -0
  7. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +308 -0
  8. classiq/evaluators/qmod_node_evaluators/__init__.py +0 -0
  9. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +112 -0
  10. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +132 -0
  11. classiq/evaluators/qmod_node_evaluators/bool_op_evaluation.py +70 -0
  12. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +311 -0
  13. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +107 -0
  14. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +67 -0
  15. classiq/evaluators/qmod_node_evaluators/list_evaluation.py +107 -0
  16. classiq/evaluators/qmod_node_evaluators/measurement_evaluation.py +25 -0
  17. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +50 -0
  18. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +66 -0
  19. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +225 -0
  20. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +58 -0
  21. classiq/evaluators/qmod_node_evaluators/utils.py +80 -0
  22. classiq/execution/execution_session.py +4 -0
  23. classiq/interface/_version.py +1 -1
  24. classiq/interface/analyzer/analysis_params.py +1 -1
  25. classiq/interface/analyzer/result.py +1 -1
  26. classiq/interface/executor/quantum_code.py +2 -2
  27. classiq/interface/generator/arith/arithmetic_expression_validator.py +5 -1
  28. classiq/interface/generator/arith/binary_ops.py +43 -51
  29. classiq/interface/generator/arith/number_utils.py +3 -2
  30. classiq/interface/generator/arith/register_user_input.py +15 -0
  31. classiq/interface/generator/arith/unary_ops.py +32 -28
  32. classiq/interface/generator/expressions/expression_types.py +2 -2
  33. classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
  34. classiq/interface/generator/functions/classical_function_declaration.py +0 -4
  35. classiq/interface/generator/functions/classical_type.py +0 -32
  36. classiq/interface/generator/generated_circuit_data.py +2 -0
  37. classiq/interface/generator/quantum_program.py +6 -1
  38. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +29 -0
  39. classiq/interface/ide/operation_registry.py +2 -2
  40. classiq/interface/ide/visual_model.py +22 -1
  41. classiq/interface/model/quantum_type.py +67 -33
  42. classiq/model_expansions/arithmetic.py +115 -0
  43. classiq/model_expansions/arithmetic_compute_result_attrs.py +71 -0
  44. classiq/model_expansions/atomic_expression_functions_defs.py +5 -5
  45. classiq/model_expansions/generative_functions.py +15 -2
  46. classiq/model_expansions/interpreters/base_interpreter.py +7 -0
  47. classiq/model_expansions/interpreters/generative_interpreter.py +2 -0
  48. classiq/model_expansions/quantum_operations/call_emitter.py +5 -2
  49. classiq/model_expansions/scope_initialization.py +2 -0
  50. classiq/model_expansions/sympy_conversion/sympy_to_python.py +1 -1
  51. classiq/model_expansions/transformers/type_modifier_inference.py +5 -0
  52. classiq/model_expansions/visitors/boolean_expression_transformers.py +1 -1
  53. classiq/qmod/builtins/operations.py +1 -1
  54. classiq/qmod/declaration_inferrer.py +5 -3
  55. classiq/qmod/write_qmod.py +3 -1
  56. {classiq-0.85.0.dist-info → classiq-0.86.0.dist-info}/METADATA +1 -1
  57. {classiq-0.85.0.dist-info → classiq-0.86.0.dist-info}/RECORD +59 -37
  58. /classiq/{model_expansions/sympy_conversion/arithmetics.py → evaluators/qmod_expression_visitors/sympy_wrappers.py} +0 -0
  59. {classiq-0.85.0.dist-info → classiq-0.86.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,308 @@
1
+ import ast
2
+ from typing import Any, Optional, cast
3
+
4
+ import sympy
5
+
6
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
7
+ from classiq.interface.generator.arith.arithmetic import compute_arithmetic_result_type
8
+ from classiq.interface.generator.expressions.expression import Expression
9
+ from classiq.interface.model.handle_binding import HandleBinding
10
+ from classiq.interface.model.quantum_type import (
11
+ QuantumBit,
12
+ QuantumNumeric,
13
+ QuantumType,
14
+ )
15
+
16
+ from classiq.evaluators.qmod_annotated_expression import (
17
+ QmodAnnotatedExpression,
18
+ QmodExprNodeId,
19
+ )
20
+ from classiq.evaluators.qmod_expression_visitors.sympy_wrappers import (
21
+ BitwiseAnd,
22
+ BitwiseNot,
23
+ BitwiseOr,
24
+ BitwiseXor,
25
+ LShift,
26
+ RShift,
27
+ )
28
+ from classiq.evaluators.qmod_node_evaluators.utils import QmodType
29
+
30
+ _SYMPY_WRAPPERS = {
31
+ wrapper.__name__: wrapper
32
+ for wrapper in [
33
+ BitwiseAnd,
34
+ BitwiseNot,
35
+ BitwiseOr,
36
+ BitwiseXor,
37
+ LShift,
38
+ RShift,
39
+ ]
40
+ }
41
+
42
+
43
+ class _VarMaskTransformer(ast.NodeTransformer):
44
+ def __init__(self, expr_val: QmodAnnotatedExpression) -> None:
45
+ self._expr_val = expr_val
46
+ self._mask_id = 0
47
+ self.masks: dict[str, QmodExprNodeId] = {}
48
+ self._assigned_masks: dict[HandleBinding, str] = {}
49
+
50
+ def _create_mask(self) -> str:
51
+ mask = f"x{self._mask_id}"
52
+ self._mask_id += 1
53
+ return mask
54
+
55
+ def visit(self, node: ast.AST) -> ast.AST:
56
+ if self._expr_val.has_value(node):
57
+ return ast.parse(str(self._expr_val.get_value(node)))
58
+ if self._expr_val.has_var(node):
59
+ var = self._expr_val.get_var(node)
60
+ if var in self._assigned_masks:
61
+ mask = self._assigned_masks[var]
62
+ else:
63
+ mask = self._create_mask()
64
+ self._assigned_masks[var] = mask
65
+ self.masks[mask] = id(node)
66
+ return ast.Name(id=mask)
67
+ if self._expr_val.has_quantum_subscript(node):
68
+ mask = self._create_mask()
69
+ self.masks[mask] = id(node)
70
+ return ast.Name(id=mask)
71
+ return self.generic_visit(node)
72
+
73
+ def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
74
+ mask = self._create_mask()
75
+ self.masks[mask] = id(node)
76
+ return ast.Name(id=mask)
77
+
78
+
79
+ class _InverseVarMaskTransformer(ast.NodeTransformer):
80
+ def __init__(
81
+ self, expr_val: QmodAnnotatedExpression, masks: dict[str, QmodExprNodeId]
82
+ ) -> None:
83
+ self._expr_val = expr_val
84
+ self._masks = masks
85
+
86
+ def visit_Name(self, node: ast.Name) -> Any:
87
+ name = node.id
88
+ if name in self._masks:
89
+ mask_id = self._masks[name]
90
+ if not self._expr_val.has_node(mask_id):
91
+ raise ClassiqInternalExpansionError
92
+ return ast.Name(id=ast.unparse(self._expr_val.get_node(mask_id)))
93
+ return node
94
+
95
+
96
+ class _SympyCompatibilityTransformer(ast.NodeTransformer):
97
+ def visit_BoolOp(self, node: ast.BoolOp) -> ast.Call:
98
+ if len(node.values) != 2:
99
+ raise ClassiqInternalExpansionError
100
+ node = cast(ast.BoolOp, self.generic_visit(node))
101
+ if isinstance(node.op, ast.Or):
102
+ sympy_func = "Or"
103
+ else:
104
+ sympy_func = "And"
105
+ return ast.Call(func=ast.Name(id=sympy_func), args=node.values, keywords=[])
106
+
107
+ def visit_UnaryOp(self, node: ast.UnaryOp) -> Any:
108
+ node = cast(ast.UnaryOp, self.generic_visit(node))
109
+ if isinstance(node.op, ast.Not):
110
+ sympy_func = "Not"
111
+ elif isinstance(node.op, ast.Invert):
112
+ sympy_func = BitwiseNot.__name__
113
+ else:
114
+ return node
115
+ return ast.Call(func=ast.Name(id=sympy_func), args=[node.operand], keywords=[])
116
+
117
+ def visit_Compare(self, node: ast.Compare) -> ast.Call:
118
+ if len(node.ops) != 1:
119
+ raise ClassiqInternalExpansionError
120
+ node = cast(ast.Compare, self.generic_visit(node))
121
+ op = node.ops[0]
122
+ if isinstance(op, ast.Eq):
123
+ sympy_func = "Eq"
124
+ elif isinstance(op, ast.NotEq):
125
+ sympy_func = "Ne"
126
+ elif isinstance(op, ast.Lt):
127
+ sympy_func = "Lt"
128
+ elif isinstance(op, ast.LtE):
129
+ sympy_func = "Le"
130
+ elif isinstance(op, ast.Gt):
131
+ sympy_func = "Gt"
132
+ elif isinstance(op, ast.GtE):
133
+ sympy_func = "Ge"
134
+ else:
135
+ raise ClassiqInternalExpansionError
136
+ return ast.Call(
137
+ func=ast.Name(id=sympy_func),
138
+ args=[node.left, node.comparators[0]],
139
+ keywords=[],
140
+ )
141
+
142
+ def visit_BinOp(self, node: ast.BinOp) -> Any:
143
+ node = cast(ast.BinOp, self.generic_visit(node))
144
+ if isinstance(node.op, ast.LShift):
145
+ sympy_func = LShift.__name__
146
+ elif isinstance(node.op, ast.RShift):
147
+ sympy_func = RShift.__name__
148
+ elif isinstance(node.op, ast.BitOr):
149
+ sympy_func = BitwiseOr.__name__
150
+ elif isinstance(node.op, ast.BitXor):
151
+ sympy_func = BitwiseXor.__name__
152
+ elif isinstance(node.op, ast.BitAnd):
153
+ sympy_func = BitwiseAnd.__name__
154
+ else:
155
+ return node
156
+ return ast.Call(
157
+ func=ast.Name(id=sympy_func), args=[node.left, node.right], keywords=[]
158
+ )
159
+
160
+
161
+ class _InverseSympyCompatibilityTransformer(ast.NodeTransformer):
162
+ def visit_Call(self, node: ast.Call) -> Any:
163
+ node = cast(ast.Call, self.generic_visit(node))
164
+ if not isinstance(node.func, ast.Name):
165
+ raise ClassiqInternalExpansionError
166
+ func = node.func.id
167
+
168
+ if (
169
+ func
170
+ in {
171
+ "Eq",
172
+ "Ne",
173
+ "Lt",
174
+ "Le",
175
+ "Gt",
176
+ "Ge",
177
+ LShift.__name__,
178
+ RShift.__name__,
179
+ BitwiseOr.__name__,
180
+ BitwiseXor.__name__,
181
+ BitwiseAnd.__name__,
182
+ }
183
+ and (len(node.args) != 2 or len(node.keywords) > 0)
184
+ ) or (
185
+ func in {BitwiseNot.__name__}
186
+ and (len(node.args) != 2 or len(node.keywords) > 0)
187
+ ):
188
+ raise ClassiqInternalExpansionError
189
+
190
+ if func == BitwiseNot.__name__:
191
+ return ast.UnaryOp(op=ast.Invert, operand=node.args[0])
192
+
193
+ if func == "Eq":
194
+ return ast.Compare(
195
+ left=node.args[0], ops=[ast.Eq()], comparators=[node.args[1]]
196
+ )
197
+ if func == "Ne":
198
+ return ast.Compare(
199
+ left=node.args[0], ops=[ast.NotEq()], comparators=[node.args[1]]
200
+ )
201
+ if func == "Lt":
202
+ return ast.Compare(
203
+ left=node.args[0], ops=[ast.Lt()], comparators=[node.args[1]]
204
+ )
205
+ if func == "Le":
206
+ return ast.Compare(
207
+ left=node.args[0], ops=[ast.LtE()], comparators=[node.args[1]]
208
+ )
209
+ if func == "Gt":
210
+ return ast.Compare(
211
+ left=node.args[0], ops=[ast.Gt()], comparators=[node.args[1]]
212
+ )
213
+ if func == "GtE":
214
+ return ast.Compare(
215
+ left=node.args[0], ops=[ast.GtE()], comparators=[node.args[1]]
216
+ )
217
+
218
+ if func == LShift.__name__:
219
+ return ast.BinOp(left=node.args[0], op=ast.LShift(), right=node.args[1])
220
+ if func == RShift.__name__:
221
+ return ast.BinOp(left=node.args[0], op=ast.RShift(), right=node.args[1])
222
+ if func == BitwiseOr.__name__:
223
+ return ast.BinOp(left=node.args[0], op=ast.BitOr(), right=node.args[1])
224
+ if func == BitwiseXor.__name__:
225
+ return ast.BinOp(left=node.args[0], op=ast.BitXor(), right=node.args[1])
226
+ if func == BitwiseAnd.__name__:
227
+ return ast.BinOp(left=node.args[0], op=ast.BitAnd(), right=node.args[1])
228
+
229
+ return node
230
+
231
+ def visit_UnaryOp(self, node: ast.UnaryOp) -> Any:
232
+ node = cast(ast.UnaryOp, self.generic_visit(node))
233
+
234
+ if isinstance(node.op, ast.Invert):
235
+ return ast.UnaryOp(op=ast.Not(), operand=node.operand)
236
+
237
+ return node
238
+
239
+ def visit_BinOp(self, node: ast.BinOp) -> Any:
240
+ node = cast(ast.BinOp, self.generic_visit(node))
241
+
242
+ if isinstance(node.op, ast.BitOr):
243
+ return ast.BoolOp(op=ast.Or(), values=[node.left, node.right])
244
+ if isinstance(node.op, ast.BitAnd):
245
+ return ast.BoolOp(op=ast.And(), values=[node.left, node.right])
246
+
247
+ return node
248
+
249
+
250
+ def _get_numeric_type(
251
+ expr_val: QmodAnnotatedExpression,
252
+ expr_type: QmodType,
253
+ simplified_expr: str,
254
+ masks: dict[str, QmodExprNodeId],
255
+ machine_precision: int,
256
+ ) -> Optional[QuantumNumeric]:
257
+ if isinstance(expr_type, QuantumBit):
258
+ return QuantumNumeric(
259
+ size=Expression(expr="1"),
260
+ is_signed=Expression(expr="False"),
261
+ fraction_digits=Expression(expr="0"),
262
+ )
263
+ if not isinstance(expr_type, QuantumNumeric):
264
+ return None
265
+ if expr_type.is_evaluated:
266
+ return expr_type
267
+ var_types = {
268
+ var_name: expr_val.get_type(node_id) for var_name, node_id in masks.items()
269
+ }
270
+ if not all(
271
+ isinstance(var_type, QuantumBit)
272
+ or (isinstance(var_type, QuantumNumeric) and var_type.is_instantiated)
273
+ for var_type in var_types.values()
274
+ ):
275
+ return None
276
+ return compute_arithmetic_result_type(
277
+ simplified_expr, cast(dict[str, QuantumType], var_types), machine_precision
278
+ )
279
+
280
+
281
+ def simplify_qmod_expression(
282
+ expr_val: QmodAnnotatedExpression, machine_precision: int
283
+ ) -> tuple[str, Optional[QuantumNumeric]]:
284
+ if expr_val.has_value(expr_val.root):
285
+ raise ClassiqInternalExpansionError(
286
+ "This expression is a constant value. No need for simplification"
287
+ )
288
+ var_mask_transformer = _VarMaskTransformer(expr_val)
289
+ masks = var_mask_transformer.masks
290
+ mask_expr = var_mask_transformer.visit(expr_val.root)
291
+ sympy_expr = _SympyCompatibilityTransformer().visit(mask_expr)
292
+ simplified_expr = str(
293
+ sympy.sympify(ast.unparse(sympy_expr), locals=_SYMPY_WRAPPERS)
294
+ )
295
+ restored_expr = _InverseSympyCompatibilityTransformer().visit(
296
+ ast.parse(simplified_expr, mode="eval")
297
+ )
298
+ expr_type = _get_numeric_type(
299
+ expr_val,
300
+ expr_val.get_type(expr_val.root),
301
+ ast.unparse(restored_expr),
302
+ masks,
303
+ machine_precision,
304
+ )
305
+ restored_expr = _InverseVarMaskTransformer(
306
+ expr_val, var_mask_transformer.masks
307
+ ).visit(restored_expr)
308
+ return ast.unparse(restored_expr), expr_type
File without changes
@@ -0,0 +1,112 @@
1
+ import ast
2
+ from collections.abc import Mapping
3
+
4
+ from classiq.interface.exceptions import (
5
+ ClassiqExpansionError,
6
+ ClassiqInternalExpansionError,
7
+ )
8
+ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_instance import (
9
+ QmodStructInstance,
10
+ )
11
+ from classiq.interface.generator.functions.classical_type import (
12
+ Bool,
13
+ ClassicalArray,
14
+ ClassicalTuple,
15
+ Integer,
16
+ )
17
+ from classiq.interface.generator.functions.type_name import TypeName
18
+ from classiq.interface.model.handle_binding import FieldHandleBinding
19
+ from classiq.interface.model.quantum_type import (
20
+ QuantumBitvector,
21
+ QuantumNumeric,
22
+ QuantumType,
23
+ )
24
+
25
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
26
+ from classiq.evaluators.qmod_node_evaluators.utils import QmodType, get_qmod_type_name
27
+
28
+
29
+ def _eval_type_attribute(
30
+ expr_val: QmodAnnotatedExpression, node: ast.Attribute
31
+ ) -> None:
32
+ subject = node.value
33
+ attr = node.attr
34
+
35
+ subject_type = expr_val.get_type(subject)
36
+ if isinstance(subject_type, QuantumType) and attr == "size":
37
+ expr_val.set_type(node, Integer())
38
+ if subject_type.has_size_in_bits:
39
+ expr_val.set_value(node, subject_type.size_in_bits)
40
+ else:
41
+ expr_val.set_quantum_type_attr(node, subject, attr)
42
+ return
43
+ if isinstance(subject_type, (ClassicalArray, QuantumBitvector)) and attr == "len":
44
+ expr_val.set_type(node, Integer())
45
+ if subject_type.has_length:
46
+ expr_val.set_value(node, subject_type.length_value)
47
+ elif isinstance(subject_type, QuantumType):
48
+ expr_val.set_quantum_type_attr(node, subject, attr)
49
+ return
50
+ if isinstance(subject_type, ClassicalTuple) and attr == "len":
51
+ expr_val.set_type(node, Integer())
52
+ expr_val.set_value(node, len(subject_type.element_types))
53
+ return
54
+ if isinstance(subject_type, QuantumNumeric):
55
+ if attr == "is_signed":
56
+ expr_val.set_type(node, Bool())
57
+ if subject_type.has_sign:
58
+ expr_val.set_value(node, subject_type.sign_value)
59
+ elif subject_type.has_size_in_bits:
60
+ expr_val.set_value(node, False)
61
+ else:
62
+ expr_val.set_quantum_type_attr(node, subject, attr)
63
+ return
64
+ if attr == "fraction_digits":
65
+ expr_val.set_type(node, Integer())
66
+ if subject_type.has_fraction_digits:
67
+ expr_val.set_value(node, subject_type.fraction_digits_value)
68
+ elif subject_type.has_size_in_bits:
69
+ expr_val.set_value(node, 0)
70
+ else:
71
+ expr_val.set_quantum_type_attr(node, subject, attr)
72
+ return
73
+ raise ClassiqExpansionError(
74
+ f"{get_qmod_type_name(subject_type)} has no attribute {attr!r}"
75
+ )
76
+
77
+
78
+ def eval_attribute(expr_val: QmodAnnotatedExpression, node: ast.Attribute) -> None:
79
+ subject = node.value
80
+ attr = node.attr
81
+
82
+ subject_type = expr_val.get_type(subject)
83
+ if not isinstance(subject_type, TypeName) or (
84
+ subject_type.has_fields and attr == "size"
85
+ ):
86
+ _eval_type_attribute(expr_val, node)
87
+ return
88
+ subject_fields: Mapping[str, QmodType]
89
+ if subject_type.has_classical_struct_decl:
90
+ subject_fields = subject_type.classical_struct_decl.variables
91
+ elif subject_type.has_fields:
92
+ subject_fields = subject_type.fields
93
+ else:
94
+ raise ClassiqInternalExpansionError
95
+ if attr not in subject_fields:
96
+ raise ClassiqExpansionError(
97
+ f"{get_qmod_type_name(subject_type)} has no field {attr!r}"
98
+ )
99
+ expr_val.set_type(node, subject_fields[attr])
100
+
101
+ if expr_val.has_value(subject):
102
+ subject_value = expr_val.get_value(subject)
103
+ if (
104
+ not isinstance(subject_value, QmodStructInstance)
105
+ or attr not in subject_value.fields
106
+ ):
107
+ raise ClassiqInternalExpansionError
108
+ expr_val.set_value(node, subject_value.fields[attr])
109
+ elif expr_val.has_var(subject):
110
+ subject_var = expr_val.get_var(subject)
111
+ expr_val.set_var(node, FieldHandleBinding(base_handle=subject_var, field=attr))
112
+ expr_val.remove_var(subject)
@@ -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)