classiq 0.87.0__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 (67) hide show
  1. classiq/applications/__init__.py +1 -2
  2. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +1 -1
  3. classiq/applications/hamiltonian/pauli_decomposition.py +1 -1
  4. classiq/evaluators/qmod_annotated_expression.py +30 -7
  5. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +0 -5
  6. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +18 -11
  7. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +14 -7
  8. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +39 -16
  9. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +1 -1
  10. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +18 -8
  11. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +8 -0
  12. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +4 -1
  13. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +1 -1
  14. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +1 -1
  15. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +3 -3
  16. classiq/evaluators/qmod_node_evaluators/utils.py +1 -1
  17. classiq/evaluators/qmod_type_inference/classical_type_inference.py +4 -4
  18. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +38 -0
  19. classiq/evaluators/type_type_match.py +1 -1
  20. classiq/execution/execution_session.py +17 -2
  21. classiq/interface/_version.py +1 -1
  22. classiq/interface/execution/primitives.py +1 -0
  23. classiq/interface/generator/application_apis/__init__.py +0 -1
  24. classiq/interface/generator/expressions/atomic_expression_functions.py +0 -2
  25. classiq/interface/generator/function_param_list.py +0 -4
  26. classiq/interface/generator/functions/classical_type.py +8 -0
  27. classiq/interface/generator/functions/type_name.py +6 -0
  28. classiq/interface/generator/transpiler_basis_gates.py +5 -1
  29. classiq/interface/generator/types/builtin_enum_declarations.py +0 -8
  30. classiq/interface/model/classical_if.py +16 -8
  31. classiq/interface/model/classical_parameter_declaration.py +4 -0
  32. classiq/interface/model/port_declaration.py +12 -0
  33. classiq/interface/model/quantum_function_declaration.py +12 -0
  34. classiq/interface/model/quantum_type.py +56 -0
  35. classiq/interface/pretty_print/expression_to_qmod.py +3 -17
  36. classiq/model_expansions/quantum_operations/allocate.py +1 -1
  37. classiq/model_expansions/quantum_operations/handle_evaluator.py +2 -8
  38. classiq/open_library/functions/__init__.py +1 -0
  39. classiq/open_library/functions/lcu.py +2 -2
  40. classiq/open_library/functions/state_preparation.py +47 -5
  41. classiq/qmod/builtins/__init__.py +0 -3
  42. classiq/qmod/builtins/classical_functions.py +0 -28
  43. classiq/qmod/builtins/enums.py +24 -18
  44. classiq/qmod/builtins/functions/__init__.py +0 -5
  45. classiq/qmod/builtins/operations.py +142 -0
  46. classiq/qmod/builtins/structs.py +0 -11
  47. classiq/qmod/native/pretty_printer.py +1 -1
  48. classiq/qmod/pretty_print/expression_to_python.py +3 -6
  49. classiq/qmod/pretty_print/pretty_printer.py +4 -1
  50. classiq/qmod/qmod_variable.py +94 -2
  51. classiq/qmod/semantics/annotation/call_annotation.py +4 -2
  52. classiq/qmod/semantics/annotation/qstruct_annotator.py +20 -5
  53. {classiq-0.87.0.dist-info → classiq-0.88.0.dist-info}/METADATA +3 -3
  54. {classiq-0.87.0.dist-info → classiq-0.88.0.dist-info}/RECORD +55 -67
  55. classiq/applications/finance/__init__.py +0 -15
  56. classiq/interface/finance/__init__.py +0 -0
  57. classiq/interface/finance/finance_modelling_params.py +0 -11
  58. classiq/interface/finance/function_input.py +0 -102
  59. classiq/interface/finance/gaussian_model_input.py +0 -50
  60. classiq/interface/finance/log_normal_model_input.py +0 -40
  61. classiq/interface/finance/model_input.py +0 -22
  62. classiq/interface/generator/application_apis/finance_declarations.py +0 -108
  63. classiq/interface/generator/expressions/enums/__init__.py +0 -0
  64. classiq/interface/generator/expressions/enums/finance_functions.py +0 -12
  65. classiq/interface/generator/finance.py +0 -107
  66. classiq/qmod/builtins/functions/finance.py +0 -34
  67. {classiq-0.87.0.dist-info → classiq-0.88.0.dist-info}/WHEEL +0 -0
@@ -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
@@ -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,6 +82,7 @@ class QmodAnnotatedExpression:
82
82
  QmodExprNodeId, QuantumTypeAttributeAnnotation
83
83
  ] = {}
