classiq 0.87.0__py3-none-any.whl → 0.89.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 (81) hide show
  1. classiq/_internals/config.py +1 -1
  2. classiq/applications/__init__.py +1 -2
  3. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +1 -1
  4. classiq/applications/hamiltonian/pauli_decomposition.py +1 -1
  5. classiq/evaluators/qmod_annotated_expression.py +37 -15
  6. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +0 -5
  7. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +20 -13
  8. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +15 -8
  9. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +39 -16
  10. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +67 -6
  11. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +18 -8
  12. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +8 -0
  13. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +4 -1
  14. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +1 -1
  15. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +1 -1
  16. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +3 -3
  17. classiq/evaluators/qmod_node_evaluators/utils.py +1 -1
  18. classiq/evaluators/qmod_type_inference/classical_type_inference.py +4 -4
  19. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +38 -0
  20. classiq/evaluators/type_type_match.py +1 -1
  21. classiq/execution/execution_session.py +17 -2
  22. classiq/interface/_version.py +1 -1
  23. classiq/interface/analyzer/analysis_params.py +1 -1
  24. classiq/interface/backend/backend_preferences.py +1 -1
  25. classiq/interface/execution/primitives.py +1 -0
  26. classiq/interface/generator/application_apis/__init__.py +0 -1
  27. classiq/interface/generator/arith/register_user_input.py +1 -1
  28. classiq/interface/generator/expressions/atomic_expression_functions.py +0 -2
  29. classiq/interface/generator/function_param_list.py +0 -4
  30. classiq/interface/generator/functions/classical_type.py +32 -3
  31. classiq/interface/generator/functions/function_declaration.py +0 -4
  32. classiq/interface/generator/functions/type_name.py +31 -0
  33. classiq/interface/generator/hardware_efficient_ansatz.py +1 -1
  34. classiq/interface/generator/quantum_function_call.py +3 -3
  35. classiq/interface/generator/transpiler_basis_gates.py +5 -1
  36. classiq/interface/generator/types/builtin_enum_declarations.py +0 -8
  37. classiq/interface/generator/user_defined_function_params.py +0 -3
  38. classiq/interface/ide/ide_data.py +1 -1
  39. classiq/interface/ide/visual_model.py +2 -2
  40. classiq/interface/model/block.py +5 -1
  41. classiq/interface/model/classical_if.py +16 -8
  42. classiq/interface/model/classical_parameter_declaration.py +4 -0
  43. classiq/interface/model/handle_binding.py +1 -1
  44. classiq/interface/model/port_declaration.py +12 -0
  45. classiq/interface/model/quantum_function_declaration.py +12 -0
  46. classiq/interface/model/quantum_lambda_function.py +1 -1
  47. classiq/interface/model/quantum_statement.py +2 -4
  48. classiq/interface/model/quantum_type.py +102 -3
  49. classiq/interface/pretty_print/expression_to_qmod.py +3 -17
  50. classiq/model_expansions/quantum_operations/allocate.py +1 -1
  51. classiq/model_expansions/quantum_operations/handle_evaluator.py +2 -8
  52. classiq/open_library/functions/__init__.py +3 -0
  53. classiq/open_library/functions/lcu.py +2 -2
  54. classiq/open_library/functions/state_preparation.py +182 -5
  55. classiq/qmod/builtins/__init__.py +0 -3
  56. classiq/qmod/builtins/classical_functions.py +0 -28
  57. classiq/qmod/builtins/enums.py +24 -18
  58. classiq/qmod/builtins/functions/__init__.py +0 -5
  59. classiq/qmod/builtins/operations.py +142 -0
  60. classiq/qmod/builtins/structs.py +0 -11
  61. classiq/qmod/native/pretty_printer.py +1 -1
  62. classiq/qmod/pretty_print/expression_to_python.py +3 -6
  63. classiq/qmod/pretty_print/pretty_printer.py +4 -1
  64. classiq/qmod/qmod_variable.py +141 -3
  65. classiq/qmod/semantics/annotation/call_annotation.py +4 -2
  66. classiq/qmod/semantics/annotation/qstruct_annotator.py +20 -5
  67. {classiq-0.87.0.dist-info → classiq-0.89.0.dist-info}/METADATA +4 -4
  68. {classiq-0.87.0.dist-info → classiq-0.89.0.dist-info}/RECORD +69 -81
  69. classiq/applications/finance/__init__.py +0 -15
  70. classiq/interface/finance/__init__.py +0 -0
  71. classiq/interface/finance/finance_modelling_params.py +0 -11
  72. classiq/interface/finance/function_input.py +0 -102
  73. classiq/interface/finance/gaussian_model_input.py +0 -50
  74. classiq/interface/finance/log_normal_model_input.py +0 -40
  75. classiq/interface/finance/model_input.py +0 -22
  76. classiq/interface/generator/application_apis/finance_declarations.py +0 -108
  77. classiq/interface/generator/expressions/enums/__init__.py +0 -0
  78. classiq/interface/generator/expressions/enums/finance_functions.py +0 -12
  79. classiq/interface/generator/finance.py +0 -107
  80. classiq/qmod/builtins/functions/finance.py +0 -34
  81. {classiq-0.87.0.dist-info → classiq-0.89.0.dist-info}/WHEEL +0 -0
