classiq 0.86.0__py3-none-any.whl → 0.87.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 (96) hide show
  1. classiq/__init__.py +2 -0
  2. classiq/applications/chemistry/hartree_fock.py +5 -1
  3. classiq/applications/chemistry/op_utils.py +2 -2
  4. classiq/applications/combinatorial_helpers/encoding_mapping.py +11 -15
  5. classiq/applications/combinatorial_helpers/encoding_utils.py +6 -6
  6. classiq/applications/combinatorial_helpers/memory.py +4 -4
  7. classiq/applications/combinatorial_helpers/optimization_model.py +5 -5
  8. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +6 -10
  9. classiq/applications/combinatorial_helpers/pyomo_utils.py +27 -26
  10. classiq/applications/combinatorial_helpers/sympy_utils.py +2 -2
  11. classiq/applications/combinatorial_helpers/transformations/encoding.py +4 -6
  12. classiq/applications/combinatorial_helpers/transformations/fixed_variables.py +4 -4
  13. classiq/applications/combinatorial_helpers/transformations/ising_converter.py +2 -2
  14. classiq/applications/combinatorial_helpers/transformations/penalty_support.py +3 -3
  15. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -0
  16. classiq/applications/hamiltonian/pauli_decomposition.py +33 -1
  17. classiq/evaluators/argument_types.py +15 -6
  18. classiq/evaluators/parameter_types.py +43 -39
  19. classiq/evaluators/qmod_annotated_expression.py +88 -11
  20. classiq/evaluators/qmod_expression_visitors/out_of_place_node_transformer.py +19 -0
  21. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +54 -11
  22. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +40 -25
  23. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +29 -59
  24. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +12 -5
  25. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +175 -28
  26. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +21 -14
  27. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +9 -5
  28. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +20 -1
  29. classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +97 -0
  30. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +11 -26
  31. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +56 -0
  32. classiq/evaluators/qmod_node_evaluators/piecewise_evaluation.py +40 -0
  33. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +2 -3
  34. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +48 -21
  35. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +53 -9
  36. classiq/evaluators/qmod_node_evaluators/utils.py +27 -5
  37. classiq/evaluators/qmod_type_inference/classical_type_inference.py +188 -0
  38. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +292 -0
  39. classiq/evaluators/quantum_type_utils.py +0 -131
  40. classiq/execution/execution_session.py +1 -1
  41. classiq/execution/qnn.py +4 -1
  42. classiq/execution/user_budgets.py +1 -1
  43. classiq/interface/_version.py +1 -1
  44. classiq/interface/backend/backend_preferences.py +10 -30
  45. classiq/interface/backend/quantum_backend_providers.py +63 -52
  46. classiq/interface/generator/arith/binary_ops.py +107 -115
  47. classiq/interface/generator/arith/extremum_operations.py +33 -45
  48. classiq/interface/generator/arith/number_utils.py +4 -1
  49. classiq/interface/generator/circuit_code/types_and_constants.py +0 -9
  50. classiq/interface/generator/compiler_keywords.py +2 -0
  51. classiq/interface/generator/function_param_list.py +133 -5
  52. classiq/interface/generator/functions/classical_type.py +59 -2
  53. classiq/interface/generator/functions/qmod_python_interface.py +15 -0
  54. classiq/interface/generator/functions/type_name.py +6 -0
  55. classiq/interface/generator/model/preferences/preferences.py +1 -17
  56. classiq/interface/generator/quantum_program.py +1 -13
  57. classiq/interface/helpers/model_normalizer.py +2 -2
  58. classiq/interface/helpers/text_utils.py +7 -2
  59. classiq/interface/interface_version.py +1 -1
  60. classiq/interface/model/classical_if.py +40 -0
  61. classiq/interface/model/handle_binding.py +28 -16
  62. classiq/interface/model/quantum_type.py +61 -2
  63. classiq/interface/pretty_print/expression_to_qmod.py +24 -11
  64. classiq/interface/pyomo_extension/__init__.py +0 -4
  65. classiq/interface/pyomo_extension/pyomo_sympy_bimap.py +2 -2
  66. classiq/model_expansions/arithmetic.py +43 -1
  67. classiq/model_expansions/arithmetic_compute_result_attrs.py +255 -0
  68. classiq/model_expansions/capturing/captured_vars.py +2 -5
  69. classiq/model_expansions/quantum_operations/allocate.py +22 -15
  70. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +1 -3
  71. classiq/model_expansions/quantum_operations/assignment_result_processor.py +52 -71
  72. classiq/model_expansions/quantum_operations/bind.py +15 -7
  73. classiq/model_expansions/quantum_operations/call_emitter.py +2 -10
  74. classiq/model_expansions/quantum_operations/classical_var_emitter.py +6 -0
  75. classiq/open_library/functions/__init__.py +3 -0
  76. classiq/open_library/functions/lcu.py +117 -0
  77. classiq/qmod/builtins/enums.py +2 -2
  78. classiq/qmod/builtins/structs.py +33 -18
  79. classiq/qmod/pretty_print/expression_to_python.py +7 -9
  80. {classiq-0.86.0.dist-info → classiq-0.87.0.dist-info}/METADATA +3 -3
  81. {classiq-0.86.0.dist-info → classiq-0.87.0.dist-info}/RECORD +83 -89
  82. classiq/interface/generator/amplitude_estimation.py +0 -34
  83. classiq/interface/generator/function_param_list_without_self_reference.py +0 -160
  84. classiq/interface/generator/grover_diffuser.py +0 -93
  85. classiq/interface/generator/grover_operator.py +0 -106
  86. classiq/interface/generator/oracles/__init__.py +0 -3
  87. classiq/interface/generator/oracles/arithmetic_oracle.py +0 -82
  88. classiq/interface/generator/oracles/custom_oracle.py +0 -65
  89. classiq/interface/generator/oracles/oracle_abc.py +0 -76
  90. classiq/interface/generator/oracles/oracle_function_param_list.py +0 -6
  91. classiq/interface/generator/piecewise_linear_amplitude_loading.py +0 -165
  92. classiq/interface/generator/qpe.py +0 -169
  93. classiq/interface/grover/grover_modelling_params.py +0 -13
  94. classiq/model_expansions/transformers/var_splitter.py +0 -224
  95. /classiq/{interface/grover → evaluators/qmod_type_inference}/__init__.py +0 -0
  96. {classiq-0.86.0.dist-info → classiq-0.87.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
2
  from collections.abc import 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"{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)
@@ -53,7 +83,7 @@ class QmodAnnotatedExpression:
53
83
  ] = {}
