classiq 0.75.0__py3-none-any.whl → 0.76.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. classiq/_internals/api_wrapper.py +36 -0
  2. classiq/analyzer/show_interactive_hack.py +58 -2
  3. classiq/applications/chemistry/chemistry_model_constructor.py +8 -1
  4. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +2 -0
  5. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -4
  6. classiq/applications/qnn/gradients/quantum_gradient.py +3 -5
  7. classiq/applications/qnn/gradients/simple_quantum_gradient.py +2 -2
  8. classiq/applications/qnn/qlayer.py +14 -19
  9. classiq/applications/qnn/types.py +1 -4
  10. classiq/execution/__init__.py +3 -0
  11. classiq/execution/execution_session.py +3 -16
  12. classiq/execution/qnn.py +2 -2
  13. classiq/execution/user_budgets.py +38 -0
  14. classiq/executor.py +7 -19
  15. classiq/interface/_version.py +1 -1
  16. classiq/interface/debug_info/debug_info.py +16 -2
  17. classiq/interface/executor/user_budget.py +56 -0
  18. classiq/interface/generator/application_apis/finance_declarations.py +3 -0
  19. classiq/interface/generator/expressions/atomic_expression_functions.py +3 -0
  20. classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +30 -124
  21. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +45 -21
  22. classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
  23. classiq/interface/generator/expressions/proxies/classical/utils.py +12 -11
  24. classiq/interface/generator/expressions/proxies/quantum/qmod_qscalar_proxy.py +9 -2
  25. classiq/interface/generator/expressions/proxies/quantum/qmod_sized_proxy.py +4 -1
  26. classiq/interface/generator/expressions/sympy_supported_expressions.py +1 -0
  27. classiq/interface/generator/functions/classical_type.py +6 -1
  28. classiq/interface/generator/functions/type_name.py +7 -2
  29. classiq/interface/generator/functions/type_qualifier.py +15 -0
  30. classiq/interface/generator/model/preferences/preferences.py +7 -0
  31. classiq/interface/generator/quantum_program.py +5 -19
  32. classiq/interface/helpers/backward_compatibility.py +9 -0
  33. classiq/interface/helpers/datastructures.py +6 -0
  34. classiq/interface/model/port_declaration.py +1 -2
  35. classiq/interface/model/quantum_lambda_function.py +2 -1
  36. classiq/interface/server/routes.py +6 -0
  37. classiq/model_expansions/atomic_expression_functions_defs.py +62 -19
  38. classiq/model_expansions/capturing/captured_vars.py +2 -0
  39. classiq/model_expansions/closure.py +5 -0
  40. classiq/model_expansions/evaluators/classical_type_inference.py +17 -6
  41. classiq/model_expansions/evaluators/parameter_types.py +26 -13
  42. classiq/model_expansions/expression_evaluator.py +1 -1
  43. classiq/model_expansions/generative_functions.py +61 -34
  44. classiq/model_expansions/interpreters/base_interpreter.py +17 -6
  45. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +5 -0
  46. classiq/model_expansions/interpreters/generative_interpreter.py +13 -1
  47. classiq/model_expansions/quantum_operations/allocate.py +6 -1
  48. classiq/model_expansions/quantum_operations/assignment_result_processor.py +219 -20
  49. classiq/model_expansions/quantum_operations/bind.py +54 -30
  50. classiq/model_expansions/quantum_operations/block_evaluator.py +42 -0
  51. classiq/model_expansions/quantum_operations/call_emitter.py +14 -7
  52. classiq/model_expansions/quantum_operations/composite_emitter.py +1 -1
  53. classiq/model_expansions/quantum_operations/declarative_call_emitter.py +23 -9
  54. classiq/model_expansions/quantum_operations/emitter.py +20 -3
  55. classiq/model_expansions/quantum_operations/quantum_function_call.py +4 -3
  56. classiq/model_expansions/scope.py +10 -7
  57. classiq/model_expansions/sympy_conversion/arithmetics.py +18 -0
  58. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +2 -0
  59. classiq/model_expansions/sympy_conversion/sympy_to_python.py +10 -1
  60. classiq/model_expansions/transformers/model_renamer.py +45 -7
  61. classiq/model_expansions/utils/handles_collector.py +1 -1
  62. classiq/model_expansions/visitors/variable_references.py +45 -9
  63. classiq/qmod/builtins/functions/allocation.py +2 -2
  64. classiq/qmod/builtins/functions/arithmetic.py +14 -12
  65. classiq/qmod/builtins/functions/standard_gates.py +23 -23
  66. classiq/qmod/declaration_inferrer.py +19 -7
  67. classiq/qmod/generative.py +9 -1
  68. classiq/qmod/native/expression_to_qmod.py +4 -0
  69. classiq/qmod/native/pretty_printer.py +8 -3
  70. classiq/qmod/pretty_print/pretty_printer.py +1 -1
  71. classiq/qmod/python_classical_type.py +4 -5
  72. classiq/qmod/qmod_constant.py +15 -7
  73. classiq/qmod/qmod_variable.py +7 -1
  74. classiq/qmod/quantum_function.py +19 -6
  75. classiq/qmod/semantics/lambdas.py +6 -2
  76. classiq/qmod/semantics/validation/main_validation.py +17 -4
  77. classiq/qmod/symbolic.py +8 -19
  78. classiq/qmod/symbolic_expr.py +26 -0
  79. classiq/synthesis.py +17 -31
  80. classiq/visualization.py +35 -0
  81. {classiq-0.75.0.dist-info → classiq-0.76.0.dist-info}/METADATA +1 -1
  82. {classiq-0.75.0.dist-info → classiq-0.76.0.dist-info}/RECORD +83 -79
  83. {classiq-0.75.0.dist-info → classiq-0.76.0.dist-info}/WHEEL +0 -0
