classiq 0.78.0__py3-none-any.whl → 0.79.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. classiq/interface/_version.py +1 -1
  2. classiq/interface/generator/arith/arithmetic.py +10 -6
  3. classiq/interface/generator/arith/binary_ops.py +8 -11
  4. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +90 -23
  5. classiq/interface/generator/expressions/proxies/classical/utils.py +4 -0
  6. classiq/interface/generator/functions/classical_type.py +74 -0
  7. classiq/interface/generator/functions/concrete_types.py +3 -0
  8. classiq/interface/generator/functions/type_name.py +32 -3
  9. classiq/interface/generator/generated_circuit_data.py +3 -1
  10. classiq/interface/helpers/model_normalizer.py +47 -0
  11. classiq/interface/interface_version.py +1 -1
  12. classiq/interface/model/bounds.py +12 -0
  13. classiq/interface/model/model.py +9 -5
  14. classiq/interface/model/quantum_type.py +25 -3
  15. classiq/interface/model/statement_block.py +2 -0
  16. classiq/model_expansions/atomic_expression_functions_defs.py +20 -6
  17. classiq/model_expansions/evaluators/argument_types.py +20 -0
  18. classiq/model_expansions/evaluators/classical_type_inference.py +42 -13
  19. classiq/model_expansions/evaluators/quantum_type_utils.py +31 -3
  20. classiq/model_expansions/generative_functions.py +1 -1
  21. classiq/model_expansions/interpreters/generative_interpreter.py +9 -0
  22. classiq/model_expansions/quantum_operations/__init__.py +3 -0
  23. classiq/model_expansions/quantum_operations/allocate.py +3 -1
  24. classiq/model_expansions/quantum_operations/assignment_result_processor.py +7 -3
  25. classiq/model_expansions/quantum_operations/bind.py +8 -1
  26. classiq/model_expansions/quantum_operations/bounds.py +30 -0
  27. classiq/model_expansions/quantum_operations/call_emitter.py +47 -7
  28. classiq/model_expansions/quantum_operations/function_calls_cache.py +8 -1
  29. classiq/model_expansions/quantum_operations/quantum_function_call.py +5 -6
  30. classiq/model_expansions/scope.py +14 -4
  31. classiq/model_expansions/scope_initialization.py +1 -14
  32. classiq/model_expansions/visitors/symbolic_param_inference.py +32 -16
  33. classiq/model_expansions/visitors/variable_references.py +39 -43
  34. classiq/open_library/functions/linear_pauli_rotation.py +6 -6
  35. classiq/open_library/functions/state_preparation.py +1 -1
  36. classiq/open_library/functions/utility_functions.py +2 -2
  37. classiq/qmod/declaration_inferrer.py +5 -3
  38. classiq/qmod/model_state_container.py +21 -1
  39. classiq/qmod/native/pretty_printer.py +8 -0
  40. classiq/qmod/pretty_print/expression_to_python.py +8 -1
  41. classiq/qmod/pretty_print/pretty_printer.py +7 -0
  42. classiq/qmod/python_classical_type.py +1 -1
  43. classiq/qmod/qmod_parameter.py +43 -7
  44. classiq/qmod/semantics/annotation/qstruct_annotator.py +15 -4
  45. classiq/qmod/utilities.py +1 -1
  46. {classiq-0.78.0.dist-info → classiq-0.79.1.dist-info}/METADATA +1 -1
  47. {classiq-0.78.0.dist-info → classiq-0.79.1.dist-info}/RECORD +48 -45
  48. {classiq-0.78.0.dist-info → classiq-0.79.1.dist-info}/WHEEL +0 -0
@@ -18,6 +18,7 @@ from classiq.interface.generator.expressions.proxies.classical.any_classical_val
18
18
  )
19
19
  from classiq.interface.generator.expressions.proxies.classical.classical_array_proxy import (
20
20
  ClassicalArrayProxy,
21
+ ClassicalSequenceProxy,
21
22
  )