84
84
  self._concatenations: dict[QmodExprNodeId, ConcatenationAnnotation] = {}
85
+ self._locked = False
85
86
 
86
87
  def __str__(self) -> str:
87
88
  return ast.unparse(_ExprInliner(self).visit(self.root))
@@ -93,6 +94,8 @@ class QmodAnnotatedExpression:
93
94
  return self._node_mapping[node_id]
94
95
 
95
96
  def set_value(self, node: Union[ast.AST, QmodExprNodeId], value: Any) -> None:
97
+ if self._locked:
98
+ raise ClassiqInternalExpansionError("QAE is locked")
96
99
  if isinstance(node, ast.AST):
97
100
  node = id(node)
98
101
  self._values[node] = value
@@ -110,6 +113,8 @@ class QmodAnnotatedExpression:
110
113
  def set_type(
111
114
  self, node: Union[ast.AST, QmodExprNodeId], qmod_type: QmodType
112
115
  ) -> None:
116
+ if self._locked:
117
+ raise ClassiqInternalExpansionError("QAE is locked")
113
118
  if isinstance(node, ast.AST):
114
119
  node_id = id(node)
115
120
  self._node_mapping[node_id] = node
@@ -138,6 +143,8 @@ class QmodAnnotatedExpression:
138
143
  return cast(ClassicalType, qmod_type)
139
144
 
140
145
  def set_var(self, node: Union[ast.AST, QmodExprNodeId], var: HandleBinding) -> None:
146
+ if self._locked:
147
+ raise ClassiqInternalExpansionError("QAE is locked")
141
148
  var = var.collapse()
142
149
  if isinstance(node, ast.AST):
143
150
  node = id(node)
@@ -165,6 +172,8 @@ class QmodAnnotatedExpression:
165
172
  return node in self._quantum_vars
166
173
 
167
174
  def remove_var(self, node: Union[ast.AST, QmodExprNodeId]) -> None:
175
+ if self._locked:
176
+ raise ClassiqInternalExpansionError("QAE is locked")
168
177
  if isinstance(node, ast.AST):
169
178
  node = id(node)
170
179
  if node in self._classical_vars:
@@ -178,6 +187,8 @@ class QmodAnnotatedExpression:
178
187
  value: Union[ast.AST, QmodExprNodeId],
179
188
  index: Union[ast.AST, QmodExprNodeId],
180
189
  ) -> None:
190
+ if self._locked:
191
+ raise ClassiqInternalExpansionError("QAE is locked")
181
192
  if isinstance(node, ast.AST):
182
193
  node = id(node)
183
194
  if isinstance(value, ast.AST):
@@ -195,7 +206,7 @@ class QmodAnnotatedExpression:
195
206
 
196
207
  def get_quantum_subscripts(
197
208
  self,
198
- ) -> dict[QmodExprNodeId, QuantumSubscriptAnnotation]:
209
+ ) -> Mapping[QmodExprNodeId, QuantumSubscriptAnnotation]:
199
210
  return self._quantum_subscripts
200
211
 
201
212
  def set_quantum_type_attr(
@@ -204,6 +215,8 @@ class QmodAnnotatedExpression:
204
215
  value: Union[ast.AST, QmodExprNodeId],
205
216
  attr: str,
206
217
  ) -> None:
218
+ if self._locked:
219
+ raise ClassiqInternalExpansionError("QAE is locked")
207
220
  if isinstance(node, ast.AST):
208
221
  node = id(node)
209
222
  if isinstance(value, ast.AST):
@@ -219,7 +232,7 @@ class QmodAnnotatedExpression:
219
232
 
220
233
  def get_quantum_type_attributes(
221
234
  self,
222
- ) -> dict[QmodExprNodeId, QuantumTypeAttributeAnnotation]:
235
+ ) -> Mapping[QmodExprNodeId, QuantumTypeAttributeAnnotation]:
223
236
  return self._quantum_type_attrs
224
237
 
225
238
  def set_concatenation(
@@ -227,6 +240,8 @@ class QmodAnnotatedExpression:
227
240
  node: Union[ast.AST, QmodExprNodeId],
228
241
  elements: Sequence[Union[ast.AST, QmodExprNodeId]],
229
242
  ) -> None:
243
+ if self._locked:
244
+ raise ClassiqInternalExpansionError("QAE is locked")
230
245
  if isinstance(node, ast.AST):
231
246
  node = id(node)
232
247
  inlined_elements: list[QmodExprNodeId] = []
@@ -244,13 +259,13 @@ class QmodAnnotatedExpression:
244
259
  node = id(node)