@@ -24,7 +24,7 @@ class Configuration(BaseModel):
24
24
 
25
25
  host: pydantic.AnyHttpUrl = pydantic.Field(..., description="Classiq backend URI.")
26
26
  ide: pydantic.AnyHttpUrl = pydantic.Field(
27
- default=DEFAULT_IDE_FE_APP, description="Classiq IDE URI."
27
+ default=pydantic.AnyHttpUrl(DEFAULT_IDE_FE_APP), description="Classiq IDE URI."
28
28
  )
29
29
  should_check_host: bool = pydantic.Field(
30
30
  default=True, description="Should check backend URI and version."
@@ -1,9 +1,8 @@
1
- from classiq.applications import chemistry, combinatorial_optimization, finance, qsvm
1
+ from classiq.applications import chemistry, combinatorial_optimization, qsvm
2
2
 
3
3
  __all__ = [
4
4
  "chemistry",
5
5
  "combinatorial_optimization",
6
- "finance",
7
6
  "qsvm",
8
7
  ]
9
8
 
@@ -43,7 +43,7 @@ def compute_qaoa_initial_point(
43
43
 
44
44
  beta_params: np.ndarray = np.linspace(1, 0, repetitions) * time_step
45
45
  gamma_params: np.ndarray = np.linspace(0, 1, repetitions) * time_step
46
- return list(itertools.chain(*zip(gamma_params, beta_params)))
46
+ return [float(x) for x in itertools.chain(*zip(gamma_params, beta_params))]
47
47
 
48
48
 
49
49
  def pyo_model_to_hamiltonian(
@@ -137,7 +137,7 @@ def hamiltonian_to_matrix(
137
137
  hamiltonian = _sparse_pauli_to_list(hamiltonian)
138
138
  matrix = np.zeros(
139
139
  [2 ** len(hamiltonian[0].pauli), 2 ** len(hamiltonian[0].pauli)],
140
- dtype=np.complex_,
140
+ dtype=np.complex128,
141
141
  )
142
142
  for p in hamiltonian:
143
143
  matrix += p.coefficient * pauli_string_to_mat(p.pauli)
@@ -1,5 +1,5 @@
1
1
  import ast
2
- from collections.abc import Sequence
2
+ from collections.abc import Mapping, Sequence
3
3
  from dataclasses import dataclass
4
4
  from enum import Enum
5
5
  from typing import Any, Union, cast
@@ -30,7 +30,7 @@ class QuantumSubscriptAnnotation:
30
30
 
31
31
  @dataclass(frozen=True)
32
32
  class QuantumTypeAttributeAnnotation:
33
- value: QmodExprNodeId
33
+ value: HandleBinding
34
34
  attr: str
35
35
 
36
36
 
@@ -45,7 +45,7 @@ def qmod_val_to_str(val: Any) -> str:
45
45
  f"{field_name}={qmod_val_to_str(field_val)}"
46
46
  for field_name, field_val in val.fields.items()
47
47
  )
48
- return f"{val.struct_declaration.name}({fields})"
48
+ return f"struct_literal({val.struct_declaration.name}, {fields})"
49
49
  if isinstance(val, list):
50
50
  return f"[{', '.join(qmod_val_to_str(item) for item in val)}]"
51
51
  if isinstance(val, Enum):
@@ -82,9 +82,13 @@ class QmodAnnotatedExpression:
82
82
  QmodExprNodeId, QuantumTypeAttributeAnnotation
83
83
  ] = {}
84
84
  self._concatenations: dict[QmodExprNodeId, ConcatenationAnnotation] = {}
85
+ self._locked = False
86
+
87
+ def print_by_node(self, node: ast.AST) -> str:
88
+ return ast.unparse(_ExprInliner(self).visit(node))
85
89
 
86
90
  def __str__(self) -> str:
87
- return ast.unparse(_ExprInliner(self).visit(self.root))
91
+ return self.print_by_node(self.root)
88
92
 
89
93
  def has_node(self, node_id: QmodExprNodeId) -> bool:
90
94
  return node_id in self._node_mapping
@@ -93,6 +97,8 @@ class QmodAnnotatedExpression:
93
97
  return self._node_mapping[node_id]
94
98
 
95
99
  def set_value(self, node: Union[ast.AST, QmodExprNodeId], value: Any) -> None:
100
+ if self._locked:
101
+ raise ClassiqInternalExpansionError("QAE is locked")
96
102
  if isinstance(node, ast.AST):
97
103
  node = id(node)
98
104
  self._values[node] = value
@@ -110,6 +116,8 @@ class QmodAnnotatedExpression:
110
116
  def set_type(
111
117
  self, node: Union[ast.AST, QmodExprNodeId], qmod_type: QmodType
112
118
  ) -> None:
119
+ if self._locked:
120
+ raise ClassiqInternalExpansionError("QAE is locked")
113
121
  if isinstance(node, ast.AST):
114
122
  node_id = id(node)
115
123
  self._node_mapping[node_id] = node
@@ -138,6 +146,8 @@ class QmodAnnotatedExpression:
138
146
  return cast(ClassicalType, qmod_type)
139
147
 
140
148
  def set_var(self, node: Union[ast.AST, QmodExprNodeId], var: HandleBinding) -> None:
149
+ if self._locked:
150
+ raise ClassiqInternalExpansionError("QAE is locked")
141
151
  var = var.collapse()
142
152
  if isinstance(node, ast.AST):
143
153
  node = id(node)
@@ -165,6 +175,8 @@ class QmodAnnotatedExpression:
165
175
  return node in self._quantum_vars
166
176
 
167
177
  def remove_var(self, node: Union[ast.AST, QmodExprNodeId]) -> None:
178
+ if self._locked:
179
+ raise ClassiqInternalExpansionError("QAE is locked")
168
180
  if isinstance(node, ast.AST):
169
181
  node = id(node)
170
182
  if node in self._classical_vars:
@@ -178,6 +190,8 @@ class QmodAnnotatedExpression:
178
190
  value: Union[ast.AST, QmodExprNodeId],
179
191
  index: Union[ast.AST, QmodExprNodeId],
180
192
  ) -> None:
193
+ if self._locked:
194
+ raise ClassiqInternalExpansionError("QAE is locked")
181
195
  if isinstance(node, ast.AST):
182
196
  node = id(node)
183
197
  if isinstance(value, ast.AST):
@@ -195,19 +209,19 @@ class QmodAnnotatedExpression:
195
209
 
196
210
  def get_quantum_subscripts(
197
211
  self,
198
- ) -> dict[QmodExprNodeId, QuantumSubscriptAnnotation]:
212
+ ) -> Mapping[QmodExprNodeId, QuantumSubscriptAnnotation]:
199
213
  return self._quantum_subscripts