22
23
  from classiq.interface.generator.expressions.proxies.classical.classical_proxy import (
23
24
  ClassicalProxy,
@@ -39,6 +40,7 @@ from classiq.interface.generator.functions.classical_type import (
39
40
  Bool,
40
41
  ClassicalArray,
41
42
  ClassicalList,
43
+ ClassicalTuple,
42
44
  ClassicalType,
43
45
  OpaqueHandle,
44
46
  QmodPyObject,
@@ -46,6 +48,7 @@ from classiq.interface.generator.functions.classical_type import (
46
48
  StructMetaType,
47
49
  )
48
50
  from classiq.interface.generator.functions.type_name import TypeName
51
+ from classiq.interface.helpers.backward_compatibility import zip_strict
49
52
 
50
53
  from classiq.model_expansions.model_tables import (
51
54
  HandleIdentifier,
@@ -92,6 +95,15 @@ def qmod_val_to_python(val: ExpressionValue, qmod_type: ClassicalType) -> Any:
92
95
  if isinstance(val, list):
93
96
  return [qmod_val_to_python(elem, qmod_type.element_type) for elem in val]
94
97
 
98
+ elif isinstance(qmod_type, ClassicalTuple):
99
+ if isinstance(val, list):
100
+ return [
101
+ qmod_val_to_python(elem, elem_type)
102
+ for elem_type, elem in zip_strict(
103
+ qmod_type.element_types, val, strict=True
104
+ )
105
+ ]
106
+
95
107
  elif isinstance(qmod_type, OpaqueHandle):
96
108
  if isinstance(val, HandleIdentifier):
97
109
  return HandleTable.get_handle_object(val)
@@ -133,7 +145,7 @@ def python_val_to_qmod(val: Any, qmod_type: ClassicalType) -> ExpressionValue:
133
145
  field_name: python_val_to_qmod(val[field_name], field_type)
134
146
  for field_name, field_type in struct_decl.variables.items()
135
147
  }
136
- return QmodStructInstance(struct_decl, qmod_dict)
148
+ return QmodStructInstance(struct_decl.model_copy(), qmod_dict)
137
149
 
138
150
  if isinstance(qmod_type, ClassicalList):
139
151
  if not isinstance(val, list):
@@ -162,7 +174,7 @@ def python_call_wrapper(func: Callable, *args: ExpressionValue) -> Any:
162
174
 
163
175
  def struct_literal(struct_type_symbol: Symbol, **kwargs: Any) -> QmodStructInstance:
164
176
  return QmodStructInstance(
165
- QMODULE.type_decls[struct_type_symbol.name],
177
+ QMODULE.type_decls[struct_type_symbol.name].model_copy(),
166
178
  {field: sympy_to_python(field_value) for field, field_value in kwargs.items()},
167
179
  )
168
180
 
@@ -236,7 +248,7 @@ def _is_qmod_value(val: Any) -> bool:
236
248
 
237
249
 
238
250
  def do_subscript(value: Any, index: Any) -> Any:
239
- if not isinstance(value, (list, ClassicalArrayProxy)) or not isinstance(
251
+ if not isinstance(value, (list, ClassicalSequenceProxy)) or not isinstance(
240
252
  index, QmodQNumProxy
241
253
  ):
242
254
  if isinstance(index, (QmodSizedProxy, QmodStructInstance)):
@@ -260,7 +272,7 @@ def do_subscript(value: Any, index: Any) -> Any:
260
272
  "Quantum numeric subscript must be an unsigned integer (is_signed=False, "
261
273
  "fraction_digits=0)"
262
274
  )
263
- if isinstance(value, ClassicalArrayProxy):
275
+ if isinstance(value, ClassicalSequenceProxy):
264
276
  length = value.length
265
277
  else:
266
278
  length = len(value)
@@ -269,7 +281,7 @@ def do_subscript(value: Any, index: Any) -> Any:
269
281
  f"Quantum numeric subscript size mismatch: The quantum numeric has "
270
282
  f"{index.size} qubits but the list size is {length} != 2**{index.size}"
271
283
  )
272
- if isinstance(value, ClassicalArrayProxy):
284
+ if isinstance(value, ClassicalSequenceProxy):
273
285
  return AnyClassicalValue(
274
286
  f"do_subscript({qmod_val_to_expr_str(value)}, {qmod_val_to_expr_str(index)})"
275
287
  )
@@ -289,7 +301,9 @@ def do_slice(value: Any, lower: Any, upper: Any) -> Any:
289
301
 
290
302
 
291
303
  def do_sum(val: Any) -> Any:
292
- if isinstance(val, AnyClassicalValue):
304
+ if (isinstance(val, sympy.Basic) and len(val.free_symbols) > 0) or (
305
+ isinstance(val, ClassicalArrayProxy) and not isinstance(val.length, int)
306
+ ):
293
307
  return AnyClassicalValue(f"sum({val})")
294
308
  return sum(val)
295
309
 
@@ -3,8 +3,10 @@ from collections.abc import Sequence
3
3
  from classiq.interface.generator.functions.port_declaration import (
4
4
  PortDeclarationDirection,
5
5
  )
6
+ from classiq.interface.generator.functions.type_qualifier import TypeQualifier
6
7
  from classiq.interface.model.port_declaration import AnonPortDeclaration
7
8
  from classiq.interface.model.quantum_function_declaration import AnonPositionalArg
9
+ from classiq.interface.model.quantum_type import QuantumNumeric
8
10
 
9
11
  from classiq.model_expansions.evaluators.quantum_type_utils import copy_type_information
10
12
  from classiq.model_expansions.scope import Evaluated, QuantumVariable
@@ -40,3 +42,21 @@ def add_information_from_output_arguments(
40
42
  argument_as_quantum_symbol.quantum_type,
41
43
  str(argument_as_quantum_symbol),
42
44
  )
45
+
46
+
47
+ def handle_args_numeric_bounds(
48
+ parameters: Sequence[AnonPositionalArg],
49
+ args: list[Evaluated],
50
+ ) -> None:
51
+ for parameter, argument in zip(parameters, args):
52
+ if not isinstance(parameter, AnonPortDeclaration):
53
+ continue
54
+
55
+ argument_as_quantum_symbol = argument.as_type(QuantumVariable)
56
+
57
+ if (
58
+ parameter.direction != PortDeclarationDirection.Output
59
+ and parameter.type_qualifier != TypeQualifier.Const
60
+ and isinstance(argument_as_quantum_symbol.quantum_type, QuantumNumeric)
61
+ ):
62
+ argument_as_quantum_symbol.quantum_type.reset_bounds()
@@ -1,11 +1,12 @@
1
- from typing import Any, Union
1
+ from typing import TYPE_CHECKING, Any, Union
2
2
 
3
3
  from classiq.interface.exceptions import ClassiqExpansionError
4
4
  from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
5
5
  AnyClassicalValue,
6
6
  )
