classiq 0.40.0__py3-none-any.whl → 0.41.1__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/__init__.py +4 -2
- classiq/_internals/api_wrapper.py +3 -21
- classiq/applications/chemistry/chemistry_model_constructor.py +86 -100
- classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +6 -24
- classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +33 -45
- classiq/applications/combinatorial_optimization/__init__.py +2 -0
- classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +29 -26
- classiq/applications/finance/finance_model_constructor.py +23 -26
- classiq/applications/grover/grover_model_constructor.py +37 -38
- classiq/applications/qsvm/qsvm.py +1 -2
- classiq/applications/qsvm/qsvm_model_constructor.py +15 -16
- classiq/execution/__init__.py +4 -0
- classiq/execution/execution_session.py +151 -0
- classiq/execution/qnn.py +80 -0
- classiq/executor.py +2 -109
- classiq/interface/_version.py +1 -1
- classiq/interface/analyzer/analysis_params.py +11 -0
- classiq/interface/applications/qsvm.py +0 -8
- classiq/interface/ast_node.py +12 -2
- classiq/interface/backend/backend_preferences.py +25 -1
- classiq/interface/backend/quantum_backend_providers.py +4 -4
- classiq/interface/executor/execution_preferences.py +4 -59
- classiq/interface/executor/execution_result.py +22 -1
- classiq/interface/generator/arith/arithmetic_expression_validator.py +0 -2
- classiq/interface/generator/arith/binary_ops.py +88 -25
- classiq/interface/generator/arith/unary_ops.py +28 -19
- classiq/interface/generator/expressions/atomic_expression_functions.py +6 -2
- classiq/interface/generator/expressions/enums/__init__.py +10 -0
- classiq/interface/generator/expressions/enums/classical_enum.py +5 -1
- classiq/interface/generator/expressions/expression.py +9 -2
- classiq/interface/generator/expressions/qmod_qarray_proxy.py +7 -0
- classiq/interface/generator/expressions/qmod_qscalar_proxy.py +0 -1
- classiq/interface/generator/expressions/sympy_supported_expressions.py +10 -1
- classiq/interface/generator/functions/builtins/internal_operators.py +7 -62
- classiq/interface/generator/functions/builtins/open_lib_functions.py +810 -2
- classiq/interface/generator/functions/classical_type.py +1 -3
- classiq/interface/generator/synthesis_metadata/synthesis_duration.py +0 -4
- classiq/interface/model/bind_operation.py +3 -1
- classiq/interface/model/call_synthesis_data.py +2 -13
- classiq/interface/model/classical_if.py +3 -1
- classiq/interface/model/classical_parameter_declaration.py +13 -0
- classiq/interface/model/control.py +3 -101
- classiq/interface/model/inplace_binary_operation.py +3 -1
- classiq/interface/model/invert.py +3 -1
- classiq/interface/model/port_declaration.py +8 -1
- classiq/interface/model/power.py +3 -1
- classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +4 -2
- classiq/interface/model/quantum_expressions/arithmetic_operation.py +3 -1
- classiq/interface/model/quantum_expressions/quantum_expression.py +11 -1
- classiq/interface/model/quantum_function_call.py +4 -10
- classiq/interface/model/quantum_function_declaration.py +26 -4
- classiq/interface/model/quantum_lambda_function.py +1 -20
- classiq/interface/model/quantum_statement.py +9 -2
- classiq/interface/model/repeat.py +3 -1
- classiq/interface/model/statement_block.py +19 -13
- classiq/interface/model/validations/handles_validator.py +8 -2
- classiq/interface/model/variable_declaration_statement.py +3 -1
- classiq/interface/model/within_apply_operation.py +3 -1
- classiq/interface/server/routes.py +0 -5
- classiq/qmod/__init__.py +1 -2
- classiq/qmod/builtins/classical_execution_primitives.py +22 -2
- classiq/qmod/builtins/functions.py +51 -1
- classiq/qmod/builtins/operations.py +6 -36
- classiq/qmod/declaration_inferrer.py +8 -15
- classiq/qmod/native/__init__.py +9 -0
- classiq/qmod/native/expression_to_qmod.py +12 -9
- classiq/qmod/native/pretty_printer.py +4 -4
- classiq/qmod/pretty_print/__init__.py +9 -0
- classiq/qmod/pretty_print/expression_to_python.py +221 -0
- classiq/qmod/pretty_print/pretty_printer.py +421 -0
- classiq/qmod/qmod_parameter.py +7 -4
- classiq/qmod/quantum_callable.py +2 -1
- classiq/qmod/quantum_expandable.py +4 -3
- classiq/qmod/quantum_function.py +4 -16
- classiq/qmod/symbolic.py +1 -6
- classiq/synthesis.py +15 -16
- {classiq-0.40.0.dist-info → classiq-0.41.1.dist-info}/METADATA +5 -4
- {classiq-0.40.0.dist-info → classiq-0.41.1.dist-info}/RECORD +79 -76
- classiq/interface/model/common_model_types.py +0 -23
- classiq/interface/model/quantum_expressions/control_state.py +0 -38
- {classiq-0.40.0.dist-info → classiq-0.41.1.dist-info}/WHEEL +0 -0
classiq/qmod/__init__.py
CHANGED
@@ -5,7 +5,7 @@ from .cfunc import cfunc
|
|
5
5
|
from .expression_query import get_expression_numeric_attributes
|
6
6
|
from .qfunc import qfunc
|
7
7
|
from .qmod_constant import QConstant
|
8
|
-
from .qmod_parameter import Array, CArray, CBool, CInt, CReal
|
8
|
+
from .qmod_parameter import Array, CArray, CBool, CInt, CReal
|
9
9
|
from .qmod_struct import struct
|
10
10
|
from .qmod_variable import Input, Output, QArray, QBit, QNum
|
11
11
|
from .quantum_callable import QCallable, QCallableList
|
@@ -26,7 +26,6 @@ __all__ = [
|
|
26
26
|
"QCallable",
|
27
27
|
"QCallableList",
|
28
28
|
"QConstant",
|
29
|
-
"QParam",
|
30
29
|
"struct",
|
31
30
|
"qfunc",
|
32
31
|
"cfunc",
|
@@ -2,7 +2,12 @@ from typing import Dict, List, Optional, Union
|
|
2
2
|
|
3
3
|
from classiq.interface.executor.execution_preferences import QaeWithQpeEstimationMethod
|
4
4
|
from classiq.interface.executor.iqae_result import IQAEResult
|
5
|
-
from classiq.interface.executor.result import
|
5
|
+
from classiq.interface.executor.result import (
|
6
|
+
EstimationResult,
|
7
|
+
EstimationResults,
|
8
|
+
ExecutionDetails,
|
9
|
+
MultipleExecutionDetails,
|
10
|
+
)
|
6
11
|
from classiq.interface.executor.vqe_result import VQESolverResult
|
7
12
|
from classiq.interface.generator.expressions.enums import Optimizer
|
8
13
|
from classiq.interface.generator.functions.qmod_python_interface import QmodPyStruct
|
@@ -31,16 +36,29 @@ def sample( # type: ignore[return]
|
|
31
36
|
_raise_error("sample")
|
32
37
|
|
33
38
|
|
39
|
+
def batch_sample( # type: ignore[return]
|
40
|
+
batch_execution_params: List[ExecutionParams],
|
41
|
+
) -> MultipleExecutionDetails:
|
42
|
+
_raise_error("batch_sample")
|
43
|
+
|
44
|
+
|
34
45
|
def estimate( # type: ignore[return]
|
35
46
|
hamiltonian: List[QmodPyStruct], execution_params: Optional[ExecutionParams] = None
|
36
47
|
) -> EstimationResult:
|
37
48
|
_raise_error("estimate")
|
38
49
|
|
39
50
|
|
51
|
+
def batch_estimate( # type: ignore[return]
|
52
|
+
hamiltonian: List[QmodPyStruct],
|
53
|
+
batch_execution_params: List[ExecutionParams],
|
54
|
+
) -> EstimationResults:
|
55
|
+
_raise_error("batch_estimate")
|
56
|
+
|
57
|
+
|
40
58
|
def vqe( # type: ignore[return]
|
41
59
|
hamiltonian: List[QmodPyStruct],
|
42
60
|
maximize: bool,
|
43
|
-
initial_point: List[
|
61
|
+
initial_point: List[float],
|
44
62
|
optimizer: Optimizer,
|
45
63
|
max_iteration: int,
|
46
64
|
tolerance: float,
|
@@ -87,7 +105,9 @@ __all__ = [
|
|
87
105
|
"ExecutionParams",
|
88
106
|
"save",
|
89
107
|
"sample",
|
108
|
+
"batch_sample",
|
90
109
|
"estimate",
|
110
|
+
"batch_estimate",
|
91
111
|
"vqe",
|
92
112
|
"qae_with_qpe_result_post_processing",
|
93
113
|
"qsvm_full_run",
|
@@ -611,7 +611,7 @@ def _check_msb(
|
|
611
611
|
@qfunc(external=True)
|
612
612
|
def _ctrl_x(
|
613
613
|
ref: CInt,
|
614
|
-
ctrl:
|
614
|
+
ctrl: QNum,
|
615
615
|
aux: QArray[QBit],
|
616
616
|
) -> None:
|
617
617
|
pass
|
@@ -678,6 +678,52 @@ def modular_exp(
|
|
678
678
|
pass
|
679
679
|
|
680
680
|
|
681
|
+
@qfunc(external=True)
|
682
|
+
def qsvt_step(
|
683
|
+
phase_seq: CArray[CReal],
|
684
|
+
index: CInt,
|
685
|
+
proj_cnot_1: QCallable[QArray[QBit], QArray[QBit]],
|
686
|
+
proj_cnot_2: QCallable[QArray[QBit], QArray[QBit]],
|
687
|
+
u: QCallable[QArray[QBit]],
|
688
|
+
qvar: QArray[QBit],
|
689
|
+
aux: QArray[QBit],
|
690
|
+
) -> None:
|
691
|
+
pass
|
692
|
+
|
693
|
+
|
694
|
+
@qfunc(external=True)
|
695
|
+
def qsvt(
|
696
|
+
phase_seq: CArray[CReal],
|
697
|
+
proj_cnot_1: QCallable[QArray[QBit], QArray[QBit]],
|
698
|
+
proj_cnot_2: QCallable[QArray[QBit], QArray[QBit]],
|
699
|
+
u: QCallable[QArray[QBit]],
|
700
|
+
qvar: QArray[QBit],
|
701
|
+
aux: QArray[QBit],
|
702
|
+
) -> None:
|
703
|
+
pass
|
704
|
+
|
705
|
+
|
706
|
+
@qfunc(external=True)
|
707
|
+
def projector_controlled_phase(
|
708
|
+
phase: CReal,
|
709
|
+
proj_cnot: QCallable[QArray[QBit], QArray[QBit]],
|
710
|
+
qvar: QArray[QBit],
|
711
|
+
aux: QArray[QBit],
|
712
|
+
) -> None:
|
713
|
+
pass
|
714
|
+
|
715
|
+
|
716
|
+
@qfunc(external=True)
|
717
|
+
def qsvt_inversion(
|
718
|
+
phase_seq: CArray[CReal],
|
719
|
+
block_encoding_cnot: QCallable[QArray[QBit], QArray[QBit]],
|
720
|
+
u: QCallable[QArray[QBit]],
|
721
|
+
qvar: QArray[QBit],
|
722
|
+
aux: QArray[QBit],
|
723
|
+
) -> None:
|
724
|
+
pass
|
725
|
+
|
726
|
+
|
681
727
|
@qfunc(external=True)
|
682
728
|
def allocate_num(
|
683
729
|
num_qubits: CInt,
|
@@ -883,6 +929,10 @@ __all__ = [
|
|
883
929
|
"multiswap",
|
884
930
|
"inplace_c_modular_multiply",
|
885
931
|
"modular_exp",
|
932
|
+
"qsvt_step",
|
933
|
+
"qsvt",
|
934
|
+
"projector_controlled_phase",
|
935
|
+
"qsvt_inversion",
|
886
936
|
"allocate_num",
|
887
937
|
"qaoa_mixer_layer",
|
888
938
|
"qaoa_cost_layer",
|
@@ -1,12 +1,11 @@
|
|
1
1
|
import inspect
|
2
2
|
import sys
|
3
|
-
import warnings
|
4
3
|
from types import FrameType
|
5
4
|
from typing import Any, Callable, Final, List, Mapping, Union, overload
|
6
5
|
|
7
6
|
from classiq.interface.generator.expressions.expression import Expression
|
8
7
|
from classiq.interface.generator.functions.builtins.internal_operators import (
|
9
|
-
|
8
|
+
REPEAT_OPERATOR_NAME,
|
10
9
|
)
|
11
10
|
from classiq.interface.model.bind_operation import BindOperation
|
12
11
|
from classiq.interface.model.classical_if import ClassicalIf
|
@@ -26,6 +25,7 @@ from classiq.interface.model.repeat import Repeat
|
|
26
25
|
from classiq.interface.model.statement_block import StatementBlock
|
27
26
|
from classiq.interface.model.within_apply_operation import WithinApply
|
28
27
|
|
28
|
+
from classiq import Integer
|
29
29
|
from classiq.exceptions import ClassiqValueError
|
30
30
|
from classiq.qmod.qmod_variable import Input, Output, QArray, QBit, QNum, QVar
|
31
31
|
from classiq.qmod.quantum_callable import QCallable
|
@@ -91,27 +91,6 @@ def control(
|
|
91
91
|
ctrl: Union[SymbolicExpr, QBit, QArray[QBit]],
|
92
92
|
operand: Union[QCallable, Callable[[], None]],
|
93
93
|
) -> None:
|
94
|
-
if isinstance(operand, (SymbolicExpr, QVar)) and isinstance(
|
95
|
-
control, (QCallable, Callable)
|
96
|
-
): # type:ignore[unreachable]
|
97
|
-
warnings.warn( # type:ignore[unreachable]
|
98
|
-
"The `control` syntax has changed. Switch the `ctrl` and "
|
99
|
-
"`operand` arguments. The old syntax will not be supported in the future.",
|
100
|
-
category=DeprecationWarning,
|
101
|
-
stacklevel=2,
|
102
|
-
)
|
103
|
-
ctrl, operand = operand, ctrl
|
104
|
-
if isinstance(ctrl, QNum):
|
105
|
-
warnings.warn(
|
106
|
-
"The `control` semantics has changed. Applying `control` to "
|
107
|
-
"a `QNum` without comparing it to an integer will not be supported in the "
|
108
|
-
"future.\nTips:\n 1. Use a `bind` statement to cast `ctrl` into a "
|
109
|
-
"QArray.\n 2.`control(n, ...)` is equivalent to "
|
110
|
-
"`ctrl(n == 2 ** n.size - 1, ...)` if n>=0 or to `ctrl(n == -1, ...)` if "
|
111
|
-
"n<0.",
|
112
|
-
category=DeprecationWarning,
|
113
|
-
stacklevel=2,
|
114
|
-
)
|
115
94
|
_validate_operand(operand)
|
116
95
|
assert QCallable.CURRENT_EXPANDABLE is not None
|
117
96
|
source_ref = get_source_ref(sys._getframe(1))
|
@@ -124,18 +103,6 @@ def control(
|
|
124
103
|
)
|
125
104
|
|
126
105
|
|
127
|
-
def quantum_if(
|
128
|
-
ctrl: SymbolicExpr, operand: Union[QCallable, Callable[[], None]]
|
129
|
-
) -> None:
|
130
|
-
warnings.warn(
|
131
|
-
"`quantum_if` is no longer supported, use `control` instead (with the "
|
132
|
-
"same arguments). `quantum_if` will be removed in a future release.",
|
133
|
-
category=DeprecationWarning,
|
134
|
-
stacklevel=2,
|
135
|
-
)
|
136
|
-
control(ctrl, operand)
|
137
|
-
|
138
|
-
|
139
106
|
def inplace_add(
|
140
107
|
value: QNum,
|
141
108
|
target: QNum,
|
@@ -190,7 +157,10 @@ def repeat(count: Union[SymbolicExpr, int], iteration: Callable[[int], None]) ->
|
|
190
157
|
assert QCallable.CURRENT_EXPANDABLE is not None
|
191
158
|
source_ref = get_source_ref(sys._getframe(1))
|
192
159
|
iteration_operand = prepare_arg(
|
193
|
-
|
160
|
+
QuantumOperandDeclaration(
|
161
|
+
name=REPEAT_OPERATOR_NAME, param_decls={"index": Integer()}
|
162
|
+
),
|
163
|
+
iteration,
|
194
164
|
)
|
195
165
|
QCallable.CURRENT_EXPANDABLE.append_statement_to_body(
|
196
166
|
Repeat(
|
@@ -27,7 +27,7 @@ from classiq.interface.model.quantum_function_declaration import (
|
|
27
27
|
from classiq import StructDeclaration
|
28
28
|
from classiq.exceptions import ClassiqValueError
|
29
29
|
from classiq.qmod.model_state_container import ModelStateContainer
|
30
|
-
from classiq.qmod.qmod_parameter import CArray, CBool, CInt, CParam, CReal
|
30
|
+
from classiq.qmod.qmod_parameter import CArray, CBool, CInt, CParam, CReal
|
31
31
|
from classiq.qmod.qmod_variable import QVar, get_type_hint_expr
|
32
32
|
from classiq.qmod.quantum_callable import QCallable, QCallableList
|
33
33
|
from classiq.qmod.utilities import unmangle_keyword, version_portable_get_args
|
@@ -87,18 +87,6 @@ def _add_qmod_struct(
|
|
87
87
|
)
|
88
88
|
|
89
89
|
|
90
|
-
def _extract_param_decl(
|
91
|
-
name: str, py_type: Any, *, qmodule: ModelStateContainer
|
92
|
-
) -> ClassicalParameterDeclaration:
|
93
|
-
if get_origin(py_type) is QParam:
|
94
|
-
if len(get_args(py_type)) != 1:
|
95
|
-
raise ClassiqValueError("QParam takes exactly one generic argument")
|
96
|
-
py_type = get_args(py_type)[0]
|
97
|
-
return ClassicalParameterDeclaration(
|
98
|
-
name=name, classical_type=python_type_to_qmod(py_type, qmodule=qmodule)
|
99
|
-
)
|
100
|
-
|
101
|
-
|
102
90
|
def _extract_port_decl(name: str, py_type: Any) -> PortDeclaration:
|
103
91
|
# FIXME: CAD-13409
|
104
92
|
qtype: Type[QVar] = QVar.from_type_hint(py_type) # type:ignore[assignment]
|
@@ -139,9 +127,14 @@ def _extract_positional_args(
|
|
139
127
|
and issubclass(py_type, CParam)
|
140
128
|
or inspect.isclass(py_type)
|
141
129
|
and issubclass(py_type, CStructBase)
|
142
|
-
or get_origin(py_type)
|
130
|
+
or get_origin(py_type) == CArray
|
143
131
|
):
|
144
|
-
result.append(
|
132
|
+
result.append(
|
133
|
+
ClassicalParameterDeclaration(
|
134
|
+
name=name,
|
135
|
+
classical_type=python_type_to_qmod(py_type, qmodule=qmodule),
|
136
|
+
)
|
137
|
+
)
|
145
138
|
elif QVar.from_type_hint(py_type) is not None:
|
146
139
|
result.append(_extract_port_decl(name, py_type))
|
147
140
|
else:
|
classiq/qmod/native/__init__.py
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
import ast
|
2
2
|
import re
|
3
3
|
from dataclasses import dataclass
|
4
|
-
from typing import Callable, Dict, List, Mapping, Type
|
4
|
+
from typing import Callable, Dict, List, Mapping, Optional, Type
|
5
5
|
|
6
6
|
import numpy as np
|
7
7
|
|
8
|
-
from classiq.interface.generator.functions.classical_type import CLASSICAL_ATTRIBUTES
|
9
|
-
|
10
8
|
from classiq.qmod.utilities import DEFAULT_DECIMAL_PRECISION
|
11
9
|
|
12
10
|
IDENTIFIER = re.compile(r"[a-zA-Z_]\w*")
|
@@ -44,7 +42,7 @@ LIST_FORMAT_CHAR_LIMIT = 20
|
|
44
42
|
@dataclass
|
45
43
|
class ASTToQMODCode:
|
46
44
|
level: int
|
47
|
-
decimal_precision: int
|
45
|
+
decimal_precision: Optional[int]
|
48
46
|
indent_seq: str = " "
|
49
47
|
|
50
48
|
@property
|
@@ -59,13 +57,16 @@ class ASTToQMODCode:
|
|
59
57
|
return self.indent.join(self.ast_to_code(child) for child in node.body)
|
60
58
|
elif isinstance(node, ast.Attribute):
|
61
59
|
# Enum attribute access
|
62
|
-
if not isinstance(node.attr, str):
|
60
|
+
if not isinstance(node.value, ast.Name) or not isinstance(node.attr, str):
|
61
|
+
raise AssertionError("Error parsing enum attribute access")
|
62
|
+
if not (IDENTIFIER.match(node.value.id) and IDENTIFIER.match(node.attr)):
|
63
63
|
raise AssertionError("Error parsing enum attribute access")
|
64
|
-
|
65
|
-
return f"{self.ast_to_code(node.value)!s}{access_operator}{node.attr!s}"
|
64
|
+
return f"{node.value.id!s}::{node.attr!s}"
|
66
65
|
elif isinstance(node, ast.Name):
|
67
66
|
return node.id
|
68
67
|
elif isinstance(node, ast.Num):
|
68
|
+
if self.decimal_precision is None:
|
69
|
+
return str(node.n)
|
69
70
|
return str(np.round(node.n, self.decimal_precision))
|
70
71
|
elif isinstance(node, ast.Str):
|
71
72
|
return repr(node.s)
|
@@ -123,7 +124,7 @@ class ASTToQMODCode:
|
|
123
124
|
keywords = node.keywords
|
124
125
|
initializer_list = self.indent_items(
|
125
126
|
lambda: [
|
126
|
-
f"{keyword.arg}
|
127
|
+
f"{keyword.arg}={self._cleaned_ast_to_code(keyword.value)}"
|
127
128
|
for keyword in keywords
|
128
129
|
if keyword.arg is not None
|
129
130
|
]
|
@@ -183,7 +184,9 @@ def _remove_redundant_parentheses(expr: str) -> str:
|
|
183
184
|
|
184
185
|
|
185
186
|
def transform_expression(
|
186
|
-
expr: str,
|
187
|
+
expr: str,
|
188
|
+
level: int = 0,
|
189
|
+
decimal_precision: Optional[int] = DEFAULT_DECIMAL_PRECISION,
|
187
190
|
) -> str:
|
188
191
|
return ASTToQMODCode(level=level, decimal_precision=decimal_precision).visit(
|
189
192
|
ast.parse(expr)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Dict, List
|
1
|
+
from typing import Dict, List, Optional
|
2
2
|
|
3
3
|
from classiq.interface.generator.constant import Constant
|
4
4
|
from classiq.interface.generator.expressions.expression import Expression
|
@@ -63,7 +63,9 @@ from classiq.qmod.utilities import DEFAULT_DECIMAL_PRECISION
|
|
63
63
|
|
64
64
|
|
65
65
|
class DSLPrettyPrinter(Visitor):
|
66
|
-
def __init__(
|
66
|
+
def __init__(
|
67
|
+
self, decimal_precision: Optional[int] = DEFAULT_DECIMAL_PRECISION
|
68
|
+
) -> None:
|
67
69
|
self._level = 0
|
68
70
|
self._decimal_precision = decimal_precision
|
69
71
|
|
@@ -136,8 +138,6 @@ class DSLPrettyPrinter(Visitor):
|
|
136
138
|
|
137
139
|
def visit_QuantumBitvector(self, qtype: QuantumBitvector) -> str:
|
138
140
|
if qtype.length is not None:
|
139
|
-
if qtype.length.is_evaluated() and qtype.length.to_int_value() == 1:
|
140
|
-
return "qbit"
|
141
141
|
return f"qbit[{self.visit(qtype.length)}]"
|
142
142
|
return "qbit[]"
|
143
143
|
|
@@ -0,0 +1,221 @@
|
|
1
|
+
import ast
|
2
|
+
import re
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import Callable, Dict, List, Mapping, Type
|
5
|
+
|
6
|
+
import numpy as np
|
7
|
+
|
8
|
+
import classiq
|
9
|
+
from classiq.qmod.utilities import DEFAULT_DECIMAL_PRECISION
|
10
|
+
|
11
|
+
IDENTIFIER = re.compile(r"[a-zA-Z_]\w*")
|
12
|
+
BINARY_OPS: Mapping[Type[ast.operator], str] = {
|
13
|
+
ast.Add: "+",
|
14
|
+
ast.Sub: "-",
|
15
|
+
ast.Mult: "*",
|
16
|
+
ast.Div: "/",
|
17
|
+
ast.Mod: "%",
|
18
|
+
ast.Pow: "**",
|
19
|
+
ast.BitAnd: "&",
|
20
|
+
ast.BitOr: "|",
|
21
|
+
ast.BitXor: "^",
|
22
|
+
ast.LShift: "<<",
|
23
|
+
ast.RShift: ">>",
|
24
|
+
}
|
25
|
+
BOOL_OPS: Mapping[Type[ast.boolop], str] = {ast.And: "and", ast.Or: "or"}
|
26
|
+
UNARY_OPS: Mapping[Type[ast.unaryop], str] = {
|
27
|
+
ast.UAdd: "+",
|
28
|
+
ast.USub: "-",
|
29
|
+
ast.Invert: "~",
|
30
|
+
ast.Not: "not",
|
31
|
+
}
|
32
|
+
COMPARE_OPS: Mapping[Type[ast.cmpop], str] = {
|
33
|
+
ast.Eq: "==",
|
34
|
+
ast.NotEq: "!=",
|
35
|
+
ast.Lt: "<",
|
36
|
+
ast.LtE: "<=",
|
37
|
+
ast.Gt: ">",
|
38
|
+
ast.GtE: ">=",
|
39
|
+
}
|
40
|
+
LIST_FORMAT_CHAR_LIMIT = 20
|
41
|
+
|
42
|
+
|
43
|
+
@dataclass
|
44
|
+
class ASTToQMODCode(ast.NodeVisitor):
|
45
|
+
level: int
|
46
|
+
imports: Dict[str, int]
|
47
|
+
symbolic_imports: Dict[str, int]
|
48
|
+
decimal_precision: int
|
49
|
+
indent_seq: str = " "
|
50
|
+
|
51
|
+
@property
|
52
|
+
def indent(self) -> str:
|
53
|
+
return self.level * self.indent_seq
|
54
|
+
|
55
|
+
def _handle_imports(self, name: str, is_possibly_symbolic: bool = False) -> None:
|
56
|
+
if name in dir(classiq):
|
57
|
+
self.imports[name] = 1
|
58
|
+
if is_possibly_symbolic and name in dir(classiq.qmod.symbolic):
|
59
|
+
self.symbolic_imports[name] = 1
|
60
|
+
|
61
|
+
def visit(self, node: ast.AST) -> str:
|
62
|
+
res = super().visit(node)
|
63
|
+
if not isinstance(res, str):
|
64
|
+
raise AssertionError("Error parsing expression: unsupported AST node.")
|
65
|
+
return res
|
66
|
+
|
67
|
+
def visit_Module(self, node: ast.Module) -> str:
|
68
|
+
return self.indent.join(self.visit(child) for child in node.body)
|
69
|
+
|
70
|
+
def visit_Attribute(self, node: ast.Attribute) -> str:
|
71
|
+
if not isinstance(node.value, ast.Name) or not isinstance(node.attr, str):
|
72
|
+
raise AssertionError("Error parsing enum attribute access")
|
73
|
+
if not (IDENTIFIER.match(node.value.id) and IDENTIFIER.match(node.attr)):
|
74
|
+
raise AssertionError("Error parsing enum attribute access")
|
75
|
+
self._handle_imports(node.value.id)
|
76
|
+
return f"{node.value.id!s}.{node.attr!s}"
|
77
|
+
|
78
|
+
def visit_Name(self, node: ast.Name) -> str:
|
79
|
+
self._handle_imports(node.id, True)
|
80
|
+
return node.id
|
81
|
+
|
82
|
+
def visit_Num(self, node: ast.Num) -> str:
|
83
|
+
return str(np.round(node.n, self.decimal_precision))
|
84
|
+
|
85
|
+
def visit_Str(self, node: ast.Str) -> str:
|
86
|
+
return repr(node.s)
|
87
|
+
|
88
|
+
def visit_Constant(self, node: ast.Constant) -> str:
|
89
|
+
return repr(node.value)
|
90
|
+
|
91
|
+
def visit_BinOp(self, node: ast.BinOp) -> str:
|
92
|
+
return "({} {} {})".format(
|
93
|
+
self.visit(node.left),
|
94
|
+
BINARY_OPS[type(node.op)],
|
95
|
+
self.visit(node.right),
|
96
|
+
)
|
97
|
+
|
98
|
+
def visit_UnaryOp(self, node: ast.UnaryOp) -> str:
|
99
|
+
unary_op = UNARY_OPS[type(node.op)]
|
100
|
+
space = " " if unary_op == "not" else ""
|
101
|
+
return f"({unary_op}{space}{self.visit(node.operand)})"
|
102
|
+
|
103
|
+
def visit_BoolOp(self, node: ast.BoolOp) -> str:
|
104
|
+
return "({})".format(
|
105
|
+
(" " + BOOL_OPS[type(node.op)] + " ").join(
|
106
|
+
self.visit(value) for value in node.values
|
107
|
+
)
|
108
|
+
)
|
109
|
+
|
110
|
+
def visit_Compare(self, node: ast.Compare) -> str:
|
111
|
+
if len(node.ops) != 1 or len(node.comparators) != 1:
|
112
|
+
raise AssertionError("Error parsing comparison expression.")
|
113
|
+
return "({} {} {})".format(
|
114
|
+
self.visit(node.left),
|
115
|
+
COMPARE_OPS[type(node.ops[0])],
|
116
|
+
self.visit(node.comparators[0]),
|
117
|
+
)
|
118
|
+
|
119
|
+
def visit_List(self, node: ast.List) -> str:
|
120
|
+
elts = node.elts
|
121
|
+
elements = self.indent_items(lambda: [self.visit(element) for element in elts])
|
122
|
+
return f"[{elements}]"
|
123
|
+
|
124
|
+
def visit_Subscript(self, node: ast.Subscript) -> str:
|
125
|
+
return f"{self.visit(node.value)}[{_remove_redundant_parentheses(self.visit(node.slice))}]"
|
126
|
+
|
127
|
+
def visit_Slice(self, node: ast.Slice) -> str:
|
128
|
+
if node.lower is None or node.upper is None or node.step is not None:
|
129
|
+
raise AssertionError("Error parsing slice expression.")
|
130
|
+
return f"{self.visit(node.lower)}:{self.visit(node.upper)}"
|
131
|
+
|
132
|
+
def visit_Call(self, node: ast.Call) -> str:
|
133
|
+
func = self.visit(node.func)
|
134
|
+
self._handle_imports(func, True)
|
135
|
+
if func == "get_field":
|
136
|
+
if len(node.args) != 2:
|
137
|
+
raise AssertionError("Error parsing struct field access.")
|
138
|
+
field = str(self.visit(node.args[1])).replace("'", "")
|
139
|
+
if not IDENTIFIER.match(field):
|
140
|
+
raise AssertionError("Error parsing struct field access.")
|
141
|
+
return f"{self.visit(node.args[0])}.{field}"
|
142
|
+
elif func == "struct_literal":
|
143
|
+
if len(node.args) != 1 or not isinstance(node.args[0], ast.Name):
|
144
|
+
raise AssertionError("Error parsing struct literal.")
|
145
|
+
keywords = node.keywords
|
146
|
+
initializer_list = self.indent_items(
|
147
|
+
lambda: [
|
148
|
+
f"{keyword.arg} = {self._cleaned_ast_to_code(keyword.value)}"
|
149
|
+
for keyword in keywords
|
150
|
+
if keyword.arg is not None
|
151
|
+
]
|
152
|
+
)
|
153
|
+
return f"{self.visit(node.args[0])}({initializer_list})"
|
154
|
+
else:
|
155
|
+
return "{}({})".format(
|
156
|
+
func, ", ".join(self._cleaned_ast_to_code(arg) for arg in node.args)
|
157
|
+
)
|
158
|
+
|
159
|
+
def visit_Expr(self, node: ast.Expr) -> str:
|
160
|
+
return self._cleaned_ast_to_code(node.value)
|
161
|
+
|
162
|
+
def generic_visit(self, node: ast.AST) -> None:
|
163
|
+
raise AssertionError("Cannot parse node of type: " + type(node).__name__)
|
164
|
+
|
165
|
+
def indent_items(self, items: Callable[[], List[str]]) -> str:
|
166
|
+
should_indent = (
|
167
|
+
len("".join([i.strip() for i in items()])) >= LIST_FORMAT_CHAR_LIMIT
|
168
|
+
)
|
169
|
+
if should_indent:
|
170
|
+
self.level += 1
|
171
|
+
left_ws = "\n" + self.indent
|
172
|
+
inner_ws = ",\n" + self.indent
|
173
|
+
else:
|
174
|
+
left_ws = ""
|
175
|
+
inner_ws = ", "
|
176
|
+
items_ = items()
|
177
|
+
if should_indent:
|
178
|
+
self.level -= 1
|
179
|
+
right_ws = "\n" + self.indent
|
180
|
+
else:
|
181
|
+
right_ws = ""
|
182
|
+
return f"{left_ws}{inner_ws.join(items_)}{right_ws}"
|
183
|
+
|
184
|
+
def _cleaned_ast_to_code(self, node: ast.AST) -> str:
|
185
|
+
return _remove_redundant_parentheses(self.visit(node))
|
186
|
+
|
187
|
+
|
188
|
+
def _remove_redundant_parentheses(expr: str) -> str:
|
189
|
+
if not (expr.startswith("(") and expr.endswith(")")):
|
190
|
+
return expr
|
191
|
+
parentheses_map: Dict[int, int] = dict()
|
192
|
+
stack: List[int] = []
|
193
|
+
for index, char in enumerate(expr):
|
194
|
+
if char == "(":
|
195
|
+
stack.append(index)
|
196
|
+
elif char == ")":
|
197
|
+
parentheses_map[stack.pop()] = index
|
198
|
+
index = 0
|
199
|
+
original_length = len(expr)
|
200
|
+
while (
|
201
|
+
index in parentheses_map
|
202
|
+
and parentheses_map[index] == original_length - index - 1
|
203
|
+
):
|
204
|
+
expr = expr[1:-1]
|
205
|
+
index += 1
|
206
|
+
return expr
|
207
|
+
|
208
|
+
|
209
|
+
def transform_expression(
|
210
|
+
expr: str,
|
211
|
+
imports: Dict[str, int],
|
212
|
+
symbolic_imports: Dict[str, int],
|
213
|
+
level: int = 0,
|
214
|
+
decimal_precision: int = DEFAULT_DECIMAL_PRECISION,
|
215
|
+
) -> str:
|
216
|
+
return ASTToQMODCode(
|
217
|
+
level=level,
|
218
|
+
decimal_precision=decimal_precision,
|
219
|
+
imports=imports,
|
220
|
+
symbolic_imports=symbolic_imports,
|
221
|
+
).visit(ast.parse(expr))
|