200
214
 
201
215
  def set_quantum_type_attr(
202
216
  self,
203
217
  node: Union[ast.AST, QmodExprNodeId],
204
- value: Union[ast.AST, QmodExprNodeId],
218
+ value: HandleBinding,
205
219
  attr: str,
206
220
  ) -> None:
221
+ if self._locked:
222
+ raise ClassiqInternalExpansionError("QAE is locked")
207
223
  if isinstance(node, ast.AST):
208
224
  node = id(node)
209
- if isinstance(value, ast.AST):
210
- value = id(value)
211
225
  self._quantum_type_attrs[node] = QuantumTypeAttributeAnnotation(
212
226
  value=value, attr=attr
213
227
  )
@@ -219,7 +233,7 @@ class QmodAnnotatedExpression:
219
233
 
220
234
  def get_quantum_type_attributes(
221
235
  self,
222
- ) -> dict[QmodExprNodeId, QuantumTypeAttributeAnnotation]:
236
+ ) -> Mapping[QmodExprNodeId, QuantumTypeAttributeAnnotation]:
223
237
  return self._quantum_type_attrs
224
238
 
225
239
  def set_concatenation(
@@ -227,6 +241,8 @@ class QmodAnnotatedExpression:
227
241
  node: Union[ast.AST, QmodExprNodeId],
228
242
  elements: Sequence[Union[ast.AST, QmodExprNodeId]],
229
243
  ) -> None:
244
+ if self._locked:
245
+ raise ClassiqInternalExpansionError("QAE is locked")
230
246
  if isinstance(node, ast.AST):
231
247
  node = id(node)
232
248
  inlined_elements: list[QmodExprNodeId] = []
@@ -244,13 +260,13 @@ class QmodAnnotatedExpression:
244
260
  node = id(node)
245
261
  return node in self._concatenations