245
260
  return node in self._concatenations
246
261
 
247
- def get_concatenations(self) -> dict[QmodExprNodeId, ConcatenationAnnotation]:
262
+ def get_concatenations(self) -> Mapping[QmodExprNodeId, ConcatenationAnnotation]:
248
263
  return self._concatenations
249
264
 
250
- def get_classical_vars(self) -> dict[QmodExprNodeId, HandleBinding]:
265
+ def get_classical_vars(self) -> Mapping[QmodExprNodeId, HandleBinding]:
251
266
  return self._classical_vars
252
267
 
253
- def get_quantum_vars(self) -> dict[QmodExprNodeId, HandleBinding]:
268
+ def get_quantum_vars(self) -> Mapping[QmodExprNodeId, HandleBinding]:
254
269
  return self._quantum_vars
255
270
 
256
271
  def clear_node_data(self, node: Union[ast.AST, QmodExprNodeId]) -> None:
@@ -282,3 +297,11 @@ class QmodAnnotatedExpression:
282
297
  self._quantum_subscripts |= other._quantum_subscripts
283
298
  self._quantum_type_attrs |= other._quantum_type_attrs
284
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
@@ -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
@@ -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]
@@ -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,15 +26,18 @@ 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
43
  var = expr_val.get_var(ta.value)
@@ -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
 
@@ -42,7 +42,7 @@ def _eval_type_attribute(
42
42
  return
43
43
  if isinstance(subject_type, (ClassicalArray, QuantumBitvector)) and attr == "len":
44
44
  expr_val.set_type(node, Integer())
45
- if subject_type.has_length:
45
+ if subject_type.has_constant_length:
46
46
  expr_val.set_value(node, subject_type.length_value)
47
47
  elif isinstance(subject_type, QuantumType):
48
48
  expr_val.set_quantum_type_attr(node, subject, attr)
@@ -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}")
@@ -33,7 +33,10 @@ def eval_enum_member(
33
33
  }
34
34
  attr = node.attr
35
35
  if attr not in enum_members:
36
- raise ClassiqExpansionError(f"Enum {enum_name} has no member named {attr!r}")
36
+ raise ClassiqExpansionError(
37
+ f"Enum {enum_name} has no member {attr!r}. Available members: "
38
+ f"{', '.join(enum_members.keys())}"
39
+ )
37
40
 
38
41
  expr_val.set_type(node, Enum(name=enum_name))
39
42
  expr_val.set_value(node, enum_members[attr])
