classiq 0.89.0__py3-none-any.whl → 0.90.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 (86) hide show
  1. classiq/__init__.py +1 -0
  2. classiq/_internals/api_wrapper.py +16 -32
  3. classiq/analyzer/show_interactive_hack.py +26 -1
  4. classiq/applications/chemistry/chemistry_model_constructor.py +14 -2
  5. classiq/applications/combinatorial_helpers/pyomo_utils.py +9 -6
  6. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +2 -2
  7. classiq/applications/combinatorial_optimization/combinatorial_problem.py +16 -8
  8. classiq/evaluators/classical_expression.py +63 -41
  9. classiq/evaluators/control.py +31 -52
  10. classiq/evaluators/expression_evaluator.py +8 -4
  11. classiq/evaluators/parameter_types.py +200 -104
  12. classiq/evaluators/qmod_annotated_expression.py +3 -1
  13. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +12 -37
  14. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +8 -17
  15. classiq/evaluators/qmod_node_evaluators/measurement_evaluation.py +1 -1
  16. classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +7 -1
  17. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +0 -1
  18. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +9 -1
  19. classiq/evaluators/qmod_node_evaluators/utils.py +33 -0
  20. classiq/evaluators/qmod_type_inference/classical_type_inference.py +4 -7
  21. classiq/interface/_version.py +1 -1
  22. classiq/interface/analyzer/analysis_params.py +1 -25
  23. classiq/interface/analyzer/result.py +4 -0
  24. classiq/interface/chemistry/ground_state_problem.py +16 -2
  25. classiq/interface/executor/optimizer_preferences.py +0 -112
  26. classiq/interface/generator/application_apis/chemistry_declarations.py +3 -1
  27. classiq/interface/generator/arith/arithmetic_expression_validator.py +2 -7
  28. classiq/interface/generator/expressions/evaluated_expression.py +3 -13
  29. classiq/interface/generator/expressions/expression_types.py +8 -22
  30. classiq/interface/generator/expressions/proxies/classical/classical_proxy.py +2 -2
  31. classiq/interface/generator/expressions/proxies/classical/classical_struct_proxy.py +1 -2
  32. classiq/interface/generator/functions/concrete_types.py +1 -1
  33. classiq/interface/generator/generated_circuit_data.py +4 -0
  34. classiq/interface/generator/preferences/qasm_to_qmod_params.py +14 -0
  35. classiq/interface/helpers/model_normalizer.py +0 -6
  36. classiq/interface/ide/visual_model.py +1 -0
  37. classiq/interface/model/handle_binding.py +1 -1
  38. classiq/interface/model/port_declaration.py +2 -1
  39. classiq/interface/model/quantum_expressions/arithmetic_operation.py +16 -12
  40. classiq/interface/model/quantum_type.py +1 -1
  41. classiq/interface/server/routes.py +2 -3
  42. classiq/model_expansions/atomic_expression_functions_defs.py +4 -22
  43. classiq/model_expansions/capturing/captured_vars.py +7 -3
  44. classiq/model_expansions/closure.py +8 -0
  45. classiq/model_expansions/interpreters/base_interpreter.py +84 -22
  46. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +1 -1
  47. classiq/model_expansions/interpreters/generative_interpreter.py +7 -5
  48. classiq/model_expansions/quantum_operations/allocate.py +92 -21
  49. classiq/model_expansions/quantum_operations/assignment_result_processor.py +28 -27
  50. classiq/model_expansions/quantum_operations/call_emitter.py +32 -26
  51. classiq/model_expansions/quantum_operations/classical_var_emitter.py +6 -2
  52. classiq/model_expansions/quantum_operations/emitter.py +39 -69
  53. classiq/model_expansions/quantum_operations/expression_evaluator.py +13 -2
  54. classiq/model_expansions/quantum_operations/quantum_function_call.py +4 -5
  55. classiq/model_expansions/quantum_operations/variable_decleration.py +16 -11
  56. classiq/model_expansions/scope.py +36 -29
  57. classiq/model_expansions/scope_initialization.py +3 -6
  58. classiq/model_expansions/sympy_conversion/sympy_to_python.py +6 -2
  59. classiq/model_expansions/transformers/model_renamer.py +35 -64
  60. classiq/model_expansions/transformers/type_modifier_inference.py +6 -6
  61. classiq/model_expansions/visitors/boolean_expression_transformers.py +7 -31
  62. classiq/model_expansions/visitors/symbolic_param_inference.py +9 -3
  63. classiq/open_library/functions/state_preparation.py +3 -3
  64. classiq/qmod/builtins/functions/allocation.py +8 -8
  65. classiq/qmod/builtins/functions/arithmetic.py +1 -1
  66. classiq/qmod/builtins/functions/chemistry.py +64 -0
  67. classiq/qmod/builtins/functions/exponentiation.py +7 -13
  68. classiq/qmod/builtins/functions/qsvm.py +1 -1
  69. classiq/qmod/builtins/operations.py +38 -10
  70. classiq/qmod/generative.py +2 -4
  71. classiq/qmod/native/pretty_printer.py +1 -1
  72. classiq/qmod/pretty_print/pretty_printer.py +1 -1
  73. classiq/qmod/qmod_constant.py +1 -1
  74. classiq/qmod/qmod_parameter.py +2 -2
  75. classiq/qmod/qmod_variable.py +15 -15
  76. classiq/qmod/quantum_expandable.py +1 -1
  77. classiq/synthesis.py +37 -1
  78. classiq/visualization.py +1 -1
  79. {classiq-0.89.0.dist-info → classiq-0.90.0.dist-info}/METADATA +1 -1
  80. {classiq-0.89.0.dist-info → classiq-0.90.0.dist-info}/RECORD +81 -85
  81. classiq/evaluators/arg_type_match.py +0 -168
  82. classiq/evaluators/classical_type_inference.py +0 -121
  83. classiq/interface/combinatorial_optimization/optimization_problem.py +0 -17
  84. classiq/interface/combinatorial_optimization/result.py +0 -9
  85. classiq/model_expansions/transformers/ast_renamer.py +0 -26
  86. {classiq-0.89.0.dist-info → classiq-0.90.0.dist-info}/WHEEL +0 -0