246
262
 
247
- def get_concatenations(self) -> dict[QmodExprNodeId, ConcatenationAnnotation]:
263
+ def get_concatenations(self) -> Mapping[QmodExprNodeId, ConcatenationAnnotation]:
248
264
  return self._concatenations
249
265
 
250
- def get_classical_vars(self) -> dict[QmodExprNodeId, HandleBinding]:
266
+ def get_classical_vars(self) -> Mapping[QmodExprNodeId, HandleBinding]:
251
267
  return self._classical_vars
252
268
 
253
- def get_quantum_vars(self) -> dict[QmodExprNodeId, HandleBinding]:
269
+ def get_quantum_vars(self) -> Mapping[QmodExprNodeId, HandleBinding]:
254
270
  return self._quantum_vars
255
271
 
256
272
  def clear_node_data(self, node: Union[ast.AST, QmodExprNodeId]) -> None:
@@ -265,9 +281,7 @@ class QmodAnnotatedExpression:
265
281
  if qs is not None:
266
282
  self.clear_node_data(qs.value)
267
283
  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)
284
+ self._quantum_type_attrs.pop(node, None)
271
285
  cnct = self._concatenations.pop(node, None)
272
286
  if cnct is not None:
273
287
  for element in cnct.elements:
@@ -282,3 +296,11 @@ class QmodAnnotatedExpression:
282
296
  self._quantum_subscripts |= other._quantum_subscripts
283
297
  self._quantum_type_attrs |= other._quantum_type_attrs
284
298
  self._concatenations |= other._concatenations
299
+
300
+ def clone(self) -> "QmodAnnotatedExpression":
301
+ expr_val = QmodAnnotatedExpression(self.root)
302
+ expr_val._add_data_from(self)
303
+ return expr_val
304
+
305
+ def lock(self) -> None:
306
+ self._locked = True
@@ -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
@@ -85,7 +85,7 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
85
85
  Sequence[ClassicalFunctionDeclaration]
86
86
  ] = None,
87
87
  classical_function_callables: Optional[Mapping[str, Callable]] = None,
88
- scope: Optional[dict[str, Any]] = None,
88
+ scope: Optional[Mapping[str, Any]] = None,
89
89
  ) -> None:
90
90
  self._expr_val = expr_val
91
91
  self._treat_qnum_as_float = treat_qnum_as_float
@@ -103,8 +103,8 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
103
103
 
104
104
  def visit(self, node: ast.AST) -> None:
105
105
  if not isinstance(node, _SUPPORTED_NODES):
106
- raise ClassiqInternalExpansionError(
107
- 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"
108
108
  )
109
109
  super().visit(node)
110
110
 
@@ -138,11 +138,23 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
138
138
  self._eval_piecewise(node)
139
139
  return
140
140
 
141
- for arg in node.args:
142
- self.visit(arg)
143
141
  for kwarg in node.keywords:
144
142
  self.visit(kwarg)
145
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
+
146
158
  if func_name == "measure":
147
159
  eval_measurement(self._expr_val, node)
148
160
  return
@@ -157,13 +169,6 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
157
169
  )
158
170
  return
159
171
 
160
- # FIXME: Remove (CLS-3241)
161
- if func_name in self._classical_struct_decls:
162
- eval_struct_instantiation(
163
- self._expr_val, node, self._classical_struct_decls[func_name]
164
- )
165
- return
166
-
167
172
  # FIXME: Remove (CLS-3241)
168
173
  if func_name in self._classical_function_callables:
169
174
  if func_name not in self._classical_function_declarations:
@@ -176,6 +181,7 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
176
181
  )
177
182
  return
178
183
 
184
+ # FIXME: Remove (CLS-3241)
179
185
  if func_name in self._classical_function_declarations:
180
186
  eval_symbolic_function(
181
187
  self._expr_val, node, self._classical_function_declarations[func_name]
@@ -258,7 +264,7 @@ def evaluate_qmod_expression(
258
264
  Sequence[ClassicalFunctionDeclaration]
259
265
  ] = None,
260
266
  classical_function_callables: Optional[Mapping[str, Callable]] = None,
261
- scope: Optional[dict[str, Any]] = None,
267
+ scope: Optional[Mapping[str, Any]] = None,
262
268
  ) -> QmodAnnotatedExpression:
263
269
  expr_ast = ast.parse(expr, mode="eval").body
264
270
  expr_value = QmodAnnotatedExpression(expr_ast)
@@ -272,4 +278,5 @@ def evaluate_qmod_expression(
272
278
  classical_function_callables=classical_function_callables,
273
279
  scope=scope,
274
280
  ).visit(expr_value.root)