@@ -5,6 +5,9 @@ import sympy
5
5
  from classiq.interface.debug_info.debug_info import FunctionDebugInfo
6
6
  from classiq.interface.exceptions import ClassiqValueError
7
7
  from classiq.interface.generator.expressions.expression import Expression
8
+ from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
9
+ AnyClassicalValue,
10
+ )
8
11
  from classiq.interface.model.allocate import Allocate
9
12
  from classiq.interface.model.handle_binding import NestedHandleBinding
10
13
  from classiq.interface.model.quantum_type import QuantumBitvector
@@ -55,7 +58,9 @@ class AllocateEmitter(Emitter[Allocate]):
55
58
  return str(target.quantum_type.size_in_bits)
56
59
 
57
60
  size_value = self._interpreter.evaluate(size).value
58
- if self._allow_symbolic_size and isinstance(size_value, sympy.Basic):
61
+ if self._allow_symbolic_size and isinstance(
62
+ size_value, (sympy.Basic, AnyClassicalValue)
63
+ ):
59
64
  return str(size_value)
60
65
  if not isinstance(size_value, (int, float)):
61
66
  raise ClassiqValueError(
@@ -1,6 +1,12 @@
1
+ from classiq.interface.exceptions import ClassiqExpansionError
1
2
  from classiq.interface.generator.arith.arithmetic import compute_arithmetic_result_type
2
- from classiq.interface.generator.functions.port_declaration import (
3
- PortDeclarationDirection,
3
+ from classiq.interface.generator.expressions.expression import Expression
4
+ from classiq.interface.model.allocate import Allocate
5
+ from classiq.interface.model.bind_operation import BindOperation
6
+ from classiq.interface.model.handle_binding import (
7
+ ConcreteHandleBinding,
8
+ HandleBinding,
9
+ SubscriptHandleBinding,
4
10
  )
5
11
  from classiq.interface.model.quantum_expressions.arithmetic_operation import (
6
12
  ArithmeticOperation,
@@ -9,6 +15,12 @@ from classiq.interface.model.quantum_expressions.arithmetic_operation import (
9
15
  from classiq.interface.model.quantum_expressions.quantum_expression import (
10
16
  QuantumAssignmentOperation,
11
17
  )
18
+ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
19
+ from classiq.interface.model.quantum_type import QuantumBitvector, QuantumNumeric
20
+ from classiq.interface.model.variable_declaration_statement import (
21
+ VariableDeclarationStatement,
22
+ )
23
+ from classiq.interface.model.within_apply_operation import WithinApply
12
24
 
13
25
  from classiq.model_expansions.evaluators.quantum_type_utils import copy_type_information
14
26
  from classiq.model_expansions.quantum_operations.arithmetic.explicit_boolean_expressions import (
@@ -18,26 +30,49 @@ from classiq.model_expansions.quantum_operations.arithmetic.explicit_boolean_exp
18
30
  from classiq.model_expansions.quantum_operations.emitter import Emitter
19
31
  from classiq.model_expansions.scope import QuantumSymbol
20
32
  from classiq.model_expansions.transformers.ast_renamer import rename_variables
33
+ from classiq.qmod.builtins.functions.standard_gates import CX
21
34
 
22
35
 
23
36
  class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
24
37
  def emit(self, op: QuantumAssignmentOperation, /) -> bool:
25
- if (
38
+ if not (
26
39
  isinstance(op, ArithmeticOperation)
27
40
  and op.operation_kind == ArithmeticOperationKind.Assignment
28
41
  ):
29
- direction = PortDeclarationDirection.Output
30
- self._update_result_type(op)
31
- convert_assignment_bool_expression(op)
32
- else:
33
- direction = PortDeclarationDirection.Inout
34
- self._capture_handle(op.result_var, direction)
35
- return False
36
-
37
- def _update_result_type(self, op: ArithmeticOperation) -> None:
42
+ return False
43
+
44
+ result_symbol = self._interpreter.evaluate(op.result_var).as_type(QuantumSymbol)
45
+ result_type = result_symbol.quantum_type
46
+
47
+ validate_assignment_bool_expression(
48
+ result_symbol, op.expression.expr, op.operation_kind
49
+ )
50
+ convert_assignment_bool_expression(op)
51
+
52
+ inferred_result_type = self._infer_result_type(op)
53
+ if inferred_result_type is None:
54
+ return False
55
+
56
+ if not isinstance(result_type, QuantumNumeric):
57
+ copy_type_information(
58
+ inferred_result_type, result_symbol.quantum_type, str(op.result_var)
59
+ )
60
+ return False
61
+
62
+ self._copy_numeric_attributes(result_type, inferred_result_type)
63
+ if self._same_numeric_attributes(result_type, inferred_result_type):
64
+ return False
65
+
66
+ self._validate_declared_attributes(
67
+ result_type, inferred_result_type, str(op.result_var)
68
+ )
69
+ self._assign_to_inferred_var_and_bind(op, result_type, inferred_result_type)
70
+ return True
71
+
72
+ def _infer_result_type(self, op: ArithmeticOperation) -> QuantumNumeric | None:
38
73
  expr = self._evaluate_expression(op.expression)
39
74
  if len(self._get_classical_vars_in_expression(expr)):
40
- return
75
+ return None
41
76
  symbols = self._get_symbols_in_expression(expr)
42
77
  expr_str = rename_variables(
43
78
  expr.expr,
@@ -48,16 +83,180 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
48
83
  expr_str = expr_str.replace(
49
84
  symbol.handle.qmod_expr, symbol.handle.identifier
50
85
  )
51
- result_type = compute_arithmetic_result_type(
86
+ return compute_arithmetic_result_type(
52
87
  expr_str,
53
88
  {symbol.handle.identifier: symbol.quantum_type for symbol in symbols},
54
89
  self._machine_precision,
55
90
  )
56
- result_symbol = self._interpreter.evaluate(op.result_var).as_type(QuantumSymbol)
57
91
 
58
- validate_assignment_bool_expression(
59
- result_symbol, op.expression.expr, op.operation_kind
60
- ) # must be here, otherwise copy_type_information will throw a non-indicative error
61
- copy_type_information(
62
- result_type, result_symbol.quantum_type, str(op.result_var)
92
+ @staticmethod
93
+ def _copy_numeric_attributes(
94
+ result_type: QuantumNumeric, inferred_result_type: QuantumNumeric
95
+ ) -> None:
96
+ if not result_type.has_size_in_bits:
97
+ result_type.size = Expression(expr=str(inferred_result_type.size_in_bits))
98
+ if not result_type.has_sign:
99
+ result_type.is_signed = Expression(
100
+ expr=str(inferred_result_type.sign_value)
101
+ )
102
+ if not result_type.has_fraction_digits:
103
+ result_type.fraction_digits = Expression(
104
+ expr=str(inferred_result_type.fraction_digits_value)
105
+ )
106
+
107
+ @staticmethod
108
+ def _same_numeric_attributes(
109
+ result_type: QuantumNumeric, inferred_result_type: QuantumNumeric
110
+ ) -> bool:
111
+ return (
112
+ result_type.size_in_bits == inferred_result_type.size_in_bits
113
+ and result_type.sign_value == inferred_result_type.sign_value
114
+ and result_type.fraction_digits_value
115
+ == inferred_result_type.fraction_digits_value
116
+ )
117
+
118
+ @classmethod
119
+ def _validate_declared_attributes(
120
+ cls, result_type: QuantumNumeric, inferred_result_type: QuantumNumeric, var: str
121
+ ) -> None:
122
+ result_size, result_sign, result_fractions = (
123
+ result_type.size_in_bits,
124
+ result_type.sign_value,
125
+ result_type.fraction_digits_value,
126
+ )
127
+ inferred_size, inferred_sign, inferred_fractions = (
128
+ inferred_result_type.size_in_bits,
129
+ inferred_result_type.sign_value,
130
+ inferred_result_type.fraction_digits_value,
131
+ )
132
+ result_integers = result_size - result_fractions
133
+ inferred_integers = inferred_size - inferred_fractions
134
+
135
+ if (
136
+ (result_integers < inferred_integers)
137
+ or (result_fractions < inferred_fractions)
138
+ or (not result_sign and inferred_sign)
139
+ or (
140
+ result_sign
141
+ and not inferred_sign
142
+ and result_integers == inferred_integers
143
+ )
144
+ ):
145
+ if (
146
+ not result_sign
147
+ and result_fractions == 0
148
+ and not inferred_sign
149
+ and inferred_fractions == 0
150
+ ):
151
+ result_size_str = f"size {result_size}"
152
+ inferred_size_str = f"size {inferred_size}"
153
+ hint = f"Hint: increase the size in the declaration of {var!r} or omit it to enable automatic inference."
154
+ else:
155
+ result_size_str = f"size {result_size}, {'signed' if result_sign else 'unsigned'}, and {result_fractions} fraction digits"
156
+ inferred_size_str = f"size {inferred_size}, {'signed' if inferred_sign else 'unsigned'}, and {inferred_fractions} fraction digits"
157
+ hint = f"Hint: omit the numeric attributes from the declaration of {var!r} to enable automatic inference."
158
+ raise ClassiqExpansionError(
159
+ f"Cannot assign an expression with inferred {inferred_size_str} to variable {var!r} with declared {result_size_str}. {hint}"
160
+ )
161
+
162
+ @staticmethod
163
+ def _craft_size_string(size: int, is_signed: bool, fraction_digits: int) -> str:
164
+ extra = (
165
+ f", with {fraction_digits} fraction digits" if fraction_digits > 0 else ""
166
+ )
167
+ return f"{size} ({'signed' if is_signed else 'unsigned'}{extra})"
168
+
169
+ def _assign_to_inferred_var_and_bind(
170
+ self,
171
+ op: ArithmeticOperation,
172
+ result_type: QuantumNumeric,
173
+ inferred_result_type: QuantumNumeric,
174
+ ) -> None:
175
+ handles: list[HandleBinding] = []
176
+
177
+ extra_fraction_digits = (
178
+ result_type.fraction_digits_value
179
+ - inferred_result_type.fraction_digits_value
180
+ )
181
+ if extra_fraction_digits > 0:
182
+ handles.append(
183
+ self._declare_qarray("extra_fraction_digits", extra_fraction_digits)
184
+ )
185
+
186
+ inferred_result_name = self._counted_name_allocator.allocate("inferred_result")
187
+ inferred_result_handle = HandleBinding(name=inferred_result_name)
188
+ self._interpreter.emit(
189
+ VariableDeclarationStatement(
190
+ name=inferred_result_name, quantum_type=inferred_result_type
191
+ )
192
+ )
193
+ handles.append(inferred_result_handle)
194
+ modified_op = op.model_copy(update={"result_var": inferred_result_handle})
195
+ self._interpreter.add_to_debug_info(modified_op)
196
+ self._interpreter.emit(modified_op)
197
+
198
+ result_integer_size = (
199
+ result_type.size_in_bits - result_type.fraction_digits_value
200
+ )
201
+ inferred_result_integer_size = (
202
+ inferred_result_type.size_in_bits
203
+ - inferred_result_type.fraction_digits_value
204
+ )
205
+ extra_integers = result_integer_size - inferred_result_integer_size
206
+ if extra_integers > 0:
207
+ handles.append(self._declare_qarray("extra_integers", extra_integers))
208
+
209
+ self._interpreter.emit(
210
+ BindOperation(in_handles=handles, out_handles=[op.result_var])
211
+ )
212
+
213
+ if (
214
+ result_type.sign_value
215
+ and inferred_result_type.sign_value
216
+ and extra_integers > 0
217
+ ):
218
+ sign_idx = result_type.size_in_bits - extra_integers - 1
219
+ self._sign_extension(op.result_var, sign_idx, result_type.size_in_bits)
220
+
221
+ def _declare_qarray(
222
+ self,
223
+ prefix: str,
224
+ size: int,
225
+ allocate: bool = True,
226
+ ) -> HandleBinding:
227
+ name = self._counted_name_allocator.allocate(prefix)
228
+ handle = HandleBinding(name=name)
229
+ quantum_type = QuantumBitvector(length=Expression(expr=str(size)))
230
+ self._interpreter.emit(
231
+ VariableDeclarationStatement(name=name, quantum_type=quantum_type)
232
+ )
233
+ if allocate:
234
+ self._interpreter.emit(Allocate(target=handle))
235
+ return handle
236
+
237
+ def _sign_extension(
238
+ self,
239
+ result_var: ConcreteHandleBinding,
240
+ sign_idx: int,
241
+ size: int,
242
+ ) -> None:
243
+ aux = self._declare_qarray("inferred_result_aux", size, allocate=False)
244
+ self._interpreter.emit(
245
+ WithinApply(
246
+ compute=[BindOperation(in_handles=[result_var], out_handles=[aux])],
247
+ action=[
248
+ QuantumFunctionCall(
249
+ function=CX.func_decl.name,
250
+ positional_args=[
251
+ SubscriptHandleBinding(
252
+ base_handle=aux, index=Expression(expr=str(sign_idx))
253
+ ),
254
+ SubscriptHandleBinding(
255
+ base_handle=aux, index=Expression(expr=str(idx))
256
+ ),
257
+ ],
258
+ )
259
+ for idx in range(sign_idx + 1, size)
260
+ ],
261
+ )
63
262
  )
@@ -1,3 +1,5 @@
1
+ from typing import TYPE_CHECKING
2
+
1
3
  from classiq.interface.exceptions import (
2
4
  ClassiqExpansionError,
3
5
  ClassiqInternalExpansionError,
@@ -14,41 +16,21 @@ from classiq.model_expansions.evaluators.quantum_type_utils import (
14
16
  from classiq.model_expansions.quantum_operations.emitter import Emitter
15
17
  from classiq.model_expansions.scope import Evaluated, QuantumSymbol
16
18
 
19
+ if TYPE_CHECKING:
20
+ from classiq.model_expansions.interpreters.base_interpreter import BaseInterpreter
21
+
17
22
 
18
23
  class BindEmitter(Emitter[BindOperation]):
24
+ def __init__(
25
+ self, interpreter: "BaseInterpreter", allow_symbolic_size: bool = False
26
+ ) -> None:
27
+ super().__init__(interpreter)
28
+ self._allow_symbolic_size = allow_symbolic_size
29
+
19
30
  def emit(self, bind: BindOperation, /) -> bool:
20
31
  inputs, outputs = self._get_inputs_outputs(bind)
21
32
  validate_bind_targets(bind, self._current_scope)
22
- unsized_outputs = [
23
- output for output in outputs if not output.quantum_type.has_size_in_bits
24
- ]
25
-
26
- if len(unsized_outputs) > 1:
27
- raise ClassiqExpansionError(
28
- f"Cannot perform the split operation {bind.in_handles[0].name} -> {{{', '.join(out_handle.name for out_handle in bind.out_handles)}}}:\n"
29
- f"Quantum variables {', '.join(str(out_handle.handle) for out_handle in unsized_outputs)} are used as bind outputs, but their size cannot be inferred."
30
- )
31
-
32
- input_size = sum(input.quantum_type.size_in_bits for input in inputs)
33
- output_size = sum(
34
- output.quantum_type.size_in_bits
35
- for output in outputs
36
- if output.quantum_type.has_size_in_bits
37
- )
38
-
39
- if len(unsized_outputs) == 1:
40
- set_size(
41
- unsized_outputs[0].quantum_type,
42
- input_size - output_size,
43
- str(unsized_outputs[0].handle),
44
- )
45
-
46
- else:
47
- if input_size != output_size:
48
- raise ClassiqExpansionError(
49
- f"The total size for the input and output of the bind operation must be the same. The in size is {input_size} and the out size is {output_size}"
50
- )
51
-
33
+ self._process_var_sizes(bind, inputs, outputs)
52
34
  self.emit_statement(
53
35
  BindOperation(
54
36
  in_handles=bind.in_handles,
@@ -111,3 +93,45 @@ class BindEmitter(Emitter[BindOperation]):
111
93
  f"{out.value.handle.name!r} on the right-hand side of a bind "
112
94
  f"statement"
113
95
  )
96
+
97
+ def _process_var_sizes(
98
+ self,
99
+ bind: BindOperation,
100
+ inputs: list[QuantumSymbol],
101
+ outputs: list[QuantumSymbol],
102
+ ) -> None:
103
+ unsized_inputs = [
104
+ input for input in inputs if not input.quantum_type.has_size_in_bits
105
+ ]
106
+ if len(unsized_inputs) > 0:
107
+ if self._allow_symbolic_size:
108
+ return
109
+ raise ClassiqInternalExpansionError("Uninitialized bind inputs")
110
+
111
+ unsized_outputs = [
112
+ output for output in outputs if not output.quantum_type.has_size_in_bits
113
+ ]
114
+ if len(unsized_outputs) > 1:
115
+ if self._allow_symbolic_size:
116
+ return
117
+ raise ClassiqExpansionError(
118
+ f"Cannot perform the split operation {bind.in_handles[0].name} -> {{{', '.join(out_handle.name for out_handle in bind.out_handles)}}}:\n"
119
+ f"Quantum variables {', '.join(str(out_handle.handle) for out_handle in unsized_outputs)} are used as bind outputs, but their size cannot be inferred."
120
+ )
121
+
122
+ input_size = sum(input.quantum_type.size_in_bits for input in inputs)
123
+ output_size = sum(
124
+ output.quantum_type.size_in_bits
125
+ for output in outputs
126
+ if output.quantum_type.has_size_in_bits
127
+ )
128
+ if len(unsized_outputs) == 1:
129
+ set_size(
130
+ unsized_outputs[0].quantum_type,
131
+ input_size - output_size,
132
+ str(unsized_outputs[0].handle),
133
+ )
134
+ elif input_size != output_size:
135
+ raise ClassiqExpansionError(
136
+ f"The total size for the input and output of the bind operation must be the same. The in size is {input_size} and the out size is {output_size}"
137
+ )
@@ -1,8 +1,14 @@
1
1
  from collections.abc import Sequence
2
2
  from typing import TYPE_CHECKING
3
3
 
4
+ from classiq.interface.generator.functions.builtins.internal_operators import (
5
+ CLASSICAL_IF_OPERATOR_NAME,
6
+ REPEAT_OPERATOR_NAME,
7
+ )
8
+ from classiq.interface.model.classical_if import ClassicalIf
4
9
  from classiq.interface.model.quantum_function_declaration import PositionalArg
5
10
  from classiq.interface.model.quantum_statement import QuantumOperation, QuantumStatement
11
+ from classiq.interface.model.repeat import Repeat
6
12
 
7
13
  from classiq.model_expansions.closure import Closure
8
14
  from classiq.model_expansions.quantum_operations.emitter import Emitter
@@ -88,3 +94,39 @@ class BlockEvaluator(Emitter[QuantumOperation]):
88
94
 
89
95
  def get_scope(self, op: QuantumOperation) -> Scope:
90
96
  return Scope(parent=self._current_scope)
97
+
98
+
99
+ class IfElimination(BlockEvaluator):
100
+ def __init__(self, interpreter: "BaseInterpreter") -> None:
101
+ super().__init__(interpreter, CLASSICAL_IF_OPERATOR_NAME, "then", "else_")
102
+
103
+ def emit(self, op: ClassicalIf, /) -> bool: # type:ignore[override]
104
+ cond = op.condition.value.value
105
+ if not isinstance(cond, bool):
106
+ return False
107
+ if op.is_generative():
108
+ if cond:
109
+ then_block = op.get_generative_block("then")
110
+ op.clear_generative_blocks()
111
+ op.set_generative_block("then", then_block)
112
+ elif not op.has_generative_block("else_"):
113
+ op.clear_generative_blocks()
114
+ else:
115
+ else_block = op.get_generative_block("else_")
116
+ op.clear_generative_blocks()
117
+ op.set_generative_block("else_", else_block)
118
+ else:
119
+ if cond:
120
+ op = op.model_copy(update={"else_": []})
121
+ else:
122
+ op = op.model_copy(update={"then": []})
123
+ return super().emit(op)
124
+
125
+
126
+ class RepeatElimination(BlockEvaluator):
127
+ def __init__(self, interpreter: "BaseInterpreter") -> None:
128
+ super().__init__(interpreter, REPEAT_OPERATOR_NAME, "body")
129
+
130
+ def emit(self, op: Repeat, /) -> bool: # type:ignore[override]
131
+ count = op.count.value.value
132
+ return isinstance(count, int) and count == 0
@@ -10,7 +10,10 @@ from uuid import UUID
10
10
 
11
11
  import sympy
12
12
 
13
- from classiq.interface.debug_info.debug_info import FunctionDebugInfo
13
+ from classiq.interface.debug_info.debug_info import (
14
+ FunctionDebugInfo,
15
+ calculate_port_to_passed_variable_mapping,
16
+ )
14
17
  from classiq.interface.exceptions import ClassiqExpansionError
15
18
  from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
16
19
  AnyClassicalValue,
@@ -90,13 +93,15 @@ def _validate_cloning(evaluated_args: list[Evaluated]) -> None:
90
93
 
91
94
 
92
95
  def _is_symbolic(arg: Any) -> bool:
96
+ if isinstance(arg, (ClassicalProxy, AnyClassicalValue)):
97
+ return True
93
98
  if isinstance(arg, list):
94
99
  return any(_is_symbolic(item) for item in arg)
95
100
  if isinstance(arg, QmodStructInstance):
96
101
  return any(_is_symbolic(item) for item in arg.fields.values())
97
102
  if isinstance(arg, sympy.Basic):
98
103
  return len(arg.free_symbols) > 0
99
- return isinstance(arg, (ClassicalProxy, AnyClassicalValue))
104
+ return False
100
105
 
101
106
 
102
107
  class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSplitter):
@@ -187,11 +192,13 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
187
192
  back_ref=self._get_back_ref(propagated_debug_info),
188
193
  )
189
194
 
190
- port_to_passed_variable_map = {
191
- arg_decl.name: str(evaluated_arg.value.handle)
192
- for arg_decl, evaluated_arg in zip(new_positional_arg_decls, evaluated_args)
193
- if isinstance(arg_decl, PortDeclaration)
194
- }
195
+ port_to_passed_variable_map = calculate_port_to_passed_variable_mapping(
196
+ new_positional_arg_decls,
197
+ [
198
+ arg.value.handle if isinstance(arg.value, QuantumSymbol) else None
199
+ for arg in evaluated_args
200
+ ],
201
+ )
195
202
  self._debug_info[new_call.uuid] = FunctionDebugInfo(
196
203
  name=new_call.func_name,
197
204
  port_to_passed_variable_map=port_to_passed_variable_map,
@@ -23,5 +23,5 @@ class CompositeEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT]):
23
23
  for emitter in self._emitters:
24
24
  if emitter.emit(statement):
25
25
  return True
26
- self._builder.emit_statement(statement)
26
+ self.emit_statement(statement)
27
27
  return True
@@ -1,5 +1,5 @@
1
1
  from itertools import chain
2
- from typing import TYPE_CHECKING, Generic
2
+ from typing import TYPE_CHECKING, Generic, Optional
3
3
 
4
4
  from classiq.interface.generator.functions.port_declaration import (
5
5
  PortDeclarationDirection,
@@ -34,13 +34,24 @@ class DeclarativeCallEmitter(
34
34
 
35
35
  if self._is_function_purely_declarative(
36
36
  function
37
- ) and self._are_args_purely_declarative(args):
37
+ ) and self._are_args_purely_declarative(args, function.name):
38
38
  self._interpreter.add_purely_declarative_function(function)
39
39
  return False
40
40
 
41
41
  return True
42
42
 
43
- def _is_function_purely_declarative(self, function: FunctionClosure) -> bool:
43
+ def _is_function_purely_declarative(
44
+ self, function: FunctionClosure, seen_funcs: Optional[set[str]] = None
45
+ ) -> bool:
46
+ if seen_funcs is None:
47
+ seen_funcs = set()
48
+ if function.name in seen_funcs:
49
+ return True
50
+ seen_funcs.add(function.name)
51
+
52
+ if function.is_atomic:
53
+ return True
54
+
44
55
  if function.name not in QMODULE.native_defs:
45
56
  return False
46
57
 
@@ -56,9 +67,9 @@ class DeclarativeCallEmitter(
56
67
  return False
57
68
 
58
69
  dependencies = QMODULE.function_dependencies[function.name]
59
- return self._are_identifiers_purely_declarative(dependencies)
70
+ return self._are_identifiers_purely_declarative(dependencies, seen_funcs)
60
71
 
61
- def _are_args_purely_declarative(self, args: list[Evaluated]) -> bool:
72
+ def _are_args_purely_declarative(self, args: list[Evaluated], caller: str) -> bool:
62
73
  values = [arg.value for arg in args]
63
74
  function_inputs: list[FunctionClosure] = list(
64
75
  chain.from_iterable(
@@ -78,10 +89,13 @@ class DeclarativeCallEmitter(
78
89
  if any(func.is_lambda for func in function_inputs):
79
90
  return False
80
91
  dependencies = [func.name for func in function_inputs if not func.is_lambda]
81
- return self._are_identifiers_purely_declarative(dependencies)
92
+ return self._are_identifiers_purely_declarative(dependencies, {caller})
82
93
 
83
- def _are_identifiers_purely_declarative(self, dependencies: list[str]) -> bool:
84
- return not any(
85
- isinstance(self._current_scope[dep].value, GenerativeClosure)
94
+ def _are_identifiers_purely_declarative(
95
+ self, dependencies: list[str], seen_funcs: set[str]
96
+ ) -> bool:
97
+ return all(
98
+ self._is_function_purely_declarative(self._current_scope[dep].value)
86
99
  for dep in dependencies
100
+ if seen_funcs is None or dep not in seen_funcs
87
101
  )
@@ -15,6 +15,11 @@ from classiq.interface.debug_info.debug_info import (
15
15
  DebugInfoCollection,
16
16
  )
17
17
  from classiq.interface.exceptions import ClassiqInternalExpansionError
18
+ from classiq.interface.generator.expressions.atomic_expression_functions import (
19
+ CLASSICAL_ATTRIBUTES,
20
+ SUPPORTED_CLASSIQ_BUILTIN_FUNCTIONS,
21
+ SUPPORTED_PYTHON_BUILTIN_FUNCTIONS,
22
+ )
18
23
  from classiq.interface.generator.expressions.evaluated_expression import (
19
24
  EvaluatedExpression,
20
25
  )
@@ -30,7 +35,11 @@ from classiq.interface.generator.functions.port_declaration import (
30
35
  PortDeclarationDirection,
31
36
  )
32
37
  from classiq.interface.helpers.pydantic_model_helpers import nameables_to_dict
33
- from classiq.interface.model.handle_binding import HandleBinding, NestedHandleBinding
38
+ from classiq.interface.model.handle_binding import (
39
+ FieldHandleBinding,
40
+ HandleBinding,
41
+ NestedHandleBinding,
42
+ )
34
43
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
35
44
  from classiq.interface.model.quantum_function_declaration import (
36
45
  NamedParamsQuantumFunctionDeclaration,
@@ -220,9 +229,17 @@ class Emitter(Generic[QuantumStatementT], ABC):
220
229
  handles = dict.fromkeys(
221
230
  handle
222
231
  for handle in vrc.var_handles
223
- if isinstance(self._current_scope[handle.name].value, QuantumSymbol)
232
+ if handle.name
233
+ not in SUPPORTED_PYTHON_BUILTIN_FUNCTIONS
234
+ | SUPPORTED_CLASSIQ_BUILTIN_FUNCTIONS
235
+ and isinstance(self._current_scope[handle.name].value, QuantumSymbol)
224
236
  )
225
- return [self._interpreter.evaluate(handle).value for handle in handles]
237
+ return [
238
+ self._interpreter.evaluate(handle).value
239
+ for handle in handles
240
+ if not isinstance(handle, FieldHandleBinding)
241
+ or handle.field not in CLASSICAL_ATTRIBUTES
242
+ ]
226
243
 
227
244
  def _get_classical_vars_in_expression(
228
245
  self, expr: Expression
@@ -49,13 +49,14 @@ class QuantumFunctionCallEmitter(CallEmitter[QuantumFunctionCall]):
49
49
  raise ClassiqInternalExpansionError(
50
50
  f"Unexpected lambda list type {type(funcs).__name__!r}"
51
51
  )
52
- self._interpreter.emit(self._create_recursive_if(call, index, len(funcs)))
52
+ for stmt in self._create_recursive_if(call, index, len(funcs)):
53
+ self._interpreter.emit(stmt)
53
54
  return True
54
55
 
55
56
  @staticmethod
56
57
  def _create_recursive_if(
57
58
  call: QuantumFunctionCall, index: ClassicalScalarProxy, num_funcs: int
58
- ) -> QuantumStatement:
59
+ ) -> list[QuantumStatement]:
59
60
  if TYPE_CHECKING:
60
61
  assert isinstance(call.function, OperandIdentifier)
61
62
  stmt: list[QuantumStatement] = []
@@ -74,7 +75,7 @@ class QuantumFunctionCallEmitter(CallEmitter[QuantumFunctionCall]):
74
75
  else_=stmt,
75
76
  )
76
77
  ]
77
- return stmt[0]
78
+ return stmt
78
79
 
79
80
 
80
81
  class DeclarativeQuantumFunctionCallEmitter(