54
84
  self._concatenations: dict[QmodExprNodeId, ConcatenationAnnotation] = {}
55
85
 
56
- def to_qmod_expr(self) -> str:
86
+ def __str__(self) -> str:
57
87
  return ast.unparse(_ExprInliner(self).visit(self.root))
58
88
 
59
89
  def has_node(self, node_id: QmodExprNodeId) -> bool:
@@ -91,6 +121,22 @@ class QmodAnnotatedExpression:
91
121
  node = id(node)
92
122
  return self._types[node]
93
123
 
124
+ def get_quantum_type(self, node: Union[ast.AST, QmodExprNodeId]) -> QuantumType:
125
+ if isinstance(node, ast.AST):
126
+ node = id(node)
127
+ qmod_type = self._types[node]
128
+ if is_classical_type(qmod_type):
129
+ raise ClassiqInternalExpansionError
130
+ return cast(QuantumType, qmod_type)
131
+
132
+ def get_classical_type(self, node: Union[ast.AST, QmodExprNodeId]) -> ClassicalType:
133
+ if isinstance(node, ast.AST):
134
+ node = id(node)
135
+ qmod_type = self._types[node]
136
+ if not is_classical_type(qmod_type):
137
+ raise ClassiqInternalExpansionError
138
+ return cast(ClassicalType, qmod_type)
139
+
94
140
  def set_var(self, node: Union[ast.AST, QmodExprNodeId], var: HandleBinding) -> None:
95
141
  var = var.collapse()
96
142
  if isinstance(node, ast.AST):
@@ -183,14 +229,15 @@ class QmodAnnotatedExpression:
183
229
  ) -> None:
184
230
  if isinstance(node, ast.AST):
185
231
  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)
232
+ inlined_elements: list[QmodExprNodeId] = []
233
+ for element in elements:
234
+ if isinstance(element, ast.AST):
235
+ element = id(element)
236
+ if element not in self._concatenations:
237
+ inlined_elements.append(element)
238
+ else:
239
+ inlined_elements.extend(self._concatenations.pop(element).elements)
240
+ self._concatenations[node] = ConcatenationAnnotation(elements=inlined_elements)
194
241
 
195
242
  def has_concatenation(self, node: Union[ast.AST, QmodExprNodeId]) -> bool:
196
243
  if isinstance(node, ast.AST):
@@ -205,3 +252,33 @@ class QmodAnnotatedExpression:
205
252
 
206
253
  def get_quantum_vars(self) -> dict[QmodExprNodeId, HandleBinding]:
207
254
  return self._quantum_vars
255
+
256
+ def clear_node_data(self, node: Union[ast.AST, QmodExprNodeId]) -> None:
257
+ if isinstance(node, ast.AST):
258
+ node = id(node)
259
+ self._node_mapping.pop(node, None)
260
+ self._values.pop(node, None)
261
+ self._types.pop(node, None)
262
+ self._classical_vars.pop(node, None)
263
+ self._quantum_vars.pop(node, None)
264
+ qs = self._quantum_subscripts.pop(node, None)
265
+ if qs is not None:
266
+ self.clear_node_data(qs.value)
267
+ self.clear_node_data(qs.index)
268
+ qta = self._quantum_type_attrs.pop(node, None)
269
+ if qta is not None:
270
+ self.clear_node_data(qta.value)
271
+ cnct = self._concatenations.pop(node, None)
272
+ if cnct is not None:
273
+ for element in cnct.elements:
274
+ self.clear_node_data(element)
275
+
276
+ def _add_data_from(self, other: "QmodAnnotatedExpression") -> None:
277
+ self._node_mapping |= other._node_mapping
278
+ self._values |= other._values
279
+ self._types |= other._types
280
+ self._classical_vars |= other._classical_vars
281
+ self._quantum_vars |= other._quantum_vars
282
+ self._quantum_subscripts |= other._quantum_subscripts
283
+ self._quantum_type_attrs |= other._quantum_type_attrs
284
+ self._concatenations |= other._concatenations
@@ -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)
@@ -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 []
@@ -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,37 @@ 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 arg in node.args:
142
+ self.visit(arg)
143
+ for kwarg in node.keywords:
144
+ self.visit(kwarg)
145
+
135
146
  if func_name == "measure":
136
147
  eval_measurement(self._expr_val, node)
137
148
  return
138
149
 
150
+ if func_name in ("min", "max"):
151
+ eval_min_max_op(
152
+ self._expr_val,
153
+ node,
154
+ func_name,
155
+ self._treat_qnum_as_float,
156
+ self._machine_precision,
157
+ )
158
+ return
159
+
160
+ # FIXME: Remove (CLS-3241)
139
161
  if func_name in self._classical_struct_decls:
140
162
  eval_struct_instantiation(
141
163
  self._expr_val, node, self._classical_struct_decls[func_name]
142
164
  )
143
165
  return
144
166
 
167
+ # FIXME: Remove (CLS-3241)
145
168
  if func_name in self._classical_function_callables:
146
169
  if func_name not in self._classical_function_declarations:
147
170
  raise ClassiqInternalExpansionError
@@ -167,6 +190,21 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
167
190
 
168
191
  raise ClassiqExpansionError(f"{func.id!r} is undefined")
169
192
 
193
+ def _eval_piecewise(self, node: ast.Call) -> None:
194
+ if (
195
+ len(node.args) == 0
196
+ or len(node.keywords) != 0
197
+ or not all(
198
+ isinstance(arg, ast.Tuple) and len(arg.elts) == 2 for arg in node.args
199
+ )
200
+ ):
201
+ raise ClassiqExpansionError("Malformed Piecewise expression")
202
+ args = [(arg.elts[0], arg.elts[1]) for arg in cast(list[ast.Tuple], node.args)]
203
+ for value, cond in args:
204
+ self.visit(value)
205
+ self.visit(cond)
206
+ eval_piecewise(self._expr_val, node, args)
207
+
170
208
  def visit_Constant(self, node: ast.Constant) -> None:
171
209
  eval_constant(self._expr_val, node)
172
210
 
@@ -194,12 +232,15 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
194
232
  eval_subscript(self._expr_val, node)
195
233
 
196
234
  def visit_Name(self, node: ast.Name) -> None:
235
+ if node.id in self._scope:
236
+ eval_name(self._expr_val, node, self._scope[node.id])
237
+ return
197
238
  if try_eval_sympy_constant(self._expr_val, node):
198
239
  return
240
+ if try_eval_qmod_literal(self._expr_val, node):
241
+ return
199
242
 
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])
243
+ raise ClassiqExpansionError(f"Variable {node.id!r} is undefined")
203
244
 
204
245
  def visit_List(self, node: ast.List) -> None:
205
246
  super().generic_visit(node)