281
+ expr_value.lock()
275
282
  return expr_value
@@ -9,10 +9,12 @@ from classiq.evaluators.qmod_annotated_expression import (
9
9
  def replace_expression_vars(
10
10
  expr_val: QmodAnnotatedExpression,
11
11
  renaming: dict[HandleBinding, HandleBinding],
12
- ) -> None:
12
+ ) -> QmodAnnotatedExpression:
13
+ expr_val = expr_val.clone()
13
14
  if len(renaming) == 0:
14
- return
15
- all_vars = expr_val.get_classical_vars() | expr_val.get_quantum_vars()
15
+ expr_val.lock()
16
+ return expr_val
17
+ all_vars = dict(expr_val.get_classical_vars()) | dict(expr_val.get_quantum_vars())
16
18
  for node_id, var in all_vars.items():
17
19
  renamed_var = var
18
20
  for source, target in renaming.items():
@@ -24,18 +26,21 @@ def replace_expression_vars(
24
26
  expr_val.clear_node_data(node_id)
25
27
  expr_val.set_type(node_id, node_type)
26
28
  expr_val.set_var(node_id, renamed_var)
27
- return
29
+ expr_val.lock()
30
+ return expr_val
28
31
 
29
32
 
30
33
  def replace_expression_type_attrs(
31
34
  expr_val: QmodAnnotatedExpression,
32
35
  renaming: dict[tuple[HandleBinding, str], HandleBinding],
33
- ) -> None:
36
+ ) -> QmodAnnotatedExpression:
37
+ expr_val = expr_val.clone()
34
38
  if len(renaming) == 0:
35
- return
39
+ expr_val.lock()
40
+ return expr_val
36
41
  type_attrs = dict(expr_val.get_quantum_type_attributes())
37
42
  for node_id, ta in type_attrs.items():
38
- var = expr_val.get_var(ta.value)
43
+ var = ta.value
39
44
  renamed_var = var
40
45
  renamed_attr = ta.attr
41
46
  for (source, attr), target in renaming.items():
