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,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
|