@@ -209,6 +250,7 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
209
250
  def evaluate_qmod_expression(
210
251
  expr: str,
211
252
  *,
253
+ treat_qnum_as_float: bool = False,
212
254
  machine_precision: int = DEFAULT_MACHINE_PRECISION,
213
255
  classical_struct_declarations: Optional[Sequence[StructDeclaration]] = None,
214
256
  enum_declarations: Optional[Sequence[EnumDeclaration]] = None,
@@ -222,6 +264,7 @@ def evaluate_qmod_expression(
222
264
  expr_value = QmodAnnotatedExpression(expr_ast)
223
265
  QmodExpressionEvaluator(
224
266
  expr_value,
267
+ treat_qnum_as_float=treat_qnum_as_float,
225
268
  machine_precision=machine_precision,
226
269
  classical_struct_declarations=classical_struct_declarations,
227
270
  enum_declarations=enum_declarations,
@@ -1,6 +1,4 @@
1
- from typing import Optional
2
-
3
- from classiq.interface.model.handle_binding import HandleBinding, NestedHandleBinding
1
+ from classiq.interface.model.handle_binding import HandleBinding
4
2
 
5
3
  from classiq.evaluators.qmod_annotated_expression import (
6
4
  QmodAnnotatedExpression,
@@ -8,37 +6,54 @@ from classiq.evaluators.qmod_annotated_expression import (
8
6
  )
9
7
 
10
8
 
11
- def rename_handles_in_expression(
9
+ def replace_expression_vars(
12
10
  expr_val: QmodAnnotatedExpression,
13
11
  renaming: dict[HandleBinding, HandleBinding],
14
- ) -> str:
12
+ ) -> None:
15
13
  if len(renaming) == 0:
16
- return expr_val.to_qmod_expr()
14
+ return
17
15
  all_vars = expr_val.get_classical_vars() | expr_val.get_quantum_vars()
18
16
  for node_id, var in all_vars.items():
19
- renamed_var = _rename_var(renaming, var)
20
- if renamed_var is not None:
21
- expr_val.set_var(node_id, renamed_var)
22
- return expr_val.to_qmod_expr()
17
+ renamed_var = var
18
+ for source, target in renaming.items():
19
+ if renamed_var.name == source.name:
20
+ renamed_var = renamed_var.replace_prefix(source, target)
21
+ if renamed_var is var:
22
+ continue
23
+ node_type = expr_val.get_type(node_id)
24
+ expr_val.clear_node_data(node_id)
25
+ expr_val.set_type(node_id, node_type)
26
+ expr_val.set_var(node_id, renamed_var)
27
+ return
23
28
 
24
29
 
25
- def _rename_var(
26
- renaming: dict[HandleBinding, HandleBinding], var: HandleBinding
27
- ) -> Optional[HandleBinding]:
28
- if (renamed_var := renaming.get(var)) is not None:
29
- return renamed_var
30
- if not isinstance(var, NestedHandleBinding):
31
- return None
32
- renamed_inner = _rename_var(renaming, var.base_handle)
33
- if renamed_inner is None:
34
- return None
35
- return var.model_copy(update=dict(base_handle=renamed_inner))
30
+ def replace_expression_type_attrs(
31
+ expr_val: QmodAnnotatedExpression,
32
+ renaming: dict[tuple[HandleBinding, str], HandleBinding],
33
+ ) -> None:
34
+ if len(renaming) == 0:
35
+ return
36
+ type_attrs = dict(expr_val.get_quantum_type_attributes())
37
+ for node_id, ta in type_attrs.items():
38
+ var = expr_val.get_var(ta.value)
39
+ renamed_var = var
40
+ renamed_attr = ta.attr
41
+ for (source, attr), target in renaming.items():
42
+ if renamed_attr == attr and renamed_var.name == source.name:
43
+ renamed_var = renamed_var.replace_prefix(source, target)
44
+ if renamed_var is var:
45
+ continue
46
+ node_type = expr_val.get_type(node_id)
47
+ expr_val.clear_node_data(node_id)
48
+ expr_val.set_type(node_id, node_type)
49
+ expr_val.set_var(node_id, renamed_var)
50
+ return
36
51
 
37
52
 
38
- def rename_nodes_in_expression(
53
+ def replace_expression_nodes(
39
54
  expr_val: QmodAnnotatedExpression,
40
- renaming: dict[QmodExprNodeId, HandleBinding],
55
+ renaming: dict[QmodExprNodeId, str],
41
56
  ) -> str:
42
57
  for node_id, renamed_var in renaming.items():
43
- expr_val.set_var(node_id, renamed_var)
44
- return expr_val.to_qmod_expr()
58
+ expr_val.set_var(node_id, HandleBinding(name=f"{renamed_var}"))
59
+ return str(expr_val)