classiq 0.40.0__py3-none-any.whl → 0.41.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. classiq/__init__.py +4 -2
  2. classiq/_internals/api_wrapper.py +3 -21
  3. classiq/applications/chemistry/chemistry_model_constructor.py +86 -100
  4. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +6 -24
  5. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +33 -45
  6. classiq/applications/combinatorial_optimization/__init__.py +2 -0
  7. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +29 -26
  8. classiq/applications/finance/finance_model_constructor.py +23 -26
  9. classiq/applications/grover/grover_model_constructor.py +37 -38
  10. classiq/applications/qsvm/qsvm.py +1 -2
  11. classiq/applications/qsvm/qsvm_model_constructor.py +15 -16
  12. classiq/execution/__init__.py +4 -0
  13. classiq/execution/execution_session.py +151 -0
  14. classiq/execution/qnn.py +80 -0
  15. classiq/executor.py +2 -109
  16. classiq/interface/_version.py +1 -1
  17. classiq/interface/analyzer/analysis_params.py +11 -0
  18. classiq/interface/applications/qsvm.py +0 -8
  19. classiq/interface/ast_node.py +12 -2
  20. classiq/interface/backend/backend_preferences.py +25 -1
  21. classiq/interface/backend/quantum_backend_providers.py +4 -4
  22. classiq/interface/executor/execution_preferences.py +4 -59
  23. classiq/interface/executor/execution_result.py +22 -1
  24. classiq/interface/generator/arith/arithmetic_expression_validator.py +0 -2
  25. classiq/interface/generator/arith/binary_ops.py +88 -25
  26. classiq/interface/generator/arith/unary_ops.py +28 -19
  27. classiq/interface/generator/expressions/atomic_expression_functions.py +6 -2
  28. classiq/interface/generator/expressions/enums/__init__.py +10 -0
  29. classiq/interface/generator/expressions/enums/classical_enum.py +5 -1
  30. classiq/interface/generator/expressions/expression.py +9 -2
  31. classiq/interface/generator/expressions/qmod_qarray_proxy.py +7 -0
  32. classiq/interface/generator/expressions/qmod_qscalar_proxy.py +0 -1
  33. classiq/interface/generator/expressions/sympy_supported_expressions.py +10 -1
  34. classiq/interface/generator/functions/builtins/internal_operators.py +7 -62
  35. classiq/interface/generator/functions/builtins/open_lib_functions.py +810 -2
  36. classiq/interface/generator/functions/classical_type.py +1 -3
  37. classiq/interface/generator/synthesis_metadata/synthesis_duration.py +0 -4
  38. classiq/interface/model/bind_operation.py +3 -1
  39. classiq/interface/model/call_synthesis_data.py +2 -13
  40. classiq/interface/model/classical_if.py +3 -1
  41. classiq/interface/model/classical_parameter_declaration.py +13 -0
  42. classiq/interface/model/control.py +3 -101
  43. classiq/interface/model/inplace_binary_operation.py +3 -1
  44. classiq/interface/model/invert.py +3 -1
  45. classiq/interface/model/port_declaration.py +8 -1
  46. classiq/interface/model/power.py +3 -1
  47. classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +4 -2
  48. classiq/interface/model/quantum_expressions/arithmetic_operation.py +3 -1
  49. classiq/interface/model/quantum_expressions/quantum_expression.py +11 -1
  50. classiq/interface/model/quantum_function_call.py +4 -10
  51. classiq/interface/model/quantum_function_declaration.py +26 -4
  52. classiq/interface/model/quantum_lambda_function.py +1 -20
  53. classiq/interface/model/quantum_statement.py +9 -2
  54. classiq/interface/model/repeat.py +3 -1
  55. classiq/interface/model/statement_block.py +19 -13
  56. classiq/interface/model/validations/handles_validator.py +8 -2
  57. classiq/interface/model/variable_declaration_statement.py +3 -1
  58. classiq/interface/model/within_apply_operation.py +3 -1
  59. classiq/interface/server/routes.py +0 -5
  60. classiq/qmod/__init__.py +1 -2
  61. classiq/qmod/builtins/classical_execution_primitives.py +22 -2
  62. classiq/qmod/builtins/functions.py +51 -1
  63. classiq/qmod/builtins/operations.py +6 -36
  64. classiq/qmod/declaration_inferrer.py +8 -15
  65. classiq/qmod/native/__init__.py +9 -0
  66. classiq/qmod/native/expression_to_qmod.py +12 -9
  67. classiq/qmod/native/pretty_printer.py +4 -4
  68. classiq/qmod/pretty_print/__init__.py +9 -0
  69. classiq/qmod/pretty_print/expression_to_python.py +221 -0
  70. classiq/qmod/pretty_print/pretty_printer.py +421 -0
  71. classiq/qmod/qmod_parameter.py +7 -4
  72. classiq/qmod/quantum_callable.py +2 -1
  73. classiq/qmod/quantum_expandable.py +4 -3
  74. classiq/qmod/quantum_function.py +4 -16
  75. classiq/qmod/symbolic.py +1 -6
  76. classiq/synthesis.py +15 -16
  77. {classiq-0.40.0.dist-info → classiq-0.41.0.dist-info}/METADATA +5 -4
  78. {classiq-0.40.0.dist-info → classiq-0.41.0.dist-info}/RECORD +79 -76
  79. classiq/interface/model/common_model_types.py +0 -23
  80. classiq/interface/model/quantum_expressions/control_state.py +0 -38
  81. {classiq-0.40.0.dist-info → classiq-0.41.0.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, QParam
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 EstimationResult, ExecutionDetails
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[int],
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: QArray[QBit],
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
- REPEAT_OPERATOR,
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
- REPEAT_OPERATOR.operand_declarations["iteration"], iteration
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, QParam
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) in (QParam, CArray)
130
+ or get_origin(py_type) == CArray
143
131
  ):
144
- result.append(_extract_param_decl(name, py_type, qmodule=qmodule))
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:
@@ -0,0 +1,9 @@
1
+ from typing import List
2
+
3
+ from .pretty_printer import DSLPrettyPrinter
4
+
5
+ __all__ = ["DSLPrettyPrinter"]
6
+
7
+
8
+ def __dir__() -> List[str]:
9
+ return __all__
@@ -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
- access_operator = "." if node.attr in CLASSICAL_ATTRIBUTES else "::"
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} = {self._cleaned_ast_to_code(keyword.value)}"
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, level: int = 0, decimal_precision: int = DEFAULT_DECIMAL_PRECISION
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__(self, decimal_precision: int = DEFAULT_DECIMAL_PRECISION) -> None:
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,9 @@
1
+ from typing import List
2
+
3
+ from .pretty_printer import PythonPrettyPrinter
4
+
5
+ __all__ = ["PythonPrettyPrinter"]
6
+
7
+
8
+ def __dir__() -> List[str]:
9
+ return __all__
@@ -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))