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,232 @@
1
+ import ast
2
+ from collections.abc import Mapping, Sequence
3
+ from enum import IntEnum
4
+ from typing import Any, Callable, Optional
5
+
6
+ from classiq.interface.exceptions import (
7
+ ClassiqExpansionError,
8
+ ClassiqInternalExpansionError,
9
+ )
10
+ from classiq.interface.generator.arith.machine_precision import (
11
+ DEFAULT_MACHINE_PRECISION,
12
+ )
13
+ from classiq.interface.generator.functions.classical_function_declaration import (
14
+ ClassicalFunctionDeclaration,
15
+ )
16
+ from classiq.interface.generator.types.enum_declaration import EnumDeclaration
17
+ from classiq.interface.generator.types.struct_declaration import StructDeclaration
18
+ from classiq.interface.helpers.pydantic_model_helpers import nameables_to_dict
19
+
20
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
21
+ from classiq.evaluators.qmod_node_evaluators.attribute_evaluation import eval_attribute
22
+ from classiq.evaluators.qmod_node_evaluators.binary_op_evaluation import eval_binary_op
23
+ from classiq.evaluators.qmod_node_evaluators.bool_op_evaluation import eval_bool_op
24
+ from classiq.evaluators.qmod_node_evaluators.classical_function_evaluation import (
25
+ eval_function,
26
+ eval_symbolic_function,
27
+ try_eval_builtin_function,
28
+ try_eval_sympy_function,
29
+ )
30
+ from classiq.evaluators.qmod_node_evaluators.compare_evaluation import eval_compare
31
+ from classiq.evaluators.qmod_node_evaluators.constant_evaluation import (
32
+ eval_constant,
33
+ eval_enum_member,
34
+ try_eval_sympy_constant,
35
+ )
36
+ from classiq.evaluators.qmod_node_evaluators.list_evaluation import eval_list
37
+ from classiq.evaluators.qmod_node_evaluators.measurement_evaluation import (
38
+ eval_measurement,
39
+ )
40
+ from classiq.evaluators.qmod_node_evaluators.name_evaluation import eval_name
41
+ from classiq.evaluators.qmod_node_evaluators.struct_instantiation_evaluation import (
42
+ eval_struct_instantiation,
43
+ )
44
+ from classiq.evaluators.qmod_node_evaluators.subscript_evaluation import (
45
+ eval_quantum_subscript,
46
+ eval_subscript,
47
+ )
48
+ from classiq.evaluators.qmod_node_evaluators.unary_op_evaluation import eval_unary_op
49
+ from classiq.evaluators.qmod_node_evaluators.utils import is_classical_type
50
+
51
+ _SUPPORTED_NODES = (
52
+ ast.BoolOp,
53
+ ast.BinOp,
54
+ ast.UnaryOp,
55
+ ast.Compare,
56
+ ast.Call,
57
+ ast.Constant,
58
+ ast.Attribute,
59
+ ast.Subscript,
60
+ ast.Name,
61
+ ast.List,
62
+ ast.cmpop,
63
+ ast.operator,
64
+ ast.expr_context,
65
+ ast.keyword,
66
+ ast.unaryop,
67
+ ast.boolop,
68
+ ast.Slice,
69
+ )
70
+
71
+
72
+ class QmodExpressionEvaluator(ast.NodeVisitor):
73
+ def __init__(
74
+ self,
75
+ expr_val: QmodAnnotatedExpression,
76
+ *,
77
+ machine_precision: int = DEFAULT_MACHINE_PRECISION,
78
+ classical_struct_declarations: Optional[Sequence[StructDeclaration]] = None,
79
+ enum_declarations: Optional[Sequence[EnumDeclaration]] = None,
80
+ classical_function_declarations: Optional[
81
+ Sequence[ClassicalFunctionDeclaration]
82
+ ] = None,
83
+ classical_function_callables: Optional[Mapping[str, Callable]] = None,
84
+ scope: Optional[dict[str, Any]] = None,
85
+ ) -> None:
86
+ self._expr_val = expr_val
87
+ self._machine_precision = machine_precision
88
+ self._classical_struct_decls = nameables_to_dict(
89
+ classical_struct_declarations or []
90
+ )
91
+ self._enum_declarations = {decl.name: decl for decl in enum_declarations or []}
92
+ self._enums: dict[str, type[IntEnum]] = {}
93
+ self._classical_function_declarations = nameables_to_dict(
94
+ classical_function_declarations or []
95
+ )
96
+ self._classical_function_callables = classical_function_callables or {}
97
+ self._scope = scope or {}
98
+
99
+ def visit(self, node: ast.AST) -> None:
100
+ if not isinstance(node, _SUPPORTED_NODES):
101
+ raise ClassiqInternalExpansionError(
102
+ f"Syntax error: {type(node).__name__!r} is not supported"
103
+ )
104
+ super().visit(node)
105
+
106
+ def visit_BoolOp(self, node: ast.BoolOp) -> None:
107
+ super().generic_visit(node)
108
+ eval_bool_op(self._expr_val, node)
109
+
110
+ def visit_BinOp(self, node: ast.BinOp) -> None:
111
+ super().generic_visit(node)
112
+ eval_binary_op(self._expr_val, node)
113
+
114
+ def visit_UnaryOp(self, node: ast.UnaryOp) -> None:
115
+ super().generic_visit(node)
116
+ eval_unary_op(self._expr_val, node)
117
+
118
+ def visit_Compare(self, node: ast.Compare) -> None:
119
+ super().generic_visit(node)
120
+ eval_compare(self._expr_val, node)
121
+
122
+ def visit_Call(self, node: ast.Call) -> None:
123
+ for arg in node.args:
124
+ self.visit(arg)
125
+ for kwarg in node.keywords:
126
+ self.visit(kwarg)
127
+
128
+ func = node.func
129
+ if not isinstance(func, ast.Name):
130
+ raise ClassiqExpansionError(
131
+ f"Function {ast.unparse(node.func)!r} is not supported"
132
+ )
133
+ func_name = func.id
134
+
135
+ if func_name == "measure":
136
+ eval_measurement(self._expr_val, node)
137
+ return
138
+
139
+ if func_name in self._classical_struct_decls:
140
+ eval_struct_instantiation(
141
+ self._expr_val, node, self._classical_struct_decls[func_name]
142
+ )
143
+ return
144
+
145
+ if func_name in self._classical_function_callables:
146
+ if func_name not in self._classical_function_declarations:
147
+ raise ClassiqInternalExpansionError
148
+ eval_function(
149
+ self._expr_val,
150
+ node,
151
+ self._classical_function_declarations[func_name],
152
+ self._classical_function_callables[func_name],
153
+ )
154
+ return
155
+
156
+ if func_name in self._classical_function_declarations:
157
+ eval_symbolic_function(
158
+ self._expr_val, node, self._classical_function_declarations[func_name]
159
+ )
160
+ return
161
+
162
+ if try_eval_builtin_function(self._expr_val, node, func_name):
163
+ return
164
+
165
+ if try_eval_sympy_function(self._expr_val, node, func_name):
166
+ return
167
+
168
+ raise ClassiqExpansionError(f"{func.id!r} is undefined")
169
+
170
+ def visit_Constant(self, node: ast.Constant) -> None:
171
+ eval_constant(self._expr_val, node)
172
+
173
+ def visit_Attribute(self, node: ast.Attribute) -> None:
174
+ if (
175
+ isinstance(node.value, ast.Name)
176
+ and (enum_name := node.value.id) in self._enum_declarations
177
+ ):
178
+ if enum_name not in self._enums:
179
+ self._enums[enum_name] = self._enum_declarations[
180
+ enum_name
181
+ ].create_enum()
182
+ eval_enum_member(self._expr_val, node, self._enums[enum_name])
183
+ return
184
+ super().generic_visit(node)
185
+ eval_attribute(self._expr_val, node)
186
+
187
+ def visit_Subscript(self, node: ast.Subscript) -> None:
188
+ super().generic_visit(node)
189
+ if not isinstance(node.slice, ast.Slice) and not is_classical_type(
190
+ self._expr_val.get_type(node.slice)
191
+ ):
192
+ eval_quantum_subscript(self._expr_val, node, self._machine_precision)
193
+ return
194
+ eval_subscript(self._expr_val, node)
195
+
196
+ def visit_Name(self, node: ast.Name) -> None:
197
+ if try_eval_sympy_constant(self._expr_val, node):
198
+ return
199
+
200
+ if node.id not in self._scope:
201
+ raise ClassiqExpansionError(f"Variable {node.id!r} is undefined")
202
+ eval_name(self._expr_val, node, self._scope[node.id])
203
+
204
+ def visit_List(self, node: ast.List) -> None:
205
+ super().generic_visit(node)
206
+ eval_list(self._expr_val, node)
207
+
208
+
209
+ def evaluate_qmod_expression(
210
+ expr: str,
211
+ *,
212
+ machine_precision: int = DEFAULT_MACHINE_PRECISION,
213
+ classical_struct_declarations: Optional[Sequence[StructDeclaration]] = None,
214
+ enum_declarations: Optional[Sequence[EnumDeclaration]] = None,
215
+ classical_function_declarations: Optional[
216
+ Sequence[ClassicalFunctionDeclaration]
217
+ ] = None,
218
+ classical_function_callables: Optional[Mapping[str, Callable]] = None,
219
+ scope: Optional[dict[str, Any]] = None,
220
+ ) -> QmodAnnotatedExpression:
221
+ expr_ast = ast.parse(expr, mode="eval").body
222
+ expr_value = QmodAnnotatedExpression(expr_ast)
223
+ QmodExpressionEvaluator(
224
+ expr_value,
225
+ machine_precision=machine_precision,
226
+ classical_struct_declarations=classical_struct_declarations,
227
+ enum_declarations=enum_declarations,
228
+ classical_function_declarations=classical_function_declarations,
229
+ classical_function_callables=classical_function_callables,
230
+ scope=scope,
231
+ ).visit(expr_value.root)
232
+ return expr_value
@@ -0,0 +1,44 @@
1
+ from typing import Optional
2
+
3
+ from classiq.interface.model.handle_binding import HandleBinding, NestedHandleBinding
4
+
5
+ from classiq.evaluators.qmod_annotated_expression import (
6
+ QmodAnnotatedExpression,
7
+ QmodExprNodeId,
8
+ )
9
+
10
+
11
+ def rename_handles_in_expression(
12
+ expr_val: QmodAnnotatedExpression,
13
+ renaming: dict[HandleBinding, HandleBinding],
14
+ ) -> str:
15
+ if len(renaming) == 0:
16
+ return expr_val.to_qmod_expr()
17
+ all_vars = expr_val.get_classical_vars() | expr_val.get_quantum_vars()
18
+ for node_id, var in all_vars.items():
19
+ renamed_var = _rename_var(renaming, var)
20
+ if renamed_var is not None:
21
+ expr_val.set_var(node_id, renamed_var)
22
+ return expr_val.to_qmod_expr()
23
+
24
+
25
+ def _rename_var(
26
+ renaming: dict[HandleBinding, HandleBinding], var: HandleBinding
27
+ ) -> Optional[HandleBinding]:
28
+ if (renamed_var := renaming.get(var)) is not None:
29
+ return renamed_var
30
+ if not isinstance(var, NestedHandleBinding):
31
+ return None
32
+ renamed_inner = _rename_var(renaming, var.base_handle)
33
+ if renamed_inner is None:
34
+ return None
35
+ return var.model_copy(update=dict(base_handle=renamed_inner))
36
+
37
+
38
+ def rename_nodes_in_expression(
39
+ expr_val: QmodAnnotatedExpression,
40
+ renaming: dict[QmodExprNodeId, HandleBinding],
41
+ ) -> str:
42
+ for node_id, renamed_var in renaming.items():
43
+ expr_val.set_var(node_id, renamed_var)
44
+ return expr_val.to_qmod_expr()
@@ -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)