@@ -1,4 +1,4 @@
1
- from typing import Optional, TypeVar, Union
1
+ from typing import TYPE_CHECKING, Any, NoReturn, TypeVar, Union
2
2
 
3
3
  import sympy
4
4
 
@@ -6,14 +6,17 @@ from classiq.interface.exceptions import (
6
6
  ClassiqExpansionError,
7
7
  ClassiqInternalExpansionError,
8
8
  )
9
- from classiq.interface.generator.expressions.expression import Expression
10
- from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
11
- AnyClassicalValue,
9
+ from classiq.interface.generator.expressions.evaluated_expression import (
10
+ EvaluatedExpression,
12
11
  )
12
+ from classiq.interface.generator.expressions.expression import Expression
13
+ from classiq.interface.generator.expressions.expression_types import RuntimeConstant
13
14
  from classiq.interface.generator.functions.classical_type import (
15
+ Bool,
14
16
  ClassicalArray,
15
17
  ClassicalTuple,
16
18
  ClassicalType,
19
+ Integer,
17
20
  )
18
21
  from classiq.interface.generator.functions.concrete_types import ConcreteQuantumType
19
22
  from classiq.interface.generator.functions.port_declaration import (
@@ -22,12 +25,14 @@ from classiq.interface.generator.functions.port_declaration import (
22
25
  from classiq.interface.generator.functions.type_name import (
23
26
  TypeName,
24
27
  )
28
+ from classiq.interface.helpers.backward_compatibility import zip_strict
25
29
  from classiq.interface.model.classical_parameter_declaration import (
26
30
  ClassicalParameterDeclaration,
27
31
  )
28
32
  from classiq.interface.model.handle_binding import HandleBinding
29
33
  from classiq.interface.model.port_declaration import PortDeclaration
30
34
  from classiq.interface.model.quantum_function_declaration import (
35
+ AnonQuantumOperandDeclaration,
31
36
  PositionalArg,
32
37
  QuantumOperandDeclaration,
33
38
  )
@@ -38,71 +43,62 @@ from classiq.interface.model.quantum_type import (
38
43
  QuantumType,
39
44
  )
40
45
 
41
- from classiq.evaluators.arg_type_match import check_type_match
42
46
  from classiq.evaluators.classical_expression import (
43
47
  evaluate_classical_expression,
44
48
  )
45
- from classiq.evaluators.classical_type_inference import (
49
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
50
+ from classiq.evaluators.qmod_node_evaluators.utils import QmodType, get_sympy_val
51
+ from classiq.evaluators.qmod_type_inference.classical_type_inference import (
46
52
  infer_classical_type,
53
+ inject_classical_type_attributes,
47
54
  )
48
55
  from classiq.evaluators.qmod_type_inference.quantum_type_inference import (
49
56
  inject_quantum_type_attributes,
57
+ validate_quantum_type_attributes,
50
58
  )
59
+ from classiq.evaluators.type_type_match import check_signature_match
51
60
  from classiq.model_expansions.closure import FunctionClosure
52
61
  from classiq.model_expansions.scope import (
62
+ ClassicalSymbol,
53
63
  Evaluated,
54
64
  QuantumSymbol,
55
65
  QuantumVariable,
56
66
  Scope,
57
67
  )
68
+ from classiq.model_expansions.visitors.symbolic_param_inference import (
69
+ set_generative_recursively,
70
+ )
58
71
 
59
72
 
60
73
  def evaluate_parameter_types_from_args(
61
- closure: FunctionClosure, signature_scope: Scope, arguments: list[Evaluated]
74
+ closure: FunctionClosure, arguments: list[Evaluated]
62
75
  ) -> list[PositionalArg]:
63
76
  parameters = closure.positional_arg_declarations
64
- function_name = closure.name
65
- check_type_match(parameters, arguments, function_name)
77
+ if len(parameters) != len(arguments):
78
+ raise ClassiqExpansionError(
79
+ f"Function {closure.name!r} takes {len(parameters)} arguments but "
80
+ f"{len(arguments)} were given"
81
+ )
82
+
83
+ for parameter, argument in zip_strict(parameters, arguments, strict=True):
84
+ if isinstance(parameter, ClassicalParameterDeclaration):
85
+ arg_val = argument.value
86
+ if not isinstance(arg_val, QmodAnnotatedExpression):
87
+ closure.scope[parameter.name] = argument
66
88
 
67
- for parameter, argument in zip(parameters, arguments):
68
- _update_scope(parameter, argument, closure)
89
+ evaluated_params = [
90
+ _evaluate_type_from_arg(parameter, argument, closure)
91
+ for parameter, argument in zip_strict(parameters, arguments, strict=True)
92
+ ]
69
93
 
70
94
  parameter_names = {parameter.name for parameter in parameters}
71
- for parameter in parameters:
95
+ for parameter, argument in zip_strict(parameters, arguments, strict=True):
72
96
  if isinstance(parameter, QuantumOperandDeclaration):
73
- parameter_value = closure.scope[parameter.name].value
74
97
  _update_operand_signature_environment(
75
- parameter_value, parameter_names, closure
98
+ argument.value, parameter_names, closure
76
99
  )
77
100
 
78
- return [
79
- _evaluate_type_from_arg(
80
- parameter,
81
- argument,
82
- Scope(parent=closure.scope | signature_scope),
83
- closure.name,
84
- )
85
- for parameter, argument in zip(parameters, arguments)
86
- ]
87
-
88
-
89
- def _update_scope(
90
- parameter: PositionalArg, argument: Evaluated, closure: FunctionClosure
91
- ) -> None:
92
- if not isinstance(parameter, PortDeclaration):
93
- closure.scope[parameter.name] = argument
94
- return
95
- if parameter.direction is PortDeclarationDirection.Output:
96
- return
97
- quantum_var = argument.as_type(QuantumVariable)
98
- casted_argument = _cast(
99
- parameter.quantum_type,
100
- quantum_var,
101
- parameter.name,
102
- )
103
- closure.scope[parameter.name] = Evaluated(
104
- value=casted_argument, defining_function=closure
105
- )
101
+ return evaluated_params
106
102
 
107
103
 
108
104
  NestedFunctionClosureT = Union[FunctionClosure, list["NestedFunctionClosureT"]]
@@ -130,60 +126,162 @@ def _update_operand_signature_environment(
130
126
  )
131
127
 
132
128
 
133
- def _cast(
134
- parameter_type: QuantumType, argument: QuantumVariable, param_name: str
135
- ) -> QuantumSymbol:
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
- )
143
- return QuantumSymbol(
144
- handle=HandleBinding(name=param_name), quantum_type=updated_type
145
- )
146
-
147
-
148
129
  def _evaluate_type_from_arg(
149
130
  parameter: PositionalArg,
150
131
  argument: Evaluated,
151
- inner_scope: Scope,
152
- function_name: str,
132
+ closure: FunctionClosure,
153
133
  ) -> PositionalArg:
154
134
  # FIXME: Remove suzuki_trotter overloading (CLS-2912)
155
- if function_name == "suzuki_trotter" and parameter.name == "pauli_operator":
135
+ if closure.name == "suzuki_trotter" and parameter.name == "pauli_operator":
156
136
  return parameter
157
137
  if isinstance(parameter, ClassicalParameterDeclaration):
158
- updated_classical_type = evaluate_type_in_classical_symbol(
159
- parameter.classical_type.model_copy(), inner_scope, parameter.name
160
- )
161
- return ClassicalParameterDeclaration(
162
- name=parameter.name,
163
- classical_type=infer_classical_type(argument.value, updated_classical_type),
138
+ return _evaluate_classical_type_from_arg(parameter, argument, closure)
139
+ if isinstance(parameter, PortDeclaration):
140
+ return _evaluate_quantum_type_from_arg(parameter, argument, closure)
141
+ if TYPE_CHECKING:
142
+ assert isinstance(parameter, QuantumOperandDeclaration)
143
+ if parameter.is_list:
144
+ return _evaluate_op_list_type_from_arg(parameter, argument, closure)
145
+ else:
146
+ return _evaluate_op_type_from_arg(parameter, argument, closure)
147
+
148
+
149
+ def _evaluate_classical_type_from_arg(
150
+ parameter: ClassicalParameterDeclaration,
151
+ argument: Evaluated,
152
+ closure: FunctionClosure,
153
+ ) -> ClassicalParameterDeclaration:
154
+ unified_scope = closure.scope | closure.signature_scope
155
+ updated_classical_type = evaluate_type_in_classical_symbol(
156
+ parameter.classical_type.model_copy(), unified_scope, parameter.name
157
+ )
158
+ arg_val = argument.value
159
+ if isinstance(arg_val, QmodAnnotatedExpression):
160
+ arg_type = arg_val.get_classical_type(arg_val.root)
161
+ else:
162
+ arg_type = infer_classical_type(arg_val)
163
+ injected_classical_type = inject_classical_type_attributes(
164
+ arg_type.without_symbolic_attributes(), updated_classical_type
165
+ )
166
+ if injected_classical_type is None:
167
+ _raise_argument_type_error(
168
+ arg_val, arg_type, parameter.name, updated_classical_type
164
169
  )
170
+ if parameter.classical_type.is_purely_generative:
171
+ set_generative_recursively(injected_classical_type)
172
+ closure.scope[parameter.name] = Evaluated(
173
+ value=(
174
+ ClassicalSymbol(
175
+ handle=HandleBinding(name=parameter.name),
176
+ classical_type=injected_classical_type,
177
+ )
178
+ if isinstance(arg_val, QmodAnnotatedExpression)
179
+ else arg_val
180
+ ),
181
+ defining_function=closure,
182
+ )
183
+ return ClassicalParameterDeclaration(
184
+ name=parameter.name, classical_type=injected_classical_type
185
+ )
165
186
 
166
- if not isinstance(parameter, PortDeclaration):
167
- return parameter
168
187
 
188
+ def _evaluate_quantum_type_from_arg(
189
+ parameter: PortDeclaration,
190
+ argument: Evaluated,
191
+ closure: FunctionClosure,
192
+ ) -> PortDeclaration:
193
+ unified_scope = closure.scope | closure.signature_scope
169
194
  updated_quantum_type: QuantumType = evaluate_type_in_quantum_symbol(
170
- parameter.quantum_type.model_copy(), inner_scope, parameter.name
195
+ parameter.quantum_type.model_copy(), unified_scope, parameter.name
171
196
  )
172
197
  if parameter.direction != PortDeclarationDirection.Output:
173
198
  arg_type = argument.as_type(QuantumVariable).quantum_type
174
199
  updated_output_quantum_type = inject_quantum_type_attributes(
175
- arg_type, updated_quantum_type
200
+ arg_type.without_symbolic_attributes(), updated_quantum_type
176
201
  )
177
202
  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}"
203
+ _raise_argument_type_error(
204
+ argument.value, arg_type, parameter.name, updated_quantum_type
182
205
  )
183
206
  updated_quantum_type = updated_output_quantum_type
207
+ validate_quantum_type_attributes(updated_quantum_type)
208
+ closure.scope[parameter.name] = Evaluated(
209
+ value=QuantumSymbol(
210
+ handle=HandleBinding(name=parameter.name), quantum_type=updated_quantum_type
211
+ ),
212
+ defining_function=closure,
213
+ )
184
214
  return parameter.model_copy(update={"quantum_type": updated_quantum_type})
185
215
 
186
216
 
217
+ def _evaluate_op_list_type_from_arg(
218
+ parameter: QuantumOperandDeclaration, argument: Evaluated, closure: FunctionClosure
219
+ ) -> QuantumOperandDeclaration:
220
+ arg_val = argument.value
221
+ if not isinstance(arg_val, list) or any(
222
+ not isinstance(op, FunctionClosure) for op in arg_val
223
+ ):
224
+ if isinstance(arg_val, FunctionClosure):
225
+ _raise_argument_type_error(
226
+ "<lambda>",
227
+ arg_val.as_operand_declaration(is_list=False),
228
+ parameter.name,
229
+ parameter,
230
+ )
231
+ raise ClassiqInternalExpansionError("Non-lambda argument to lambda parameter")
232
+ for idx, operand in enumerate(arg_val):
233
+ check_signature_match(
234
+ parameter.positional_arg_declarations,
235
+ operand.positional_arg_declarations,
236
+ f"operand #{idx + 1} in parameter {parameter.name!r} "
237
+ f"in function {closure.name!r}",
238
+ )
239
+ return parameter
240
+
241
+
242
+ def _evaluate_op_type_from_arg(
243
+ parameter: QuantumOperandDeclaration, argument: Evaluated, closure: FunctionClosure
244
+ ) -> QuantumOperandDeclaration:
245
+ arg_val = argument.value
246
+ if not isinstance(arg_val, FunctionClosure):
247
+ if isinstance(arg_val, list):
248
+ if len(arg_val) == 0:
249
+ _raise_argument_type_error(
250
+ arg_val,
251
+ AnonQuantumOperandDeclaration(is_list=True),
252
+ parameter.name,
253
+ parameter,
254
+ )
255
+ first_lambda = arg_val[0]
256
+ if isinstance(first_lambda, FunctionClosure):
257
+ _raise_argument_type_error(
258
+ arg_val,
259
+ first_lambda.as_operand_declaration(is_list=True),
260
+ parameter.name,
261
+ parameter,
262
+ )
263
+ raise ClassiqInternalExpansionError("Non-lambda argument to lambda parameter")
264
+ check_signature_match(
265
+ parameter.positional_arg_declarations,
266
+ arg_val.positional_arg_declarations,
267
+ f"operand {parameter.name!r} in function {closure.name!r}",
268
+ )
269
+ return parameter
270
+
271
+
272
+ def _raise_argument_type_error(
273
+ arg_val: Any,
274
+ arg_type: Union[QmodType, AnonQuantumOperandDeclaration],
275
+ param_name: str,
276
+ param_type: Union[QmodType, AnonQuantumOperandDeclaration],
277
+ ) -> NoReturn:
278
+ raise ClassiqExpansionError(
279
+ f"Argument {str(arg_val)!r} of type "
280
+ f"{arg_type.qmod_type_name} is incompatible with parameter "
281
+ f"{param_name!r} of type {param_type.qmod_type_name}"
282
+ )
283
+
284
+
187
285
  def evaluate_type_in_quantum_symbol(
188
286
  type_to_update: QuantumType, scope: Scope, param_name: str
189
287
  ) -> ConcreteQuantumType:
@@ -206,16 +304,15 @@ def _evaluate_qarray_in_quantum_symbol(
206
304
  )
207
305
  type_to_update.element_type = new_element_type
208
306
  if type_to_update.length is not None:
209
- new_length = _eval_expr(
307
+ type_to_update.length = _eval_expr(
210
308
  type_to_update.length,
211
309
  scope,
212
310
  int,
311
+ Integer,
213
312
  type_to_update.type_name,
214
313
  "length",
215
314
  param_name,
216
315
  )
217
- if new_length is not None:
218
- type_to_update.length = Expression(expr=str(new_length))
219
316
  return type_to_update
220
317
 
221
318
 
@@ -224,73 +321,74 @@ def _evaluate_qnum_in_quantum_symbol(
224
321
  ) -> QuantumNumeric:
225
322
  if type_to_update.size is None:
226
323
  return type_to_update
227
- new_size = _eval_expr(
324
+ type_to_update.size = _eval_expr(
228
325
  type_to_update.size,
229
326
  scope,
230
327
  int,
328
+ Integer,
231
329
  type_to_update.type_name,
232
330
  "size",
233
331
  param_name,
234
332
  )
235
- if new_size is not None:
236
- type_to_update.size = Expression(expr=str(new_size))
237
333
 
238
334
  if type_to_update.is_signed is not None:
239
- new_is_sign = _eval_expr(
335
+ type_to_update.is_signed = _eval_expr(
240
336
  type_to_update.is_signed,
241
337
  scope,
242
338
  bool,
339
+ Bool,
243
340
  type_to_update.type_name,
244
341
  "sign",
245
342
  param_name,
246
343
  )
247
- if new_is_sign is not None:
248
- type_to_update.is_signed = Expression(expr=str(new_is_sign))
249
344
  else:
250
345
  type_to_update.is_signed = Expression(expr="False")
251
346
 
252
347
  if type_to_update.fraction_digits is not None:
253
- new_fraction_digits = _eval_expr(
348
+ type_to_update.fraction_digits = _eval_expr(
254
349
  type_to_update.fraction_digits,
255
350
  scope,
256
351
  int,
352
+ Integer,
257
353
  type_to_update.type_name,
258
354
  "fraction digits",
259
355
  param_name,
260
356
  )
261
- if new_fraction_digits is not None:
262
- type_to_update.fraction_digits = Expression(expr=str(new_fraction_digits))
263
357
  else:
264
358
  type_to_update.fraction_digits = Expression(expr="0")
265
359
 
266
360
  return type_to_update
267
361
 
268
362
 
269
- _EXPR_TYPE = TypeVar("_EXPR_TYPE")
363
+ _EXPR_TYPE = TypeVar("_EXPR_TYPE", bound=RuntimeConstant)
270
364
 
271
365
 
272
366
  def _eval_expr(
273
367
  expression: Expression,
274
368
  scope: Scope,
275
369
  expected_type: type[_EXPR_TYPE],
370
+ expected_qmod_type: type,
276
371
  type_name: str,
277
372
  attr_name: str,
278
373
  param_name: str,
279
- ) -> Optional[_EXPR_TYPE]:
280
- val = evaluate_classical_expression(expression, scope).value
281
- if expected_type is int and isinstance(val, float):
374
+ ) -> Expression:
375
+ val = evaluate_classical_expression(Expression(expr=expression.expr), scope).value
376
+ if isinstance(val, sympy.Basic):
377
+ val = get_sympy_val(val)
378
+ if expected_type is int and isinstance(val, float) and int(val) == val:
282
379
  val = int(val)
283
- if isinstance(val, expected_type):
284
- return val
285
- if isinstance(val, AnyClassicalValue) or (
286
- isinstance(val, sympy.Basic) and len(val.free_symbols) > 0
380
+ if not isinstance(val, expected_type) and (
381
+ not isinstance(val, QmodAnnotatedExpression)
382
+ or not isinstance(val.get_type(val.root), expected_qmod_type)
287
383
  ):
288
- return None
289
- raise ClassiqExpansionError(
290
- f"When inferring the type of parameter {param_name!r}: "
291
- f"{type_name} {attr_name} must be {expected_type.__name__}, got "
292
- f"{str(val)!r}"
293
- )
384
+ raise ClassiqExpansionError(
385
+ f"When inferring the type of parameter {param_name!r}: "
386
+ f"{type_name} {attr_name} must be {expected_qmod_type.__name__}, got "
387
+ f"{str(val)!r}"
388
+ )
389
+ expr = Expression(expr=str(val))
390
+ expr._evaluated_expr = EvaluatedExpression(value=val)
391
+ return expr
294
392
 
295
393
 
296
394
  def _evaluate_qstruct_in_quantum_symbol(
@@ -325,11 +423,9 @@ def evaluate_type_in_classical_symbol(
325
423
  if isinstance(type_to_update, ClassicalArray):
326
424
  length = type_to_update.length
327
425
  if length is not None:
328
- new_length = _eval_expr(
329
- length, scope, int, "classical array", "length", param_name
426
+ length = _eval_expr(
427
+ length, scope, int, Integer, "classical array", "length", param_name
330
428
  )
331
- if new_length is not None:
332
- length = Expression(expr=str(new_length))
333
429
  updated_type = ClassicalArray(
334
430
  element_type=evaluate_type_in_classical_symbol(
335
431
  type_to_update.element_type, scope, param_name
@@ -4,6 +4,8 @@ from dataclasses import dataclass
4
4
  from enum import Enum
5
5
  from typing import Any, Union, cast
6
6
 
7
+ import sympy
8
+
7
9
  from classiq.interface.exceptions import ClassiqInternalExpansionError
8
10
  from classiq.interface.generator.expressions.proxies.classical.qmod_struct_instance import (
9
11
  QmodStructInstance,
@@ -50,7 +52,7 @@ def qmod_val_to_str(val: Any) -> str:
50
52
  return f"[{', '.join(qmod_val_to_str(item) for item in val)}]"
51
53
  if isinstance(val, Enum):
52
54
  return Enum.__str__(val)
53
- if isinstance(val, (int, float, bool, complex)):
55
+ if isinstance(val, (int, float, bool, complex, sympy.Basic)):
54
56
  return str(val)
55
57
  raise ClassiqInternalExpansionError(
56
58
  f"Unrecognized value {str(val)!r} of type {type(val)}"
@@ -1,5 +1,5 @@
1
1
  import ast
2
- from typing import Any, Callable, Optional, Union, cast
2
+ from typing import Callable, Union, cast
3
3
 
4
4
  import sympy
5
5
 
@@ -184,37 +184,25 @@ def try_eval_sympy_function(
184
184
  if not isinstance(sympy_func, sympy.FunctionClass):
185
185
  return False
186
186
  _validate_no_kwargs(node)
187
- args: Optional[list[Any]] = None
188
- sympy_ret_val: Any = None
189
- if all(expr_val.has_value(arg) for arg in node.args):
190
- args = [expr_val.get_value(arg) for arg in node.args]
191
- sympy_ret_val = sympy_func(*args)
192
- if not isinstance(sympy_ret_val, sympy.Expr):
193
- raise ClassiqInternalExpansionError
194
187
  ret_type: QmodType
195
- ret_val: Any = None
196
188
  if hasattr(sympy_func, "is_Boolean") and sympy_func.is_Boolean:
197
189
  ret_type = Bool()
198
- if args is not None:
199
- ret_val = bool(sympy_ret_val)
200
190
  elif (
201
191
  hasattr(sympy_func, "is_Integer") and sympy_func.is_Integer
202
192
  ) or func_name in INTEGER_FUNCTION_OVERRIDE:
203
193
  ret_type = Integer()
204
- if args is not None:
205
- ret_val = int(sympy_ret_val)
206
194
  else:
207
195
  ret_type = Real()
208
- if sympy_ret_val is not None:
209
- if sympy_ret_val.is_imaginary:
210
- ret_val = complex(sympy_ret_val)
211
- elif sympy_ret_val.is_real:
212
- ret_val = float(sympy_ret_val)
213
- else:
214
- raise ClassiqInternalExpansionError
215
196
  expr_val.set_type(node, ret_type)
216
- if ret_val is not None:
217
- expr_val.set_value(node, ret_val)
197
+
198
+ if any(not expr_val.has_value(arg) for arg in node.args):
199
+ return True
200
+
201
+ args = [expr_val.get_value(arg) for arg in node.args]
202
+ sympy_ret_val = sympy_func(*args)
203
+ if not isinstance(sympy_ret_val, sympy.Basic):
204
+ raise ClassiqInternalExpansionError
205
+ expr_val.set_value(node, sympy_ret_val)
218
206
  return True
219
207
 
220
208
 
@@ -265,14 +253,7 @@ def try_eval_builtin_function(
265
253
  sympy_val = sympy.mod_inverse(
266
254
  *[expr_val.get_value(arg) for arg in node.args]
267
255
  )
268
- ret_val: Any
269
- if isinstance(sympy_val, sympy.Expr) and sympy_val.is_imaginary:
270
- ret_val = complex(sympy_val)
271
- elif args_are_int:
272
- ret_val = int(sympy_val)
273
- else:
274
- ret_val = float(sympy_val)
275
- expr_val.set_value(node, ret_val)
256
+ expr_val.set_value(node, sympy_val)
276
257
  return True
277
258
 
278
259
  if func_name == "sum":
@@ -293,13 +274,7 @@ def try_eval_builtin_function(
293
274
  expr_val.set_type(node, Real())
294
275
  if args_have_values:
295
276
  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)
277
+ expr_val.set_value(node, sympy_val)
303
278
  return True
304
279
 
305
280
  if func_name == "abs":
@@ -16,7 +16,11 @@ from classiq.interface.generator.functions.classical_type import (
16
16
  from classiq.interface.generator.functions.type_name import Enum
17
17
 
18
18
  from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
19
- from classiq.evaluators.qmod_node_evaluators.utils import SYMPY_SYMBOLS, QmodType
19
+ from classiq.evaluators.qmod_node_evaluators.utils import (
20
+ SYMPY_SYMBOLS,
21
+ QmodType,
22
+ get_sympy_type,
23
+ )
20
24
 
21
25
  QMOD_LITERALS: dict[str, tuple[ClassicalType, Any]] = {
22
26
  "false": (Bool(), False),
@@ -68,22 +72,9 @@ def try_eval_qmod_literal(expr_val: QmodAnnotatedExpression, node: ast.Name) ->
68
72
 
69
73
  def try_eval_sympy_constant(expr_val: QmodAnnotatedExpression, node: ast.Name) -> bool:
70
74
  sympy_val = SYMPY_SYMBOLS.get(node.id)
71
- if not isinstance(sympy_val, sympy.Expr) or not sympy_val.is_constant():
75
+ if not isinstance(sympy_val, sympy.Basic) or len(sympy_val.free_symbols) > 0:
72
76
  return False
73
- constant_type: QmodType
74
- constant_value: Any
75
- if sympy_val.is_Integer:
76
- constant_type = Integer()
77
- constant_value = int(sympy_val)
78
- elif sympy_val.is_Boolean:
79
- constant_type = Bool()
80
- constant_value = bool(sympy_val)
81
- elif sympy_val.is_imaginary:
82
- constant_type = Real()
83
- constant_value = complex(sympy_val)
84
- else:
85
- constant_type = Real()
86
- constant_value = float(sympy_val)
77
+ constant_type = get_sympy_type(sympy_val)
87
78
  expr_val.set_type(node, constant_type)
88
- expr_val.set_value(node, constant_value)
79
+ expr_val.set_value(node, sympy_val)
89
80
  return True
@@ -21,5 +21,5 @@ def eval_measurement(expr_val: QmodAnnotatedExpression, node: ast.Call) -> None:
21
21
  if is_classical_type(arg_type):
22
22
  raise ClassiqExpansionError("'measure' expects a quantum input")
23
23
  if not isinstance(arg_type, QuantumBit):
24
- raise ClassiqExpansionError("Currently, 'measure' only supports quantum bits")
24
+ raise ClassiqExpansionError("'measure' only supports QBits")
25
25
  expr_val.set_type(node, Bool())
@@ -1,5 +1,7 @@
1
1
  import ast
2
2
 
3
+ import sympy
4
+
3
5
  from classiq.interface.exceptions import (
4
6
  ClassiqExpansionError,
5
7
  ClassiqInternalExpansionError,
@@ -84,7 +86,11 @@ def eval_min_max_op(
84
86
 
85
87
  if all(expr_val.has_value(arg) for arg in node.args):
86
88
  values = [expr_val.get_value(arg) for arg in node.args]
87
- if not all(isinstance(value, (int, float)) for value in values):
89
+ if not all(
90
+ isinstance(value, (int, float))
91
+ or (isinstance(value, sympy.Expr) and value.is_real)
92
+ for value in values
93
+ ):
88
94
  raise ClassiqExpansionError(f"Invalid argument for function {func_name!r}")
89
95
 
90
96
  if func_name == "min":
@@ -22,7 +22,6 @@ from classiq.evaluators.qmod_type_inference.classical_type_inference import (
22
22
 
23
23
 
24
24
  def eval_name(expr_val: QmodAnnotatedExpression, node: ast.Name, value: Any) -> None:
25
- # FIXME: Remove sympy compatibility (CLS-3214)
26
25
  if isinstance(
27
26
  value, (bool, int, float, complex, list, QmodStructInstance, sympy.Basic)
28
27
  ):
@@ -1,12 +1,18 @@
1
1
  import ast
2
2
  from typing import TYPE_CHECKING, Optional
3
3
 
4
+ import sympy
5
+
4
6
  from classiq.interface.exceptions import ClassiqExpansionError
5
7
  from classiq.interface.generator.functions.classical_type import Bool
6
8
  from classiq.interface.model.quantum_type import QuantumScalar
7
9
 
8
10
  from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
9
- from classiq.evaluators.qmod_node_evaluators.utils import QmodType, is_classical_type
11
+ from classiq.evaluators.qmod_node_evaluators.utils import (
12
+ QmodType,
13
+ get_sympy_val,
14
+ is_classical_type,
15
+ )
10
16
  from classiq.model_expansions.arithmetic import NumericAttributes
11
17
 
12
18
 
@@ -46,6 +52,8 @@ def get_classical_value_for_arithmetic(
46
52
  return None
47
53
 
48
54
  value = expr_val.get_value(node)
55
+ if isinstance(value, sympy.Basic):
56
+ value = get_sympy_val(value)
49
57
  if not isinstance(value, (int, float)):
50
58
  if treat_qnum_as_float and isinstance(value, complex):
51
59
  return None