@@ -47,13 +52,15 @@ def replace_expression_type_attrs(
47
52
  expr_val.clear_node_data(node_id)
48
53
  expr_val.set_type(node_id, node_type)
49
54
  expr_val.set_var(node_id, renamed_var)
50
- return
55
+ expr_val.lock()
56
+ return expr_val
51
57
 
52
58
 
53
59
  def replace_expression_nodes(
54
60
  expr_val: QmodAnnotatedExpression,
55
61
  renaming: dict[QmodExprNodeId, str],
56
62
  ) -> str:
63
+ expr_val = expr_val.clone()
57
64
  for node_id, renamed_var in renaming.items():
58
65
  expr_val.set_var(node_id, HandleBinding(name=f"{renamed_var}"))
59
66
  return str(expr_val)
@@ -1,10 +1,12 @@
1
1
  import ast
2
- from typing import Any, cast
2
+ from typing import Any, Callable, TypeVar, cast
3
3
 
4
4
  import sympy
5
5
 
6
6
  from classiq.interface.exceptions import ClassiqInternalExpansionError
7
- from classiq.interface.model.handle_binding import HandleBinding
7
+ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_instance import (
8
+ QmodStructInstance,
9
+ )
8
10
 
9
11
  from classiq.evaluators.qmod_annotated_expression import (
10
12
  QmodAnnotatedExpression,
@@ -37,13 +39,15 @@ _SYMPY_WRAPPERS = {
37
39
  do_div.__name__: do_div,
38
40
  }
39
41
 
42
+ _PY_NODE = TypeVar("_PY_NODE", bound=ast.AST)
43
+
40
44
 
41
45
  class _VarMaskTransformer(OutOfPlaceNodeTransformer):
42
46
  def __init__(self, expr_val: QmodAnnotatedExpression) -> None:
43
47
  self._expr_val = expr_val
44
48
  self._mask_id = 0
45
49
  self.masks: dict[str, QmodExprNodeId] = {}
46
- self._assigned_masks: dict[HandleBinding, str] = {}
50
+ self._assigned_masks: dict[Any, str] = {}
47
51
 
48
52
  def _create_mask(self) -> str:
49
53
  mask = f"x{self._mask_id}"
@@ -52,27 +56,44 @@ class _VarMaskTransformer(OutOfPlaceNodeTransformer):
52
56
 
53
57
  def visit(self, node: ast.AST) -> ast.AST:
54
58
  if self._expr_val.has_value(node):
55
- return ast.parse(str(self._expr_val.get_value(node)))
59
+ val = self._expr_val.get_value(node)
60
+ if not isinstance(val, (list, QmodStructInstance)):
61
+ return ast.Constant(value=val)
62
+ mask = self._create_mask()
63
+ self.masks[mask] = id(node)
64
+ return ast.Name(id=mask)
56
65
  if self._expr_val.has_var(node):
57
66
  var = self._expr_val.get_var(node)
58
- if var in self._assigned_masks:
59
- mask = self._assigned_masks[var]
60
- else:
61
- mask = self._create_mask()
62
- self._assigned_masks[var] = mask
63
- self.masks[mask] = id(node)
64
- return ast.Name(id=mask)
65
- if self._expr_val.has_quantum_subscript(node):
67
+ var_str = str(var.collapse())
68
+ if var_str in self._assigned_masks:
69
+ return ast.Name(id=self._assigned_masks[var_str])
66
70
  mask = self._create_mask()
71
+ self._assigned_masks[var_str] = mask
67
72
  self.masks[mask] = id(node)
68
73
  return ast.Name(id=mask)
69
74
  return super().visit(node)
70
75
 
71
- def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
72
- mask = self._create_mask()
76
+ def _reduce_node(
77
+ self, node: _PY_NODE, inner_node: ast.AST, key: Callable[[_PY_NODE], str]
78
+ ) -> ast.AST:
79
+ new_value = self.visit(inner_node)
80
+ if isinstance(new_value, ast.Name):
81
+ mask_key = (new_value.id, key(node))
82
+ if mask_key in self._assigned_masks:
83
+ return ast.Name(self._assigned_masks[mask_key])
84
+ mask = self._create_mask()
85
+ self._assigned_masks[mask_key] = mask
86
+ else:
87
+ mask = self._create_mask()
73
88
  self.masks[mask] = id(node)
74
89
  return ast.Name(id=mask)
75
90
 
91
+ def visit_Attribute(self, node: ast.Attribute) -> ast.AST:
92
+ return self._reduce_node(node, node.value, lambda n: n.attr)
93
+
94
+ def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
95
+ return self._reduce_node(node, node.value, lambda n: ast.unparse(node.slice))
96
+
76
97
 
77
98
  class _InverseVarMaskTransformer(OutOfPlaceNodeTransformer):
78
99
  def __init__(
@@ -230,12 +251,14 @@ class _InverseSympyCompatibilityTransformer(OutOfPlaceNodeTransformer):
230
251
  if func == BitwiseAnd.__name__:
231
252
  return ast.BinOp(left=node.args[0], op=ast.BitAnd(), right=node.args[1])
232
253
 
233
- if func == "Mod":
234
- return ast.BinOp(left=node.args[0], op=ast.Mod(), right=node.args[1])
254
+ if func == "Abs":
255
+ node.func.id = "abs"
235
256
  if func == "Max":
236
257
  node.func.id = "max"
237
258
  elif func == "Min":
238
259
  node.func.id = "min"
260
+ if func == "Mod":
261
+ return ast.BinOp(left=node.args[0], op=ast.Mod(), right=node.args[1])
239
262
 
240
263
  return node
241
264
 
@@ -5,6 +5,7 @@ from classiq.interface.exceptions import (
5
5
  ClassiqExpansionError,
6
6
  ClassiqInternalExpansionError,
7
7
  )
8
+ from classiq.interface.generator.expressions.expression import Expression
8
9
  from classiq.interface.generator.expressions.proxies.classical.qmod_struct_instance import (
9
10
  QmodStructInstance,
10
11
  )
@@ -15,7 +16,12 @@ from classiq.interface.generator.functions.classical_type import (
15
16
  Integer,
16
17
  )
17
18
  from classiq.interface.generator.functions.type_name import TypeName
18
- from classiq.interface.model.handle_binding import FieldHandleBinding
19
+ from classiq.interface.model.handle_binding import (
20
+ FieldHandleBinding,
21
+ HandleBinding,
22
+ SlicedHandleBinding,
23
+ SubscriptHandleBinding,
24
+ )
19
25
  from classiq.interface.model.quantum_type import (
20
26
  QuantumBitvector,
21
27
  QuantumNumeric,
@@ -26,6 +32,46 @@ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
26
32
  from classiq.evaluators.qmod_node_evaluators.utils import QmodType
27
33
 
28
34
 
35
+ def _get_symbolic_quantum_var(
36
+ expr_val: QmodAnnotatedExpression, node: ast.AST
37
+ ) -> HandleBinding:
38
+ if expr_val.has_quantum_var(node):
39
+ var = expr_val.get_var(node)
40
+ expr_val.remove_var(node)
41
+ return var
42
+ if isinstance(node, ast.Attribute):
43
+ return FieldHandleBinding(
44
+ base_handle=_get_symbolic_quantum_var(expr_val, node.value),
45
+ field=node.attr,
46
+ )
47
+ if isinstance(node, ast.Subscript):
48
+ base_var = _get_symbolic_quantum_var(expr_val, node.value)
49
+ slice_ = node.slice
50
+ if isinstance(slice_, ast.Slice):
51
+ if slice_.lower is None or slice_.upper is None or slice_.step is not None:
52
+ raise ClassiqInternalExpansionError("Illegal slice")
53
+ return SlicedHandleBinding(
54
+ base_handle=base_var,
55
+ start=Expression(expr=expr_val.print_by_node(slice_.lower)),
56
+ end=Expression(expr=expr_val.print_by_node(slice_.upper)),
57
+ )
58
+ else:
59
+ return SubscriptHandleBinding(
60
+ base_handle=base_var,
61
+ index=Expression(expr=expr_val.print_by_node(slice_)),
62
+ )
63
+ raise ClassiqInternalExpansionError("Symbolic variable construction failed")
64
+
65
+
66
+ def _remove_quantum_var(expr_val: QmodAnnotatedExpression, node: ast.AST) -> None:
67
+ if expr_val.has_quantum_var(node):
68
+ expr_val.remove_var(node)
69
+ elif isinstance(node, (ast.Attribute, ast.Subscript)):
70
+ _remove_quantum_var(expr_val, node.value)
71
+ else:
72
+ raise ClassiqInternalExpansionError("Failed to remove quantum var")
73
+
74
+
29
75
  def _eval_type_attribute(
30
76
  expr_val: QmodAnnotatedExpression, node: ast.Attribute
31
77
  ) -> None:
@@ -37,15 +83,22 @@ def _eval_type_attribute(
37
83
  expr_val.set_type(node, Integer())
38
84
  if subject_type.has_size_in_bits:
39
85
  expr_val.set_value(node, subject_type.size_in_bits)
86
+ _remove_quantum_var(expr_val, subject)
40
87
  else:
41
- expr_val.set_quantum_type_attr(node, subject, attr)
88
+ expr_val.set_quantum_type_attr(
89
+ node, _get_symbolic_quantum_var(expr_val, subject), attr
90
+ )
42
91
  return
43
92
  if isinstance(subject_type, (ClassicalArray, QuantumBitvector)) and attr == "len":
44
93
  expr_val.set_type(node, Integer())
45
- if subject_type.has_length:
94
+ if subject_type.has_constant_length:
46
95
  expr_val.set_value(node, subject_type.length_value)
96
+ if isinstance(subject_type, QuantumBitvector):
97
+ _remove_quantum_var(expr_val, subject)
47
98
  elif isinstance(subject_type, QuantumType):
48
- expr_val.set_quantum_type_attr(node, subject, attr)
99
+ expr_val.set_quantum_type_attr(
100
+ node, _get_symbolic_quantum_var(expr_val, subject), attr
101
+ )
49
102
  return
50
103
  if isinstance(subject_type, ClassicalTuple) and attr == "len":
51
104
  expr_val.set_type(node, Integer())
@@ -56,19 +109,27 @@ def _eval_type_attribute(
56
109
  expr_val.set_type(node, Bool())
57
110
  if subject_type.has_sign:
58
111
  expr_val.set_value(node, subject_type.sign_value)
112
+ _remove_quantum_var(expr_val, subject)
59
113
  elif subject_type.has_size_in_bits:
60
114
  expr_val.set_value(node, False)
115
+ _remove_quantum_var(expr_val, subject)
61
116
  else:
62
- expr_val.set_quantum_type_attr(node, subject, attr)
117
+ expr_val.set_quantum_type_attr(
118
+ node, _get_symbolic_quantum_var(expr_val, subject), attr
119
+ )
63
120
  return
64
121
  if attr == "fraction_digits":
65
122
  expr_val.set_type(node, Integer())
66
123
  if subject_type.has_fraction_digits:
67
124
  expr_val.set_value(node, subject_type.fraction_digits_value)
125
+ _remove_quantum_var(expr_val, subject)
68
126
  elif subject_type.has_size_in_bits:
69
127
  expr_val.set_value(node, 0)
128
+ _remove_quantum_var(expr_val, subject)
70
129
  else:
71
- expr_val.set_quantum_type_attr(node, subject, attr)
130
+ expr_val.set_quantum_type_attr(
131
+ node, _get_symbolic_quantum_var(expr_val, subject), attr
132
+ )
72
133
  return
73
134
  raise ClassiqExpansionError(
74
135
  f"{subject_type.raw_qmod_type_name} has no attribute {attr!r}"
@@ -30,6 +30,9 @@ from classiq.evaluators.qmod_node_evaluators.utils import (
30
30
  element_types,
31
31
  is_classical_integer,
32
32
  )
33
+ from classiq.evaluators.qmod_type_inference.classical_type_inference import (
34
+ infer_classical_type,
35
+ )
33
36
 
34
37
  # These sympy functions are not declared as int funcs for some reason...
35
38
  INTEGER_FUNCTION_OVERRIDE = {"floor", "ceiling"}
@@ -169,7 +172,9 @@ def eval_function(
169
172
  cast(str, kwarg_name): expr_val.get_value(kwarg_value)
170
173
  for kwarg_name, kwarg_value in kwargs.items()
171
174
  }
172
- expr_val.set_value(node, func(*arg_values, **kwarg_values))
175
+ ret_val = func(*arg_values, **kwarg_values)
176
+ expr_val.set_type(node, infer_classical_type(ret_val))
177
+ expr_val.set_value(node, ret_val)
173
178
 
174
179
 
175
180
  def try_eval_sympy_function(
@@ -261,10 +266,10 @@ def try_eval_builtin_function(
261
266
  *[expr_val.get_value(arg) for arg in node.args]
262
267
  )
263
268
  ret_val: Any
264
- if args_are_int:
265
- ret_val = int(sympy_val)
266
- elif isinstance(sympy_val, sympy.Expr) and sympy_val.is_imaginary:
269
+ if isinstance(sympy_val, sympy.Expr) and sympy_val.is_imaginary:
267
270
  ret_val = complex(sympy_val)
271
+ elif args_are_int:
272
+ ret_val = int(sympy_val)
268
273
  else:
269
274
  ret_val = float(sympy_val)
270
275
  expr_val.set_value(node, ret_val)
@@ -287,9 +292,14 @@ def try_eval_builtin_function(
287
292
  _validate_no_kwargs(node)
288
293
  expr_val.set_type(node, Real())
289
294
  if args_have_values:
290
- expr_val.set_value(
291
- node, sympy.sqrt(*[expr_val.get_value(arg) for arg in node.args])
292
- )
295
+ sympy_val = sympy.sqrt(*[expr_val.get_value(arg) for arg in node.args])
296
+ if isinstance(sympy_val, sympy.Expr) and sympy_val.is_imaginary:
297
+ ret_val = complex(sympy_val)
298
+ elif float(sympy_val) == int(sympy_val):
299
+ ret_val = int(sympy_val)
300
+ else:
301
+ ret_val = float(sympy_val)
302
+ expr_val.set_value(node, ret_val)
293
303
  return True
294
304
 
295
305
  if func_name == "abs":
@@ -301,7 +311,7 @@ def try_eval_builtin_function(
301
311
  expr_val.set_type(node, ret_type)
302
312
  if args_have_values:
303
313
  expr_val.set_value(
304
- node, sympy.Abs(*[expr_val.get_value(arg) for arg in node.args])
314
+ node, abs(*[expr_val.get_value(arg) for arg in node.args])
305
315
  )
306
316
  return True
307
317
 
@@ -100,12 +100,20 @@ def eval_compare(expr_val: QmodAnnotatedExpression, node: ast.Compare) -> None:
100
100
  elif isinstance(op, ast.NotEq):
101
101
  expr_val.set_value(node, left_value != right_value)
102
102
  elif isinstance(op, ast.Lt):
103
+ if isinstance(left_value, complex) or isinstance(right_value, complex):
104
+ raise ClassiqExpansionError("Inequality with a complex number")
103
105
  expr_val.set_value(node, left_value < right_value)
104
106
  elif isinstance(op, ast.Gt):
107
+ if isinstance(left_value, complex) or isinstance(right_value, complex):
108
+ raise ClassiqExpansionError("Inequality with a complex number")
105
109
  expr_val.set_value(node, left_value > right_value)
106
110
  elif isinstance(op, ast.LtE):
111
+ if isinstance(left_value, complex) or isinstance(right_value, complex):
112
+ raise ClassiqExpansionError("Inequality with a complex number")
107
113
  expr_val.set_value(node, left_value <= right_value)
108
114
  elif isinstance(op, ast.GtE):
115
+ if isinstance(left_value, complex) or isinstance(right_value, complex):
116
+ raise ClassiqExpansionError("Inequality with a complex number")
109
117
  expr_val.set_value(node, left_value >= right_value)
110
118
  else:
111
119
  raise ClassiqExpansionError(f"Unsupported comparison {type(op).__name__!r}")