classiq 0.86.1__py3-none-any.whl → 0.88.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.

Potentially problematic release.


This version of classiq might be problematic. Click here for more details.

Files changed (131) hide show
  1. classiq/__init__.py +2 -0
  2. classiq/applications/__init__.py +1 -2
  3. classiq/applications/chemistry/hartree_fock.py +5 -1
  4. classiq/applications/chemistry/op_utils.py +2 -2
  5. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +1 -1
  6. classiq/applications/combinatorial_helpers/encoding_mapping.py +11 -15
  7. classiq/applications/combinatorial_helpers/encoding_utils.py +6 -6
  8. classiq/applications/combinatorial_helpers/memory.py +4 -4
  9. classiq/applications/combinatorial_helpers/optimization_model.py +5 -5
  10. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +6 -10
  11. classiq/applications/combinatorial_helpers/pyomo_utils.py +27 -26
  12. classiq/applications/combinatorial_helpers/sympy_utils.py +2 -2
  13. classiq/applications/combinatorial_helpers/transformations/encoding.py +4 -6
  14. classiq/applications/combinatorial_helpers/transformations/fixed_variables.py +4 -4
  15. classiq/applications/combinatorial_helpers/transformations/ising_converter.py +2 -2
  16. classiq/applications/combinatorial_helpers/transformations/penalty_support.py +3 -3
  17. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -0
  18. classiq/applications/hamiltonian/pauli_decomposition.py +34 -2
  19. classiq/evaluators/argument_types.py +15 -6
  20. classiq/evaluators/parameter_types.py +43 -39
  21. classiq/evaluators/qmod_annotated_expression.py +117 -17
  22. classiq/evaluators/qmod_expression_visitors/out_of_place_node_transformer.py +19 -0
  23. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +0 -5
  24. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +66 -16
  25. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +48 -26
  26. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +65 -72
  27. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +13 -6
  28. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +175 -28
  29. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +36 -19
  30. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +17 -5
  31. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +24 -2
  32. classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +97 -0
  33. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +11 -26
  34. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +56 -0
  35. classiq/evaluators/qmod_node_evaluators/piecewise_evaluation.py +40 -0
  36. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +3 -4
  37. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +51 -24
  38. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +53 -9
  39. classiq/evaluators/qmod_node_evaluators/utils.py +28 -6
  40. classiq/evaluators/qmod_type_inference/classical_type_inference.py +188 -0
  41. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +330 -0
  42. classiq/evaluators/quantum_type_utils.py +0 -131
  43. classiq/evaluators/type_type_match.py +1 -1
  44. classiq/execution/execution_session.py +18 -3
  45. classiq/execution/qnn.py +4 -1
  46. classiq/execution/user_budgets.py +1 -1
  47. classiq/interface/_version.py +1 -1
  48. classiq/interface/backend/backend_preferences.py +10 -30
  49. classiq/interface/backend/quantum_backend_providers.py +63 -52
  50. classiq/interface/execution/primitives.py +1 -0
  51. classiq/interface/generator/application_apis/__init__.py +0 -1
  52. classiq/interface/generator/arith/binary_ops.py +107 -115
  53. classiq/interface/generator/arith/extremum_operations.py +33 -45
  54. classiq/interface/generator/arith/number_utils.py +4 -1
  55. classiq/interface/generator/circuit_code/types_and_constants.py +0 -9
  56. classiq/interface/generator/compiler_keywords.py +2 -0
  57. classiq/interface/generator/expressions/atomic_expression_functions.py +0 -2
  58. classiq/interface/generator/function_param_list.py +129 -5
  59. classiq/interface/generator/functions/classical_type.py +67 -2
  60. classiq/interface/generator/functions/qmod_python_interface.py +15 -0
  61. classiq/interface/generator/functions/type_name.py +12 -0
  62. classiq/interface/generator/model/preferences/preferences.py +1 -17
  63. classiq/interface/generator/quantum_program.py +1 -13
  64. classiq/interface/generator/transpiler_basis_gates.py +5 -1
  65. classiq/interface/generator/types/builtin_enum_declarations.py +0 -8
  66. classiq/interface/helpers/model_normalizer.py +2 -2
  67. classiq/interface/helpers/text_utils.py +7 -2
  68. classiq/interface/interface_version.py +1 -1
  69. classiq/interface/model/classical_if.py +48 -0
  70. classiq/interface/model/classical_parameter_declaration.py +4 -0
  71. classiq/interface/model/handle_binding.py +28 -16
  72. classiq/interface/model/port_declaration.py +12 -0
  73. classiq/interface/model/quantum_function_declaration.py +12 -0
  74. classiq/interface/model/quantum_type.py +117 -2
  75. classiq/interface/pretty_print/expression_to_qmod.py +7 -8
  76. classiq/interface/pyomo_extension/__init__.py +0 -4
  77. classiq/interface/pyomo_extension/pyomo_sympy_bimap.py +2 -2
  78. classiq/model_expansions/arithmetic.py +43 -1
  79. classiq/model_expansions/arithmetic_compute_result_attrs.py +255 -0
  80. classiq/model_expansions/capturing/captured_vars.py +2 -5
  81. classiq/model_expansions/quantum_operations/allocate.py +23 -16
  82. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +1 -3
  83. classiq/model_expansions/quantum_operations/assignment_result_processor.py +52 -71
  84. classiq/model_expansions/quantum_operations/bind.py +15 -7
  85. classiq/model_expansions/quantum_operations/call_emitter.py +2 -10
  86. classiq/model_expansions/quantum_operations/classical_var_emitter.py +6 -0
  87. classiq/model_expansions/quantum_operations/handle_evaluator.py +2 -8
  88. classiq/open_library/functions/__init__.py +4 -0
  89. classiq/open_library/functions/lcu.py +117 -0
  90. classiq/open_library/functions/state_preparation.py +47 -5
  91. classiq/qmod/builtins/__init__.py +0 -3
  92. classiq/qmod/builtins/classical_functions.py +0 -28
  93. classiq/qmod/builtins/enums.py +26 -20
  94. classiq/qmod/builtins/functions/__init__.py +0 -5
  95. classiq/qmod/builtins/operations.py +142 -0
  96. classiq/qmod/builtins/structs.py +33 -29
  97. classiq/qmod/native/pretty_printer.py +1 -1
  98. classiq/qmod/pretty_print/expression_to_python.py +1 -6
  99. classiq/qmod/pretty_print/pretty_printer.py +4 -1
  100. classiq/qmod/qmod_variable.py +94 -2
  101. classiq/qmod/semantics/annotation/call_annotation.py +4 -2
  102. classiq/qmod/semantics/annotation/qstruct_annotator.py +20 -5
  103. {classiq-0.86.1.dist-info → classiq-0.88.0.dist-info}/METADATA +5 -5
  104. {classiq-0.86.1.dist-info → classiq-0.88.0.dist-info}/RECORD +106 -124
  105. classiq/applications/finance/__init__.py +0 -15
  106. classiq/interface/finance/finance_modelling_params.py +0 -11
  107. classiq/interface/finance/function_input.py +0 -102
  108. classiq/interface/finance/gaussian_model_input.py +0 -50
  109. classiq/interface/finance/log_normal_model_input.py +0 -40
  110. classiq/interface/finance/model_input.py +0 -22
  111. classiq/interface/generator/amplitude_estimation.py +0 -34
  112. classiq/interface/generator/application_apis/finance_declarations.py +0 -108
  113. classiq/interface/generator/expressions/enums/__init__.py +0 -0
  114. classiq/interface/generator/expressions/enums/finance_functions.py +0 -12
  115. classiq/interface/generator/finance.py +0 -107
  116. classiq/interface/generator/function_param_list_without_self_reference.py +0 -160
  117. classiq/interface/generator/grover_diffuser.py +0 -93
  118. classiq/interface/generator/grover_operator.py +0 -106
  119. classiq/interface/generator/oracles/__init__.py +0 -3
  120. classiq/interface/generator/oracles/arithmetic_oracle.py +0 -82
  121. classiq/interface/generator/oracles/custom_oracle.py +0 -65
  122. classiq/interface/generator/oracles/oracle_abc.py +0 -76
  123. classiq/interface/generator/oracles/oracle_function_param_list.py +0 -6
  124. classiq/interface/generator/piecewise_linear_amplitude_loading.py +0 -165
  125. classiq/interface/generator/qpe.py +0 -169
  126. classiq/interface/grover/__init__.py +0 -0
  127. classiq/interface/grover/grover_modelling_params.py +0 -13
  128. classiq/model_expansions/transformers/var_splitter.py +0 -224
  129. classiq/qmod/builtins/functions/finance.py +0 -34
  130. /classiq/{interface/finance → evaluators/qmod_type_inference}/__init__.py +0 -0
  131. {classiq-0.86.1.dist-info → classiq-0.88.0.dist-info}/WHEEL +0 -0
