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.
- classiq/applications/combinatorial_optimization/combinatorial_problem.py +24 -45
- classiq/evaluators/classical_expression.py +32 -15
- classiq/evaluators/qmod_annotated_expression.py +207 -0
- classiq/evaluators/qmod_expression_visitors/__init__.py +0 -0
- classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +134 -0
- classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +232 -0
- classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +44 -0
- classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +308 -0
- classiq/evaluators/qmod_node_evaluators/__init__.py +0 -0
- classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +112 -0
- classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +132 -0
- classiq/evaluators/qmod_node_evaluators/bool_op_evaluation.py +70 -0
- classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +311 -0
- classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +107 -0
- classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +67 -0
- classiq/evaluators/qmod_node_evaluators/list_evaluation.py +107 -0
- classiq/evaluators/qmod_node_evaluators/measurement_evaluation.py +25 -0
- classiq/evaluators/qmod_node_evaluators/name_evaluation.py +50 -0
- classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +66 -0
- classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +225 -0
- classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +58 -0
- classiq/evaluators/qmod_node_evaluators/utils.py +80 -0
- classiq/execution/execution_session.py +53 -6
- classiq/interface/_version.py +1 -1
- classiq/interface/analyzer/analysis_params.py +1 -1
- classiq/interface/analyzer/result.py +1 -1
- classiq/interface/debug_info/debug_info.py +0 -4
- classiq/interface/executor/quantum_code.py +2 -2
- classiq/interface/generator/arith/arithmetic_expression_validator.py +5 -1
- classiq/interface/generator/arith/binary_ops.py +43 -51
- classiq/interface/generator/arith/number_utils.py +3 -2
- classiq/interface/generator/arith/register_user_input.py +15 -0
- classiq/interface/generator/arith/unary_ops.py +32 -28
- classiq/interface/generator/expressions/atomic_expression_functions.py +5 -0
- classiq/interface/generator/expressions/expression_types.py +2 -2
- classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
- classiq/interface/generator/functions/builtins/internal_operators.py +2 -0
- classiq/interface/generator/functions/classical_function_declaration.py +0 -4
- classiq/interface/generator/functions/classical_type.py +0 -32
- classiq/interface/generator/functions/concrete_types.py +20 -0
- classiq/interface/generator/generated_circuit_data.py +7 -10
- classiq/interface/generator/quantum_program.py +6 -1
- classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +29 -0
- classiq/interface/ide/operation_registry.py +45 -0
- classiq/interface/ide/visual_model.py +84 -2
- classiq/interface/model/bounds.py +12 -2
- classiq/interface/model/quantum_expressions/arithmetic_operation.py +7 -4
- classiq/interface/model/quantum_type.py +67 -33
- classiq/interface/model/variable_declaration_statement.py +33 -6
- classiq/model_expansions/arithmetic.py +115 -0
- classiq/model_expansions/arithmetic_compute_result_attrs.py +71 -0
- classiq/model_expansions/atomic_expression_functions_defs.py +10 -6
- classiq/model_expansions/function_builder.py +4 -1
- classiq/model_expansions/generative_functions.py +15 -2
- classiq/model_expansions/interpreters/base_interpreter.py +7 -0
- classiq/model_expansions/interpreters/generative_interpreter.py +18 -1
- classiq/model_expansions/quantum_operations/assignment_result_processor.py +63 -21
- classiq/model_expansions/quantum_operations/bounds.py +7 -1
- classiq/model_expansions/quantum_operations/call_emitter.py +5 -2
- classiq/model_expansions/quantum_operations/classical_var_emitter.py +16 -0
- classiq/model_expansions/quantum_operations/variable_decleration.py +30 -10
- classiq/model_expansions/scope.py +7 -0
- classiq/model_expansions/scope_initialization.py +2 -0
- classiq/model_expansions/sympy_conversion/sympy_to_python.py +1 -1
- classiq/model_expansions/transformers/type_modifier_inference.py +5 -0
- classiq/model_expansions/transformers/var_splitter.py +1 -1
- classiq/model_expansions/visitors/boolean_expression_transformers.py +1 -1
- classiq/open_library/functions/__init__.py +0 -2
- classiq/open_library/functions/qaoa_penalty.py +8 -1
- classiq/open_library/functions/state_preparation.py +1 -32
- classiq/qmod/__init__.py +2 -0
- classiq/qmod/builtins/operations.py +66 -2
- classiq/qmod/classical_variable.py +74 -0
- classiq/qmod/declaration_inferrer.py +5 -3
- classiq/qmod/native/pretty_printer.py +18 -14
- classiq/qmod/pretty_print/pretty_printer.py +34 -15
- classiq/qmod/qfunc.py +2 -19
- classiq/qmod/qmod_variable.py +5 -8
- classiq/qmod/quantum_expandable.py +1 -1
- classiq/qmod/quantum_function.py +42 -2
- classiq/qmod/symbolic_type.py +2 -1
- classiq/qmod/write_qmod.py +3 -1
- {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/METADATA +1 -1
- {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/RECORD +86 -62
- classiq/interface/model/quantum_variable_declaration.py +0 -7
- /classiq/{model_expansions/sympy_conversion/arithmetics.py → evaluators/qmod_expression_visitors/sympy_wrappers.py} +0 -0
- {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)
|