@@ -29,7 +29,7 @@ def get_numeric_attrs(
29
29
 
30
30
  if TYPE_CHECKING:
31
31
  assert isinstance(qmod_type, QuantumScalar)
32
- if not qmod_type.is_evaluated:
32
+ if not qmod_type.is_constant:
33
33
  return None
34
34
  return NumericAttributes.from_quantum_scalar(qmod_type, machine_precision)
35
35
 
@@ -20,7 +20,7 @@ from classiq.evaluators.qmod_node_evaluators.compare_evaluation import (
20
20
  def eval_struct_instantiation(
21
21
  expr_val: QmodAnnotatedExpression, node: ast.Call, decl: StructDeclaration
22
22
  ) -> None:
23
- if len(node.args) > 0 or any(kwarg.arg is None for kwarg in node.keywords):
23
+ if len(node.args) != 1 or any(kwarg.arg is None for kwarg in node.keywords):
24
24
  raise ClassiqExpansionError(
25
25
  "Classical structs must be instantiated using keyword arguments"
26
26
  )
@@ -69,7 +69,7 @@ def _eval_slice(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> None:
69
69
  subject_type = expr_val.get_type(subject)
70
70
  slice_type: QmodType
71
71
  if isinstance(subject_type, ClassicalArray):
72
- if subject_type.has_length and (
72
+ if subject_type.has_constant_length and (
73
73
  (start_val is not None and start_val >= subject_type.length_value)
74
74
  or (stop_val is not None and stop_val > subject_type.length_value)
75
75
  ):
@@ -93,7 +93,7 @@ def _eval_slice(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> None:
93
93
  slice_type = subject_type.get_raw_type()
94
94
  elif isinstance(subject_type, QuantumBitvector):
95
95
  if start_val is not None and stop_val is not None:
96
- if subject_type.has_length and (
96
+ if subject_type.has_constant_length and (
97
97
  start_val >= subject_type.length_value
98
98
  or stop_val > subject_type.length_value
99
99
  ):
@@ -150,7 +150,7 @@ def _eval_subscript(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> N
150
150
  if isinstance(subject_type, (ClassicalArray, QuantumBitvector)):
151
151
  if (
152
152
  sub_val is not None
153
- and subject_type.has_length
153
+ and subject_type.has_constant_length
154
154
  and sub_val >= subject_type.length_value
155
155
  ):
156
156
  raise ClassiqExpansionError("Array index out of range")
@@ -85,7 +85,7 @@ def array_len(
85
85
  ) -> Optional[int]:
86
86
  if isinstance(classical_type, ClassicalTuple):
87
87
  return len(classical_type.element_types)
88
- if classical_type.has_length:
88
+ if classical_type.has_constant_length:
89
89
  return classical_type.length_value
90
90
  return None
91
91
 
@@ -73,9 +73,9 @@ def _inject_classical_array_attributes(
73
73
  if isinstance(to_type, ClassicalArray):
74
74
  if isinstance(from_type, ClassicalArray):
75
75
  length: Optional[Expression]
76
- if from_type.has_length:
76
+ if from_type.has_constant_length:
77
77
  if (
78
- to_type.has_length
78
+ to_type.has_constant_length
79
79
  and from_type.length_value != to_type.length_value
80
80
  ):
81
81
  return None
@@ -90,7 +90,7 @@ def _inject_classical_array_attributes(
90
90
  return ClassicalArray(element_type=element_type, length=length)
91
91
  if isinstance(from_type, ClassicalTuple):
92
92
  if (
93
- to_type.has_length
93
+ to_type.has_constant_length
94
94
  and len(from_type.element_types) != to_type.length_value
95
95
  ):
96
96
  return None
@@ -103,7 +103,7 @@ def _inject_classical_array_attributes(
103
103
  return ClassicalTuple(element_types=element_types)
104
104
  return None
105
105
  if isinstance(from_type, ClassicalArray):
106
- if from_type.has_length and from_type.length_value != len(
106
+ if from_type.has_constant_length and from_type.length_value != len(
107
107
  to_type.element_types
108
108
  ):
109
109
  return None
@@ -290,3 +290,41 @@ def inject_quantum_type_attributes_inplace(
290
290
  to_type.set_fields(updated_type.fields)
291
291
  return True
292
292
  raise ClassiqInternalExpansionError
293
+
294
+
295
+ def validate_quantum_type_attributes(quantum_type: QuantumType) -> None:
296
+ if isinstance(quantum_type, TypeName):
297
+ if len(quantum_type.fields) == 0:
298
+ raise ClassiqExpansionError(
299
+ f"QStruct {quantum_type.name} must have at least one field"
300
+ )
301
+ for field_type in quantum_type.fields.values():
302
+ validate_quantum_type_attributes(field_type)
303
+ return
304
+ if isinstance(quantum_type, QuantumBitvector):
305
+ validate_quantum_type_attributes(quantum_type.element_type)
306
+ if quantum_type.has_constant_length and quantum_type.length_value < 1:
307
+ raise ClassiqExpansionError(
308
+ f"QArray length must be positive, got {quantum_type.length_value}"
309
+ )
310
+ return
311
+ if isinstance(quantum_type, QuantumNumeric):
312
+ if quantum_type.has_size_in_bits and quantum_type.size_in_bits < 1:
313
+ raise ClassiqExpansionError(
314
+ f"QNum size must be positive, got {quantum_type.size_in_bits}"
315
+ )
316
+ if quantum_type.has_fraction_digits:
317
+ if quantum_type.fraction_digits_value < 0:
318
+ raise ClassiqExpansionError(
319
+ f"QNum fraction digits must be positive, got "
320
+ f"{quantum_type.fraction_digits_value}"
321
+ )
322
+ if (
323
+ quantum_type.has_size_in_bits
324
+ and quantum_type.fraction_digits_value > quantum_type.size_in_bits
325
+ ):
326
+ raise ClassiqExpansionError(
327
+ f"QNum size ({quantum_type.size_in_bits}) must be greater or "
328
+ f"equals than the fraction digits "
329
+ f"({quantum_type.fraction_digits_value})"
330
+ )
@@ -21,7 +21,7 @@ def check_signature_match(
21
21
  ) -> None:
22
22
  if len(decl_params) != len(op_params):
23
23
  raise ClassiqExpansionError(
24
- f"{location_str} should have {len(decl_params)} parameters, "
24
+ f"{location_str.capitalize()} should have {len(decl_params)} parameters, "
25
25
  f"not {len(op_params)}"
26
26
  )
27
27
  for idx, (decl_param, op_param) in enumerate(zip(decl_params, op_params)):