@@ -1,5 +1,6 @@
1
1
  from collections.abc import Sequence
2
2
 
3
+ from classiq.interface.exceptions import ClassiqExpansionError
3
4
  from classiq.interface.generator.functions.port_declaration import (
4
5
  PortDeclarationDirection,
5
6
  )
@@ -8,7 +9,9 @@ from classiq.interface.model.port_declaration import AnonPortDeclaration
8
9
  from classiq.interface.model.quantum_function_declaration import AnonPositionalArg
9
10
  from classiq.interface.model.quantum_type import QuantumNumeric
10
11
 
11
- from classiq.evaluators.quantum_type_utils import copy_type_information
12
+ from classiq.evaluators.qmod_type_inference.quantum_type_inference import (
13
+ inject_quantum_type_attributes_inplace,
14
+ )
12
15
  from classiq.model_expansions.scope import Evaluated, QuantumVariable
13
16
 
14
17
 
@@ -36,11 +39,17 @@ def add_information_from_output_arguments(
36
39
  if parameter.direction != PortDeclarationDirection.Output:
37
40
  continue
38
41
 
39
- if parameter.quantum_type.is_evaluated:
40
- copy_type_information(
41
- parameter.quantum_type,
42
- argument_as_quantum_symbol.quantum_type,
43
- str(argument_as_quantum_symbol),
42
+ if (
43
+ parameter.quantum_type.is_evaluated
44
+ and not inject_quantum_type_attributes_inplace(
45
+ parameter.quantum_type, argument_as_quantum_symbol.quantum_type
46
+ )
47
+ ):
48
+ raise ClassiqExpansionError(
49
+ f"Argument {str(argument_as_quantum_symbol)!r} of type "
50
+ f"{argument_as_quantum_symbol.quantum_type.qmod_type_name} is "
51
+ f"incompatible with parameter {parameter.name!r} of type "
52
+ f"{parameter.quantum_type.qmod_type_name}"
44
53
  )
45
54
 
46
55
 
@@ -45,11 +45,8 @@ from classiq.evaluators.classical_expression import (
45
45
  from classiq.evaluators.classical_type_inference import (
46
46
  infer_classical_type,
47
47
  )
48
- from classiq.evaluators.quantum_type_utils import (
49
- copy_type_information,
50
- set_element_type,
51
- set_length,
52
- set_size,
48
+ from classiq.evaluators.qmod_type_inference.quantum_type_inference import (
49
+ inject_quantum_type_attributes,
53
50
  )
54
51
  from classiq.model_expansions.closure import FunctionClosure
55
52
  from classiq.model_expansions.scope import (
@@ -100,7 +97,7 @@ def _update_scope(
100
97
  quantum_var = argument.as_type(QuantumVariable)
101
98
  casted_argument = _cast(
102
99
  parameter.quantum_type,
103
- quantum_var.quantum_type,
100
+ quantum_var,
104
101
  parameter.name,
105
102
  )
106
103
  closure.scope[parameter.name] = Evaluated(
@@ -134,12 +131,17 @@ def _update_operand_signature_environment(
134
131
 
135
132
 
136
133
  def _cast(
137
- parameter_type: QuantumType, argument_type: QuantumType, param_name: str
134
+ parameter_type: QuantumType, argument: QuantumVariable, param_name: str
138
135
  ) -> QuantumSymbol:
139
- updated_quantum_type = parameter_type.model_copy()
140
- _inject_quantum_arg_info_to_type(updated_quantum_type, argument_type, param_name)
136
+ updated_type = inject_quantum_type_attributes(argument.quantum_type, parameter_type)
137
+ if updated_type is None:
138
+ raise ClassiqExpansionError(
139
+ f"Argument {str(argument)!r} of type "
140
+ f"{argument.quantum_type.qmod_type_name} is incompatible with parameter "
141
+ f"{param_name!r} of type {parameter_type.qmod_type_name}"
142
+ )
141
143
  return QuantumSymbol(
142
- handle=HandleBinding(name=param_name), quantum_type=updated_quantum_type
144
+ handle=HandleBinding(name=param_name), quantum_type=updated_type
143
145
  )
144
146
 
145
147
 
@@ -168,11 +170,17 @@ def _evaluate_type_from_arg(
168
170
  parameter.quantum_type.model_copy(), inner_scope, parameter.name
169
171
  )
170
172
  if parameter.direction != PortDeclarationDirection.Output:
171
- updated_quantum_type = _inject_quantum_arg_info_to_type(
172
- updated_quantum_type,
173
- argument.as_type(QuantumVariable).quantum_type,
174
- parameter.name,
173
+ arg_type = argument.as_type(QuantumVariable).quantum_type
174
+ updated_output_quantum_type = inject_quantum_type_attributes(
175
+ arg_type, updated_quantum_type
175
176
  )
177
+ if updated_output_quantum_type is None:
178
+ raise ClassiqExpansionError(
179
+ f"Argument {str(argument.value)!r} of type "
180
+ f"{arg_type.qmod_type_name} is incompatible with parameter "
181
+ f"{parameter.name!r} of type {updated_quantum_type.qmod_type_name}"
182
+ )
183
+ updated_quantum_type = updated_output_quantum_type
176
184
  return parameter.model_copy(update={"quantum_type": updated_quantum_type})
177
185
 
178
186
 
@@ -196,7 +204,7 @@ def _evaluate_qarray_in_quantum_symbol(
196
204
  new_element_type = evaluate_type_in_quantum_symbol(
197
205
  type_to_update.element_type, scope, param_name
198
206
  )
199
- set_element_type(type_to_update, new_element_type)
207
+ type_to_update.element_type = new_element_type
200
208
  if type_to_update.length is not None:
201
209
  new_length = _eval_expr(
202
210
  type_to_update.length,
@@ -207,13 +215,26 @@ def _evaluate_qarray_in_quantum_symbol(
207
215
  param_name,
208
216
  )
209
217
  if new_length is not None:
210
- set_length(type_to_update, new_length)
218
+ type_to_update.length = Expression(expr=str(new_length))
211
219
  return type_to_update
212
220
 
213
221
 
214
222
  def _evaluate_qnum_in_quantum_symbol(
215
223
  type_to_update: QuantumNumeric, scope: Scope, param_name: str
216
224
  ) -> QuantumNumeric:
225
+ if type_to_update.size is None:
226
+ return type_to_update
227
+ new_size = _eval_expr(
228
+ type_to_update.size,
229
+ scope,
230
+ int,
231
+ type_to_update.type_name,
232
+ "size",
233
+ param_name,
234
+ )
235
+ if new_size is not None:
236
+ type_to_update.size = Expression(expr=str(new_size))
237
+
217
238
  if type_to_update.is_signed is not None:
218
239
  new_is_sign = _eval_expr(
219
240
  type_to_update.is_signed,
@@ -225,6 +246,9 @@ def _evaluate_qnum_in_quantum_symbol(
225
246
  )
226
247
  if new_is_sign is not None:
227
248
  type_to_update.is_signed = Expression(expr=str(new_is_sign))
249
+ else:
250
+ type_to_update.is_signed = Expression(expr="False")
251
+
228
252
  if type_to_update.fraction_digits is not None:
229
253
  new_fraction_digits = _eval_expr(
230
254
  type_to_update.fraction_digits,
@@ -236,17 +260,9 @@ def _evaluate_qnum_in_quantum_symbol(
236
260
  )
237
261
  if new_fraction_digits is not None:
238
262
  type_to_update.fraction_digits = Expression(expr=str(new_fraction_digits))
239
- if type_to_update.size is not None:
240
- new_size = _eval_expr(
241
- type_to_update.size,
242
- scope,
243
- int,
244
- type_to_update.type_name,
245
- "size",
246
- param_name,
247
- )
248
- if new_size is not None:
249
- set_size(type_to_update, new_size, param_name)
263
+ else:
264
+ type_to_update.fraction_digits = Expression(expr="0")
265
+
250
266
  return type_to_update
251
267
 
252
268
 
@@ -302,18 +318,6 @@ def evaluate_types_in_quantum_symbols(
302
318
  ]
303
319
 
304
320
 
305
- def _inject_quantum_arg_info_to_type(
306
- parameter_type: QuantumType, argument_type: QuantumType, param_name: str
307
- ) -> QuantumType:
308
- if argument_type.has_size_in_bits:
309
- copy_type_information(
310
- argument_type,
311
- parameter_type,
312
- param_name,
313
- )
314
- return parameter_type
315
-
316
-
317
321
  def evaluate_type_in_classical_symbol(
318
322
  type_to_update: ClassicalType, scope: Scope, param_name: str
319
323
  ) -> ClassicalType:
@@ -1,10 +1,22 @@
1
1
  import ast
2
- from collections.abc import Sequence
2
+ from collections.abc import Mapping, Sequence
3
3
  from dataclasses import dataclass
4
+ from enum import Enum
4
5
  from typing import Any, Union, cast
5
6
 
7
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
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
+ ClassicalType,
13
+ )
6
14
  from classiq.interface.model.handle_binding import HandleBinding
15
+ from classiq.interface.model.quantum_type import QuantumType
7
16
 
17
+ from classiq.evaluators.qmod_expression_visitors.out_of_place_node_transformer import (
18
+ OutOfPlaceNodeTransformer,
19
+ )
8
20
  from classiq.evaluators.qmod_node_evaluators.utils import QmodType, is_classical_type
9
21
 
10
22
  QmodExprNodeId = int
@@ -27,13 +39,31 @@ class ConcatenationAnnotation:
27
39
  elements: list[QmodExprNodeId]
28
40
 
29
41
 
30
- class _ExprInliner(ast.NodeTransformer):
42
+ def qmod_val_to_str(val: Any) -> str:
43
+ if isinstance(val, QmodStructInstance):
44
+ fields = ", ".join(
45
+ f"{field_name}={qmod_val_to_str(field_val)}"
46
+ for field_name, field_val in val.fields.items()
47
+ )
48
+ return f"struct_literal({val.struct_declaration.name}, {fields})"
49
+ if isinstance(val, list):
50
+ return f"[{', '.join(qmod_val_to_str(item) for item in val)}]"
51
+ if isinstance(val, Enum):
52
+ return Enum.__str__(val)
53
+ if isinstance(val, (int, float, bool, complex)):
54
+ return str(val)
55
+ raise ClassiqInternalExpansionError(
56
+ f"Unrecognized value {str(val)!r} of type {type(val)}"
57
+ )
58
+
59
+
60
+ class _ExprInliner(OutOfPlaceNodeTransformer):
31
61
  def __init__(self, expr_val: "QmodAnnotatedExpression") -> None:
32
62
  self._expr_val = expr_val
33
63
 
34
64
  def visit(self, node: ast.AST) -> Any:
35
65
  if self._expr_val.has_value(node):
36
- return ast.Name(id=str(self._expr_val.get_value(node)))
66
+ return ast.Name(id=qmod_val_to_str(self._expr_val.get_value(node)))
37
67
  if self._expr_val.has_var(node):
38
68
  return ast.Name(id=str(self._expr_val.get_var(node)))
39
69
  return super().visit(node)
@@ -52,8 +82,9 @@ class QmodAnnotatedExpression:
52
82
  QmodExprNodeId, QuantumTypeAttributeAnnotation
53
83
  ] = {}
54
84
  self._concatenations: dict[QmodExprNodeId, ConcatenationAnnotation] = {}
85
+ self._locked = False
55
86
 
56
- def to_qmod_expr(self) -> str:
87
+ def __str__(self) -> str:
57
88
  return ast.unparse(_ExprInliner(self).visit(self.root))
58
89
 
59
90
  def has_node(self, node_id: QmodExprNodeId) -> bool:
@@ -63,6 +94,8 @@ class QmodAnnotatedExpression:
63
94
  return self._node_mapping[node_id]
64
95
 
65
96
  def set_value(self, node: Union[ast.AST, QmodExprNodeId], value: Any) -> None:
97
+ if self._locked:
98
+ raise ClassiqInternalExpansionError("QAE is locked")
66
99
  if isinstance(node, ast.AST):
67
100
  node = id(node)
68
101
  self._values[node] = value
@@ -80,6 +113,8 @@ class QmodAnnotatedExpression:
80
113
  def set_type(
81
114
  self, node: Union[ast.AST, QmodExprNodeId], qmod_type: QmodType
82
115
  ) -> None:
116
+ if self._locked:
117
+ raise ClassiqInternalExpansionError("QAE is locked")
83
118
  if isinstance(node, ast.AST):
84
119
  node_id = id(node)
85
120
  self._node_mapping[node_id] = node
@@ -91,7 +126,25 @@ class QmodAnnotatedExpression:
91
126
  node = id(node)
92
127
  return self._types[node]
93
128
 
129
+ def get_quantum_type(self, node: Union[ast.AST, QmodExprNodeId]) -> QuantumType:
130
+ if isinstance(node, ast.AST):
131
+ node = id(node)
132
+ qmod_type = self._types[node]
133
+ if is_classical_type(qmod_type):
134
+ raise ClassiqInternalExpansionError
135
+ return cast(QuantumType, qmod_type)
136
+
137
+ def get_classical_type(self, node: Union[ast.AST, QmodExprNodeId]) -> ClassicalType:
138
+ if isinstance(node, ast.AST):
139
+ node = id(node)
140
+ qmod_type = self._types[node]
141
+ if not is_classical_type(qmod_type):
142
+ raise ClassiqInternalExpansionError
143
+ return cast(ClassicalType, qmod_type)
144
+
94
145
  def set_var(self, node: Union[ast.AST, QmodExprNodeId], var: HandleBinding) -> None:
146
+ if self._locked:
147
+ raise ClassiqInternalExpansionError("QAE is locked")
95
148
  var = var.collapse()
96
149
  if isinstance(node, ast.AST):
97
150
  node = id(node)
@@ -119,6 +172,8 @@ class QmodAnnotatedExpression:
119
172
  return node in self._quantum_vars
120
173
 
121
174
  def remove_var(self, node: Union[ast.AST, QmodExprNodeId]) -> None:
175
+ if self._locked:
176
+ raise ClassiqInternalExpansionError("QAE is locked")
122
177
  if isinstance(node, ast.AST):
123
178
  node = id(node)
124
179
  if node in self._classical_vars:
@@ -132,6 +187,8 @@ class QmodAnnotatedExpression:
132
187
  value: Union[ast.AST, QmodExprNodeId],
133
188
  index: Union[ast.AST, QmodExprNodeId],
134
189
  ) -> None:
190
+ if self._locked:
191
+ raise ClassiqInternalExpansionError("QAE is locked")
135
192
  if isinstance(node, ast.AST):
136
193
  node = id(node)
137
194
  if isinstance(value, ast.AST):
@@ -149,7 +206,7 @@ class QmodAnnotatedExpression:
149
206
 
150
207
  def get_quantum_subscripts(
151
208
  self,
152
- ) -> dict[QmodExprNodeId, QuantumSubscriptAnnotation]:
209
+ ) -> Mapping[QmodExprNodeId, QuantumSubscriptAnnotation]:
153
210
  return self._quantum_subscripts
154
211
 
155
212
  def set_quantum_type_attr(
@@ -158,6 +215,8 @@ class QmodAnnotatedExpression:
158
215
  value: Union[ast.AST, QmodExprNodeId],
159
216
  attr: str,
160
217
  ) -> None:
218
+ if self._locked:
219
+ raise ClassiqInternalExpansionError("QAE is locked")
161
220
  if isinstance(node, ast.AST):
162
221
  node = id(node)
163
222
  if isinstance(value, ast.AST):
@@ -173,7 +232,7 @@ class QmodAnnotatedExpression:
173
232
 
174
233
  def get_quantum_type_attributes(
175
234
  self,
176
- ) -> dict[QmodExprNodeId, QuantumTypeAttributeAnnotation]:
235
+ ) -> Mapping[QmodExprNodeId, QuantumTypeAttributeAnnotation]:
177
236
  return self._quantum_type_attrs
178
237
 
179
238
  def set_concatenation(
@@ -181,27 +240,68 @@ class QmodAnnotatedExpression:
181
240
  node: Union[ast.AST, QmodExprNodeId],
182
241
  elements: Sequence[Union[ast.AST, QmodExprNodeId]],
183
242
  ) -> None:
243
+ if self._locked:
244
+ raise ClassiqInternalExpansionError("QAE is locked")
184
245
  if isinstance(node, ast.AST):
185
246
  node = id(node)
186
- elements = cast(
187
- list[QmodExprNodeId],
188
- [
189
- id(element) if isinstance(element, ast.AST) else element
190
- for element in elements
191
- ],
192
- )
193
- self._concatenations[node] = ConcatenationAnnotation(elements=elements)
247
+ inlined_elements: list[QmodExprNodeId] = []
248
+ for element in elements:
249
+ if isinstance(element, ast.AST):
250
+ element = id(element)
251
+ if element not in self._concatenations:
252
+ inlined_elements.append(element)
253
+ else:
254
+ inlined_elements.extend(self._concatenations.pop(element).elements)
255
+ self._concatenations[node] = ConcatenationAnnotation(elements=inlined_elements)
194
256
 
195
257
  def has_concatenation(self, node: Union[ast.AST, QmodExprNodeId]) -> bool:
196
258
  if isinstance(node, ast.AST):
197
259
  node = id(node)
198
260
  return node in self._concatenations
199
261
 
200
- def get_concatenations(self) -> dict[QmodExprNodeId, ConcatenationAnnotation]:
262
+ def get_concatenations(self) -> Mapping[QmodExprNodeId, ConcatenationAnnotation]:
201
263
  return self._concatenations
202
264
 
203
- def get_classical_vars(self) -> dict[QmodExprNodeId, HandleBinding]:
265
+ def get_classical_vars(self) -> Mapping[QmodExprNodeId, HandleBinding]:
204
266
  return self._classical_vars
205
267
 
206
- def get_quantum_vars(self) -> dict[QmodExprNodeId, HandleBinding]:
268
+ def get_quantum_vars(self) -> Mapping[QmodExprNodeId, HandleBinding]:
207
269
  return self._quantum_vars
270
+
271
+ def clear_node_data(self, node: Union[ast.AST, QmodExprNodeId]) -> None:
272
+ if isinstance(node, ast.AST):
273
+ node = id(node)
274
+ self._node_mapping.pop(node, None)
275
+ self._values.pop(node, None)
276
+ self._types.pop(node, None)
277
+ self._classical_vars.pop(node, None)
278
+ self._quantum_vars.pop(node, None)
279
+ qs = self._quantum_subscripts.pop(node, None)
280
+ if qs is not None:
281
+ self.clear_node_data(qs.value)
282
+ self.clear_node_data(qs.index)
283
+ qta = self._quantum_type_attrs.pop(node, None)
284
+ if qta is not None:
285
+ self.clear_node_data(qta.value)
286
+ cnct = self._concatenations.pop(node, None)
287
+ if cnct is not None:
288
+ for element in cnct.elements:
289
+ self.clear_node_data(element)
290
+
291
+ def _add_data_from(self, other: "QmodAnnotatedExpression") -> None:
292
+ self._node_mapping |= other._node_mapping
293
+ self._values |= other._values
294
+ self._types |= other._types
295
+ self._classical_vars |= other._classical_vars
296
+ self._quantum_vars |= other._quantum_vars
297
+ self._quantum_subscripts |= other._quantum_subscripts
298
+ self._quantum_type_attrs |= other._quantum_type_attrs
299
+ self._concatenations |= other._concatenations
300
+
301
+ def clone(self) -> "QmodAnnotatedExpression":
302
+ expr_val = QmodAnnotatedExpression(self.root)
303
+ expr_val._add_data_from(self)
304
+ return expr_val
305
+
306
+ def lock(self) -> None:
307
+ self._locked = True
@@ -0,0 +1,19 @@
1
+ import ast
2
+ from typing import Any
3
+
4
+
5
+ class OutOfPlaceNodeTransformer(ast.NodeVisitor):
6
+ def generic_visit(self, node: ast.AST) -> ast.AST:
7
+ new_fields: dict[str, Any] = {}
8
+ for field, field_val in ast.iter_fields(node):
9
+ if isinstance(field_val, list):
10
+ new_fields[field] = [
11
+ new_val
12
+ for item in field_val
13
+ if (new_val := self.visit(item)) is not None
14
+ ]
15
+ elif isinstance(field_val, ast.AST):
16
+ new_fields[field] = self.visit(field_val)
17
+ else:
18
+ new_fields[field] = field_val
19
+ return type(node)(**new_fields)
@@ -73,11 +73,6 @@ class QmodExpressionBwc(ast.NodeTransformer):
73
73
  return node
74
74
  return ast.Compare(left=args[0], ops=[ast.GtE()], comparators=[args[1]])
75
75
 
76
- if func == "struct_literal":
77
- if num_args != 1:
78
- return node
79
- return ast.Call(func=node.args[0], args=[], keywords=node.keywords)
80
-
81
76
  if func == "do_subscript":
82
77
  if num_args != 2 or num_kwargs != 0:
83
78
  return node
@@ -1,7 +1,7 @@
1
1
  import ast
2
2
  from collections.abc import Mapping, Sequence
3
3
  from enum import IntEnum
4
- from typing import Any, Callable, Optional
4
+ from typing import Any, Callable, Optional, cast
5
5
 
6
6
  from classiq.interface.exceptions import (
7
7
  ClassiqExpansionError,
@@ -31,13 +31,16 @@ from classiq.evaluators.qmod_node_evaluators.compare_evaluation import eval_comp
31
31
  from classiq.evaluators.qmod_node_evaluators.constant_evaluation import (
32
32
  eval_constant,
33
33
  eval_enum_member,
34
+ try_eval_qmod_literal,
34
35
  try_eval_sympy_constant,
35
36
  )
36
37
  from classiq.evaluators.qmod_node_evaluators.list_evaluation import eval_list
37
38
  from classiq.evaluators.qmod_node_evaluators.measurement_evaluation import (
38
39
  eval_measurement,
39
40
  )
41
+ from classiq.evaluators.qmod_node_evaluators.min_max_evaluation import eval_min_max_op
40
42
  from classiq.evaluators.qmod_node_evaluators.name_evaluation import eval_name
43
+ from classiq.evaluators.qmod_node_evaluators.piecewise_evaluation import eval_piecewise
41
44
  from classiq.evaluators.qmod_node_evaluators.struct_instantiation_evaluation import (
42
45
  eval_struct_instantiation,
43
46
  )
@@ -74,6 +77,7 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
74
77
  self,
75
78
  expr_val: QmodAnnotatedExpression,
76
79
  *,
80
+ treat_qnum_as_float: bool = False,
77
81
  machine_precision: int = DEFAULT_MACHINE_PRECISION,
78
82
  classical_struct_declarations: Optional[Sequence[StructDeclaration]] = None,
79
83
  enum_declarations: Optional[Sequence[EnumDeclaration]] = None,
@@ -84,6 +88,7 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
84
88
  scope: Optional[dict[str, Any]] = None,
85
89
  ) -> None:
86
90
  self._expr_val = expr_val
91
+ self._treat_qnum_as_float = treat_qnum_as_float
87
92
  self._machine_precision = machine_precision
88
93
  self._classical_struct_decls = nameables_to_dict(
89
94
  classical_struct_declarations or []
@@ -98,8 +103,8 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
98
103
 
99
104
  def visit(self, node: ast.AST) -> None:
100
105
  if not isinstance(node, _SUPPORTED_NODES):
101
- raise ClassiqInternalExpansionError(
102
- f"Syntax error: {type(node).__name__!r} is not supported"
106
+ raise ClassiqExpansionError(
107
+ f"Syntax error: {type(node).__name__!r} is not a valid Qmod expression"
103
108
  )
104
109
  super().visit(node)
105
110
 
@@ -109,22 +114,19 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
109
114
 
110
115
  def visit_BinOp(self, node: ast.BinOp) -> None:
111
116
  super().generic_visit(node)
112
- eval_binary_op(self._expr_val, node)
117
+ eval_binary_op(
118
+ self._expr_val, node, self._treat_qnum_as_float, self._machine_precision
119
+ )
113
120
 
114
121
  def visit_UnaryOp(self, node: ast.UnaryOp) -> None:
115
122
  super().generic_visit(node)
116
- eval_unary_op(self._expr_val, node)
123
+ eval_unary_op(self._expr_val, node, self._machine_precision)
117
124
 
118
125
  def visit_Compare(self, node: ast.Compare) -> None:
119
126
  super().generic_visit(node)
120
127
  eval_compare(self._expr_val, node)
121
128
 
122
129
  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
130
  func = node.func
129
131
  if not isinstance(func, ast.Name):
130
132
  raise ClassiqExpansionError(
@@ -132,16 +134,42 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
132
134
  )
133
135
  func_name = func.id
134
136
 
137
+ if func_name == "Piecewise":
138
+ self._eval_piecewise(node)
139
+ return
140
+
141
+ for kwarg in node.keywords:
142
+ self.visit(kwarg)
143
+
144
+ if (
145
+ func_name == "struct_literal"
146
+ and len(node.args) == 1
147
+ and isinstance(node.args[0], ast.Name)
148
+ and (struct_name := node.args[0].id) in self._classical_struct_decls
149
+ ):
150
+ eval_struct_instantiation(
151
+ self._expr_val, node, self._classical_struct_decls[struct_name]
152
+ )
153
+ return
154
+
155
+ for arg in node.args:
156
+ self.visit(arg)
157
+
135
158
  if func_name == "measure":
136
159
  eval_measurement(self._expr_val, node)
137
160
  return
138
161
 
139
- if func_name in self._classical_struct_decls:
140
- eval_struct_instantiation(
141
- self._expr_val, node, self._classical_struct_decls[func_name]
162
+ if func_name in ("min", "max"):
163
+ eval_min_max_op(
164
+ self._expr_val,
165
+ node,
166
+ func_name,
167
+ self._treat_qnum_as_float,
168
+ self._machine_precision,
142
169
  )
143
170
  return
144
171
 
172
+ # FIXME: Remove (CLS-3241)
145
173
  if func_name in self._classical_function_callables:
146
174
  if func_name not in self._classical_function_declarations:
147
175
  raise ClassiqInternalExpansionError
@@ -153,6 +181,7 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
153
181
  )
154
182
  return
155
183
 
184
+ # FIXME: Remove (CLS-3241)
156
185
  if func_name in self._classical_function_declarations:
157
186
  eval_symbolic_function(
158
187
  self._expr_val, node, self._classical_function_declarations[func_name]
@@ -167,6 +196,21 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
167
196
 
168
197
  raise ClassiqExpansionError(f"{func.id!r} is undefined")
169
198
 
199
+ def _eval_piecewise(self, node: ast.Call) -> None:
200
+ if (
201
+ len(node.args) == 0
202
+ or len(node.keywords) != 0
203
+ or not all(
204
+ isinstance(arg, ast.Tuple) and len(arg.elts) == 2 for arg in node.args
205
+ )
206
+ ):
207
+ raise ClassiqExpansionError("Malformed Piecewise expression")
208
+ args = [(arg.elts[0], arg.elts[1]) for arg in cast(list[ast.Tuple], node.args)]
209
+ for value, cond in args:
210
+ self.visit(value)
211
+ self.visit(cond)
212
+ eval_piecewise(self._expr_val, node, args)
213
+
170
214
  def visit_Constant(self, node: ast.Constant) -> None:
171
215
  eval_constant(self._expr_val, node)
172
216
 
@@ -194,12 +238,15 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
194
238
  eval_subscript(self._expr_val, node)
195
239
 
196
240
  def visit_Name(self, node: ast.Name) -> None:
241
+ if node.id in self._scope:
242
+ eval_name(self._expr_val, node, self._scope[node.id])
243
+ return
197
244
  if try_eval_sympy_constant(self._expr_val, node):
198
245
  return
246
+ if try_eval_qmod_literal(self._expr_val, node):
247
+ return
199
248
 
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])
249
+ raise ClassiqExpansionError(f"Variable {node.id!r} is undefined")
203
250
 
204
251
  def visit_List(self, node: ast.List) -> None:
205
252
  super().generic_visit(node)
@@ -209,6 +256,7 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
209
256
  def evaluate_qmod_expression(
210
257
  expr: str,
211
258
  *,
259
+ treat_qnum_as_float: bool = False,
212
260
  machine_precision: int = DEFAULT_MACHINE_PRECISION,
213
261
  classical_struct_declarations: Optional[Sequence[StructDeclaration]] = None,
214
262
  enum_declarations: Optional[Sequence[EnumDeclaration]] = None,
@@ -222,6 +270,7 @@ def evaluate_qmod_expression(
222
270
  expr_value = QmodAnnotatedExpression(expr_ast)
223
271
  QmodExpressionEvaluator(
224
272
  expr_value,
273
+ treat_qnum_as_float=treat_qnum_as_float,
225
274
  machine_precision=machine_precision,
226
275
  classical_struct_declarations=classical_struct_declarations,
227
276
  enum_declarations=enum_declarations,
@@ -229,4 +278,5 @@ def evaluate_qmod_expression(
229
278
  classical_function_callables=classical_function_callables,
230
279
  scope=scope,
231
280
  ).visit(expr_value.root)
281
+ expr_value.lock()
232
282
  return expr_value