7
7
  from classiq.interface.generator.expressions.proxies.classical.classical_array_proxy import (
8
- ClassicalArrayProxy,
8
+ ClassicalSequenceProxy,
9
+ _is_int,
9
10
  )
10
11
  from classiq.interface.generator.expressions.proxies.classical.classical_struct_proxy import (
11
12
  ClassicalStructProxy,
@@ -16,6 +17,7 @@ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_insta
16
17
  from classiq.interface.generator.functions.classical_type import (
17
18
  ClassicalArray,
18
19
  ClassicalList,
20
+ ClassicalTuple,
19
21
  ClassicalType,
20
22
  )
21
23
  from classiq.interface.generator.functions.type_name import TypeName
@@ -26,7 +28,7 @@ from classiq.interface.helpers.backward_compatibility import zip_strict
26
28
  def infer_classical_type(val: Any, classical_type: ClassicalType) -> ClassicalType:
27
29
  if isinstance(classical_type, TypeName):
28
30
  return _infer_classical_struct_type(val, classical_type)
29
- if isinstance(classical_type, (ClassicalArray, ClassicalList)):
31
+ if isinstance(classical_type, (ClassicalArray, ClassicalList, ClassicalTuple)):
30
32
  return _infer_classical_array_type(val, classical_type)
31
33
  return classical_type
32
34
 
@@ -34,7 +36,11 @@ def infer_classical_type(val: Any, classical_type: ClassicalType) -> ClassicalTy
34
36
  def _infer_classical_struct_type(val: Any, classical_type: TypeName) -> ClassicalType:
35
37
  if not isinstance(val, (QmodStructInstance, ClassicalStructProxy)):
36
38
  return classical_type
37
- decl = val.struct_declaration
39
+ if classical_type.is_enum:
40
+ raise ClassiqExpansionError(
41
+ f"{classical_type.type_name!r} expected, got {str(val)!r}"
42
+ )
43
+ decl = classical_type.classical_struct_decl
38
44
  new_fields = {
39
45
  field_name: infer_classical_type(field_val, field_type)
40
46
  for (field_name, field_val), field_type in zip_strict(
@@ -51,9 +57,9 @@ def _infer_classical_struct_type(val: Any, classical_type: TypeName) -> Classica
51
57
 
52
58
 
53
59
  def _infer_classical_array_type(
54
- val: Any, classical_type: Union[ClassicalArray, ClassicalList]
60
+ val: Any, classical_type: Union[ClassicalArray, ClassicalList, ClassicalTuple]
55
61
  ) -> ClassicalType:
56
- if isinstance(val, ClassicalArrayProxy):
62
+ if isinstance(val, ClassicalSequenceProxy):
57
63
  val_length = val.length
58
64
  elif isinstance(val, list):
59
65
  val_length = len(val)
@@ -62,7 +68,7 @@ def _infer_classical_array_type(
62
68
  else:
63
69
  raise ClassiqExpansionError(f"Array expected, got {str(val)!r}")
64
70
  if (
65
- isinstance(classical_type, ClassicalArray)
71
+ isinstance(classical_type, (ClassicalArray, ClassicalTuple))
66
72
  and isinstance(val_length, int)
67
73
  and isinstance(classical_type.size, int)
68
74
  and val_length != classical_type.size
@@ -71,11 +77,34 @@ def _infer_classical_array_type(
71
77
  f"Type mismatch: Argument has {val_length} items but "
72
78
  f"{classical_type.size} expected"
73
79
  )
74
- return ClassicalArray(
75
- element_type=(
76
- infer_classical_type(val[0], classical_type.element_type)
77
- if not isinstance(val_length, int) or val_length > 0
78
- else classical_type.element_type
80
+ new_classical_type = _infer_inner_array_types(classical_type, val, val_length)
81
+ if classical_type.is_generative:
82
+ new_classical_type.set_generative()
83
+ return new_classical_type
84
+
85
+
86
+ def _infer_inner_array_types(
87
+ classical_type: ClassicalType, val: Any, val_length: Any
88
+ ) -> ClassicalType:
89
+ if isinstance(classical_type, (ClassicalArray, ClassicalList)):
90
+ if _is_int(val_length) and val_length != 0:
91
+ return ClassicalTuple(
92
+ element_types=(
93
+ infer_classical_type(val[i], classical_type.element_type)
94
+ for i in range(int(val_length))
95
+ ),
96
+ )
97
+ element_type: ClassicalType
98
+ if val_length == 0:
99
+ element_type = classical_type.element_type
100
+ else:
101
+ element_type = infer_classical_type(val[0], classical_type.element_type)
102
+ return ClassicalArray(element_type=element_type, size=val_length)
103
+ if TYPE_CHECKING:
104
+ assert isinstance(classical_type, ClassicalTuple)
105
+ return ClassicalTuple(
106
+ element_types=(
107
+ infer_classical_type(val[idx], element_type)
108
+ for idx, element_type in enumerate(classical_type.element_types)
79
109
  ),
80
- size=val_length,
81
110
  )
@@ -38,6 +38,7 @@ def copy_type_information(
38
38
  expr=str(from_type.fraction_digits_value)
39
39
  )
40
40
  set_size(to_type, from_type.size_in_bits, to_param_name)
41
+ set_bounds(from_type, to_type)
41
42
  elif isinstance(to_type, QuantumBitvector):
42
43
  if isinstance(from_type, QuantumBitvector) and type( # noqa: E721
43
44
  from_type.element_type
@@ -146,16 +147,20 @@ def set_length_by_size(
146
147
  quantum_array.length = Expression(expr=str(size // element_size))
147
148
 
148
149
 
149
- def validate_bind_targets(bind: BindOperation, scope: Scope) -> None:
150
+ def validate_bind_targets(
151
+ bind: BindOperation, scope: Scope, allow_symbolic_size: bool
152
+ ) -> None:
150
153
  illegal_qnum_bind_targets = []
151
154
  for out_handle in bind.out_handles:
152
155
  out_var = scope[out_handle.name].as_type(QuantumSymbol)
153
156
  out_var_type = out_var.quantum_type
154
157
  if not isinstance(out_var_type, QuantumNumeric):
155
158
  continue
156
- if not out_var_type.has_size_in_bits:
159
+ if (allow_symbolic_size and not out_var_type.is_instantiated) or (
160
+ not allow_symbolic_size and not out_var_type.has_size_in_bits
161
+ ):
157
162
  illegal_qnum_bind_targets.append(str(out_var.handle))
158
- elif not out_var_type.has_sign:
163
+ elif not allow_symbolic_size and not out_var_type.has_sign:
159
164
  assert not out_var_type.has_fraction_digits
160
165
  illegal_qnum_bind_targets.append(str(out_var.handle))
161
166
  if len(illegal_qnum_bind_targets) > 0:
@@ -187,3 +192,26 @@ def is_signature_monomorphic(params: Sequence[PositionalArg]) -> bool:
187
192
  isinstance(param, PortDeclaration) and param.quantum_type.is_evaluated
188
193
  for param in params
189
194
  )
195
+
196
+
197
+ def set_bounds(from_type: QuantumType, to_type: QuantumNumeric) -> None:
198
+ if not isinstance(from_type, QuantumNumeric):
199
+ to_type.reset_bounds()
200
+ return
201
+
202
+ if from_type.is_evaluated and to_type.is_evaluated:
203
+ same_attributes = to_type.sign_value == from_type.sign_value and (
204
+ to_type.fraction_digits_value == from_type.fraction_digits_value
205
+ )
206
+ else:
207
+ same_attributes = (
208
+ (from_type.is_signed is not None and from_type.fraction_digits is not None)
209
+ and (to_type.is_signed is not None and to_type.fraction_digits is not None)
210
+ and (to_type.is_signed.expr == from_type.is_signed.expr)
211
+ and (to_type.fraction_digits.expr == from_type.fraction_digits.expr)
212
+ )
213
+
214
+ if same_attributes:
215
+ to_type.set_bounds(from_type.get_bounds())
216
+ else:
217
+ to_type.reset_bounds()
@@ -114,7 +114,7 @@ def translate_ast_arg_to_python_qmod(param: PositionalArg, value: Any) -> Any:
114
114
  return [QTerminalCallable(inner_decl, index_=idx) for idx in range(len(value))]
115
115
  if (
116
116
  isinstance(value, QmodStructInstance)
117
- and param.classical_type.is_purely_declarative
117
+ and not param.classical_type.is_purely_generative
118
118
  ):
119
119
  classical_type = Struct(name=value.struct_declaration.name)
120
120
  classical_type.set_classical_struct_decl(value.struct_declaration)
@@ -15,6 +15,7 @@ from classiq.interface.generator.functions.builtins.internal_operators import (
15
15
  )
16
16
  from classiq.interface.model.allocate import Allocate
17
17
  from classiq.interface.model.bind_operation import BindOperation
18
+ from classiq.interface.model.bounds import SetBoundsStatement
18
19
  from classiq.interface.model.classical_if import ClassicalIf
19
20
  from classiq.interface.model.control import Control
20
21
  from classiq.interface.model.inplace_binary_operation import InplaceBinaryOperation
@@ -66,6 +67,7 @@ from classiq.model_expansions.quantum_operations.block_evaluator import (
66
67
  IfElimination,
67
68
  RepeatElimination,
68
69
  )
70
+ from classiq.model_expansions.quantum_operations.bounds import SetBoundsEmitter
69
71
  from classiq.model_expansions.quantum_operations.composite_emitter import (
70
72
  CompositeEmitter,
71
73
  )
@@ -297,6 +299,13 @@ class GenerativeInterpreter(BaseInterpreter):
297
299
  ],
298
300
  ).emit(phase)
299
301
 
302
+ @emit.register
303
+ def emit_set_bounds(self, op: SetBoundsStatement) -> None:
304
+ CompositeEmitter[SetBoundsStatement](
305
+ self,
306
+ [HandleEvaluator(self, "target"), SetBoundsEmitter(self)],
307
+ ).emit(op)
308
+
300
309
  def _expand_body(self, operation: Closure) -> None:
301
310
  if isinstance(operation, FunctionClosure) and operation.name == "permute":
302
311
  # special expansion since permute is generative
@@ -1,4 +1,7 @@
1
1
  from classiq.model_expansions.quantum_operations.bind import BindEmitter
2
+ from classiq.model_expansions.quantum_operations.bounds import (
3
+ SetBoundsEmitter,
4
+ )
2
5
  from classiq.model_expansions.quantum_operations.quantum_function_call import (
3
6
  QuantumFunctionCallEmitter,
4
7
  )
@@ -10,7 +10,7 @@ from classiq.interface.generator.expressions.proxies.classical.any_classical_val
10
10
  )
11
11
  from classiq.interface.model.allocate import Allocate
12
12
  from classiq.interface.model.handle_binding import NestedHandleBinding
13
- from classiq.interface.model.quantum_type import QuantumBitvector
13
+ from classiq.interface.model.quantum_type import QuantumBitvector, QuantumNumeric
14
14
 
15
15
  from classiq.model_expansions.evaluators.quantum_type_utils import copy_type_information
16
16
  from classiq.model_expansions.quantum_operations.emitter import Emitter
@@ -38,6 +38,8 @@ class AllocateEmitter(Emitter[Allocate]):
38
38
  )
39
39
 
40
40
  size_expr = self._get_var_size(target, allocate.size)
41
+ if isinstance(target.quantum_type, QuantumNumeric):
42
+ target.quantum_type.set_bounds((0, 0))
41
43
  allocate = allocate.model_copy(
42
44
  update=dict(
43
45
  size=Expression(expr=size_expr),
@@ -37,15 +37,17 @@ from classiq.qmod.builtins.functions.standard_gates import CX
37
37
 
38
38
  class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
39
39
  def emit(self, op: QuantumAssignmentOperation, /) -> bool:
40
+ result_symbol = self._interpreter.evaluate(op.result_var).as_type(QuantumSymbol)
41
+ result_type = result_symbol.quantum_type
42
+
40
43
  if not (
41
44
  isinstance(op, ArithmeticOperation)
42
45
  and op.operation_kind == ArithmeticOperationKind.Assignment
43
46
  ):
47
+ if isinstance(result_type, QuantumNumeric):
48
+ result_type.reset_bounds()
44
49
  return False
45
50
 
46
- result_symbol = self._interpreter.evaluate(op.result_var).as_type(QuantumSymbol)
47
- result_type = result_symbol.quantum_type
48
-
49
51
  validate_assignment_bool_expression(
50
52
  result_symbol, op.expression.expr, op.operation_kind
51
53
  )
@@ -69,6 +71,7 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
69
71
  result_type, inferred_result_type, str(op.result_var)
70
72
  )
71
73
  self._assign_to_inferred_var_and_bind(op, result_type, inferred_result_type)
74
+ result_type.set_bounds(inferred_result_type.get_bounds())
72
75
  return True
73
76
 
74
77
  def _infer_result_type(self, op: ArithmeticOperation) -> Optional[QuantumNumeric]:
@@ -105,6 +108,7 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
105
108
  result_type.fraction_digits = Expression(
106
109
  expr=str(inferred_result_type.fraction_digits_value)
107
110
  )
111
+ result_type.set_bounds(inferred_result_type.get_bounds())
108
112
 
109
113
  @staticmethod
110
114
  def _same_numeric_attributes(
@@ -1,3 +1,4 @@
1
+ from itertools import chain
1
2
  from typing import TYPE_CHECKING
2
3
 
3
4
  from classiq.interface.exceptions import (
@@ -5,6 +6,7 @@ from classiq.interface.exceptions import (
5
6
  ClassiqInternalExpansionError,
6
7
  )
7
8
  from classiq.interface.model.bind_operation import BindOperation
9
+ from classiq.interface.model.quantum_type import QuantumNumeric
8
10
 
9
11
  from classiq.model_expansions.evaluators.parameter_types import (
10
12
  evaluate_types_in_quantum_symbols,
@@ -29,8 +31,13 @@ class BindEmitter(Emitter[BindOperation]):
29
31
 
30
32
  def emit(self, bind: BindOperation, /) -> bool:
31
33
  inputs, outputs = self._get_inputs_outputs(bind)
32
- validate_bind_targets(bind, self._current_scope)
34
+ validate_bind_targets(bind, self._current_scope, self._allow_symbolic_size)
33
35
  self._process_var_sizes(bind, inputs, outputs)
36
+
37
+ for symbol in chain(inputs, outputs):
38
+ if isinstance(symbol.quantum_type, QuantumNumeric):
39
+ symbol.quantum_type.reset_bounds()
40
+
34
41
  self.emit_statement(
35
42
  BindOperation(
36
43
  in_handles=bind.in_handles,
@@ -0,0 +1,30 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from classiq.interface.exceptions import ClassiqExpansionError
4
+ from classiq.interface.model.bounds import SetBoundsStatement
5
+
6
+ from classiq.model_expansions.quantum_operations.bind import Emitter
7
+ from classiq.model_expansions.scope import QuantumSymbol
8
+ from classiq.qmod.qmod_variable import QuantumNumeric
9
+
10
+ if TYPE_CHECKING:
11
+ from classiq.model_expansions.interpreters.base_interpreter import BaseInterpreter
12
+
13
+
14
+ class SetBoundsEmitter(Emitter[SetBoundsStatement]):
15
+ def __init__(
16
+ self, interpreter: "BaseInterpreter", keep_statement: bool = True
17
+ ) -> None:
18
+ super().__init__(interpreter)
19
+ self._keep_statement = keep_statement
20
+
21
+ def emit(self, op: SetBoundsStatement, /) -> bool:
22
+ target = self._interpreter.evaluate(op.target).as_type(QuantumSymbol)
23
+ if not isinstance(target.quantum_type, QuantumNumeric):
24
+ raise ClassiqExpansionError(
25
+ f"Cannot set bounds of a non-numeric variable {op.target.qmod_expr!r}"
26
+ )
27
+ target.quantum_type.set_bounds(op.bounds)
28
+ if self._keep_statement:
29
+ self.emit_statement(op)
30
+ return True
@@ -29,6 +29,7 @@ from classiq.interface.generator.functions.port_declaration import (
29
29
  PortDeclarationDirection,
30
30
  )
31
31
  from classiq.interface.generator.functions.type_qualifier import TypeQualifier
32
+ from classiq.interface.helpers.backward_compatibility import zip_strict
32
33
  from classiq.interface.model.classical_parameter_declaration import (
33
34
  ClassicalParameterDeclaration,
34
35
  )
@@ -55,6 +56,7 @@ from classiq.model_expansions.capturing.captured_vars import (
55
56
  from classiq.model_expansions.closure import Closure, FunctionClosure
56
57
  from classiq.model_expansions.evaluators.argument_types import (
57
58
  add_information_from_output_arguments,
59
+ handle_args_numeric_bounds,
58
60
  )
59
61
  from classiq.model_expansions.evaluators.parameter_types import (
60
62
  evaluate_parameter_types_from_args,
@@ -81,6 +83,7 @@ from classiq.model_expansions.transformers.type_qualifier_inference import (
81
83
  )
82
84
  from classiq.model_expansions.transformers.var_splitter import VarSplitter
83
85
  from classiq.model_expansions.utils.text_utils import are, readable_list, s
86
+ from classiq.qmod.pretty_print.expression_to_python import transform_expression
84
87
  from classiq.qmod.semantics.validation.signature_validation import (
85
88
  validate_function_signature,
86
89
  )
@@ -123,6 +126,24 @@ def _is_symbolic(arg: Any) -> bool:
123
126
  return False
124
127
 
125
128
 
129
+ def _validate_gen_args(
130
+ function: FunctionClosure, evaluated_args: list[Evaluated]
131
+ ) -> None:
132
+ for param, arg in zip_strict(
133
+ function.positional_arg_declarations, evaluated_args, strict=True
134
+ ):
135
+ if (
136
+ isinstance(param, ClassicalParameterDeclaration)
137
+ and param.classical_type.is_purely_generative
138
+ and _is_symbolic(arg.value)
139
+ ):
140
+ raise ClassiqExpansionError(
141
+ f"Parameter {param.name!r} is used in a compile-time expression "
142
+ f"context but is passed a runtime expression "
143
+ f"{transform_expression(str(arg.value), {}, {}, one_line=True)!r}"
144
+ )
145
+
146
+
126
147
  class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSplitter):
127
148
  def __init__(self, interpreter: "BaseInterpreter") -> None:
128
149
  Emitter.__init__(self, interpreter)
@@ -187,6 +208,9 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
187
208
  function.name, new_declaration
188
209
  )
189
210
  else:
211
+ # FIXME: enable for BE (CLS-2390)
212
+ if type(self._interpreter).__name__ == "FrontendGenerativeInterpreter":
213
+ _validate_gen_args(function, evaluated_args)
190
214
  new_declaration = self._expand_function(
191
215
  evaluated_args, new_declaration, function
192
216
  )
@@ -198,10 +222,20 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
198
222
  ]
199
223
 
200
224
  add_information_from_output_arguments(new_positional_arg_decls, evaluated_args)
201
- new_positional_args = [arg.emit() for arg in evaluated_args]
225
+ handle_args_numeric_bounds(new_positional_arg_decls, evaluated_args)
202
226
  captured_args = function.captured_vars.filter_vars(function).get_captured_args(
203
227
  self._builder.current_function
204
228
  )
229
+ new_positional_args = [
230
+ arg.emit(param)
231
+ for param, arg in zip_strict(
232
+ new_positional_arg_decls[
233
+ : len(new_positional_arg_decls) - len(captured_args)
234
+ ],
235
+ evaluated_args,
236
+ strict=True,
237
+ )
238
+ ]
205
239
  validate_args_are_not_propagated(
206
240
  new_positional_args,
207
241
  captured_args,
@@ -240,9 +274,11 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
240
274
  decl: NamedParamsQuantumFunctionDeclaration,
241
275
  function: FunctionClosure,
242
276
  ) -> NamedParamsQuantumFunctionDeclaration:
243
- self._add_params_to_scope(decl.positional_arg_declarations, args, function)
277
+ inferred_args = self._add_params_to_scope(
278
+ decl.positional_arg_declarations, args, function
279
+ )
244
280
  function = function.with_new_declaration(decl)
245
- cache_key = get_func_call_cache_key(decl, args)
281
+ cache_key = get_func_call_cache_key(decl, inferred_args)
246
282
  if cache_key in self._expanded_functions:
247
283
  function_def = self._expanded_functions[cache_key]
248
284
  self._expand_cached_function(function, function_def)
@@ -305,12 +341,13 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
305
341
  parameters: Sequence[PositionalArg],
306
342
  arguments: Sequence[Evaluated],
307
343
  closure: FunctionClosure,
308
- ) -> None:
344
+ ) -> list[Evaluated]:
345
+ inferred_args: list[Evaluated] = []
309
346
  for parameter, argument in zip(parameters, arguments):
310
347
  param_handle = HandleBinding(name=parameter.name)
311
348
  if isinstance(argument.value, QuantumVariable):
312
349
  assert isinstance(parameter, PortDeclaration)
313
- closure.scope[parameter.name] = Evaluated(
350
+ inferred_arg = Evaluated(
314
351
  QuantumSymbol(
315
352
  handle=param_handle,
316
353
  quantum_type=parameter.quantum_type,
@@ -319,12 +356,15 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
319
356
  )
320
357
  elif _is_symbolic(argument.value):
321
358
  assert isinstance(parameter, ClassicalParameterDeclaration)
322
- closure.scope[parameter.name] = Evaluated(
359
+ inferred_arg = Evaluated(
323
360
  value=parameter.classical_type.get_classical_proxy(param_handle),
324
361
  defining_function=closure,
325
362
  )
326
363
  else:
327
- closure.scope[parameter.name] = argument
364
+ inferred_arg = argument
365
+ closure.scope[parameter.name] = inferred_arg
366
+ inferred_args.append(inferred_arg)
367
+ return inferred_args
328
368
 
329
369
  def _prepare_fully_typed_declaration(
330
370
  self, function: FunctionClosure, evaluated_args: list[Evaluated]
@@ -18,6 +18,7 @@ from classiq.interface.model.port_declaration import PortDeclaration
18
18
  from classiq.interface.model.quantum_function_declaration import (
19
19
  NamedParamsQuantumFunctionDeclaration,
20
20
  )
21
+ from classiq.interface.model.quantum_type import QuantumNumeric
21
22
 
22
23
  from classiq.model_expansions.closure import FunctionClosure
23
24
  from classiq.model_expansions.scope import (
@@ -73,7 +74,13 @@ def _evaluated_arg_to_str(arg: Any) -> str:
73
74
 
74
75
 
75
76
  def _evaluated_quantum_symbol_to_str(port: QuantumSymbol) -> str:
76
- return port.quantum_type.model_dump_json(exclude_none=True, exclude={"name"})
77
+ res = port.quantum_type.model_dump_json(exclude_none=True, exclude={"name"})
78
+ if (
79
+ isinstance(port.quantum_type, QuantumNumeric)
80
+ and (bounds := port.quantum_type.get_bounds()) is not None
81
+ ):
82
+ res += f"_{float(bounds[0])}_{float(bounds[1])}"
83
+ return res
77
84
 
78
85
 
79
86
  def _evaluated_one_operand_to_str(operand: FunctionClosure) -> str:
@@ -1,10 +1,9 @@
1
1
  from typing import TYPE_CHECKING
2
2
 
3
+ import sympy
4
+
3
5
  from classiq.interface.exceptions import ClassiqInternalExpansionError
4
6
  from classiq.interface.generator.expressions.expression import Expression
5
- from classiq.interface.generator.expressions.proxies.classical.classical_scalar_proxy import (
6
- ClassicalScalarProxy,
7
- )
8
7
  from classiq.interface.model.classical_if import ClassicalIf
9
8
  from classiq.interface.model.quantum_function_call import QuantumFunctionCall
10
9
  from classiq.interface.model.quantum_lambda_function import OperandIdentifier
@@ -29,7 +28,7 @@ class QuantumFunctionCallEmitter(CallEmitter[QuantumFunctionCall]):
29
28
  def emit(self, call: QuantumFunctionCall, /) -> bool:
30
29
  if isinstance(call.function, OperandIdentifier):
31
30
  index_val = self._interpreter.evaluate(call.function.index).value
32
- if isinstance(index_val, ClassicalScalarProxy):
31
+ if isinstance(index_val, sympy.Basic):
33
32
  return self._emit_symbolic_lambda_list(call, index_val)
34
33
  function = self._interpreter.evaluate(call.function).as_type(FunctionClosure)
35
34
  args = call.positional_args
@@ -40,7 +39,7 @@ class QuantumFunctionCallEmitter(CallEmitter[QuantumFunctionCall]):
40
39
  return True
41
40
 
42
41
  def _emit_symbolic_lambda_list(
43
- self, call: QuantumFunctionCall, index: ClassicalScalarProxy
42
+ self, call: QuantumFunctionCall, index: sympy.Basic
44
43
  ) -> bool:
45
44
  if TYPE_CHECKING:
46
45
  assert isinstance(call.function, OperandIdentifier)
@@ -55,7 +54,7 @@ class QuantumFunctionCallEmitter(CallEmitter[QuantumFunctionCall]):
55
54
 
56
55
  @staticmethod
57
56
  def _create_recursive_if(
58
- call: QuantumFunctionCall, index: ClassicalScalarProxy, num_funcs: int
57
+ call: QuantumFunctionCall, index: sympy.Basic, num_funcs: int
59
58
  ) -> list[QuantumStatement]:
60
59
  if TYPE_CHECKING:
61
60
  assert isinstance(call.function, OperandIdentifier)