classiq 0.74.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 (96) 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 +23 -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 +18 -13
  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 +46 -22
  22. classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
  23. classiq/interface/generator/expressions/proxies/classical/utils.py +14 -13
  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 +36 -1
  28. classiq/interface/generator/functions/type_name.py +32 -5
  29. classiq/interface/generator/functions/type_qualifier.py +15 -0
  30. classiq/interface/generator/generated_circuit_data.py +11 -25
  31. classiq/interface/generator/model/preferences/preferences.py +7 -0
  32. classiq/interface/generator/quantum_program.py +5 -19
  33. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +10 -13
  34. classiq/interface/helpers/backward_compatibility.py +9 -0
  35. classiq/interface/helpers/datastructures.py +6 -0
  36. classiq/interface/helpers/versioned_model.py +12 -0
  37. classiq/interface/interface_version.py +1 -1
  38. classiq/interface/model/handle_binding.py +12 -0
  39. classiq/interface/model/port_declaration.py +1 -2
  40. classiq/interface/model/quantum_lambda_function.py +2 -1
  41. classiq/interface/model/statement_block.py +9 -1
  42. classiq/interface/model/within_apply_operation.py +12 -0
  43. classiq/interface/server/routes.py +6 -0
  44. classiq/model_expansions/atomic_expression_functions_defs.py +82 -23
  45. classiq/model_expansions/capturing/captured_vars.py +2 -0
  46. classiq/model_expansions/closure.py +18 -0
  47. classiq/model_expansions/evaluators/argument_types.py +6 -5
  48. classiq/model_expansions/evaluators/classical_type_inference.py +17 -6
  49. classiq/model_expansions/evaluators/parameter_types.py +26 -13
  50. classiq/model_expansions/evaluators/type_type_match.py +2 -2
  51. classiq/model_expansions/expression_evaluator.py +1 -1
  52. classiq/model_expansions/generative_functions.py +66 -33
  53. classiq/model_expansions/interpreters/base_interpreter.py +27 -19
  54. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +26 -0
  55. classiq/model_expansions/interpreters/generative_interpreter.py +25 -1
  56. classiq/model_expansions/quantum_operations/allocate.py +27 -11
  57. classiq/model_expansions/quantum_operations/assignment_result_processor.py +220 -19
  58. classiq/model_expansions/quantum_operations/bind.py +54 -30
  59. classiq/model_expansions/quantum_operations/block_evaluator.py +42 -0
  60. classiq/model_expansions/quantum_operations/call_emitter.py +14 -12
  61. classiq/model_expansions/quantum_operations/composite_emitter.py +1 -1
  62. classiq/model_expansions/quantum_operations/declarative_call_emitter.py +23 -9
  63. classiq/model_expansions/quantum_operations/emitter.py +21 -8
  64. classiq/model_expansions/quantum_operations/expression_evaluator.py +1 -0
  65. classiq/model_expansions/quantum_operations/handle_evaluator.py +1 -0
  66. classiq/model_expansions/quantum_operations/quantum_function_call.py +4 -3
  67. classiq/model_expansions/scope.py +10 -7
  68. classiq/model_expansions/sympy_conversion/arithmetics.py +18 -0
  69. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +2 -0
  70. classiq/model_expansions/sympy_conversion/sympy_to_python.py +10 -1
  71. classiq/model_expansions/transformers/model_renamer.py +48 -8
  72. classiq/model_expansions/utils/handles_collector.py +1 -1
  73. classiq/model_expansions/visitors/symbolic_param_inference.py +197 -0
  74. classiq/model_expansions/visitors/variable_references.py +45 -9
  75. classiq/qmod/builtins/functions/allocation.py +2 -2
  76. classiq/qmod/builtins/functions/arithmetic.py +14 -12
  77. classiq/qmod/builtins/functions/standard_gates.py +23 -23
  78. classiq/qmod/declaration_inferrer.py +19 -7
  79. classiq/qmod/generative.py +9 -1
  80. classiq/qmod/native/expression_to_qmod.py +4 -0
  81. classiq/qmod/native/pretty_printer.py +8 -3
  82. classiq/qmod/pretty_print/pretty_printer.py +1 -1
  83. classiq/qmod/python_classical_type.py +4 -5
  84. classiq/qmod/qmod_constant.py +15 -7
  85. classiq/qmod/qmod_variable.py +30 -2
  86. classiq/qmod/quantum_function.py +19 -6
  87. classiq/qmod/semantics/lambdas.py +6 -2
  88. classiq/qmod/semantics/validation/main_validation.py +17 -4
  89. classiq/qmod/symbolic.py +8 -19
  90. classiq/qmod/symbolic_expr.py +34 -2
  91. classiq/qmod/write_qmod.py +5 -1
  92. classiq/synthesis.py +17 -31
  93. classiq/visualization.py +35 -0
  94. {classiq-0.74.0.dist-info → classiq-0.76.0.dist-info}/METADATA +1 -1
  95. {classiq-0.74.0.dist-info → classiq-0.76.0.dist-info}/RECORD +96 -91
  96. {classiq-0.74.0.dist-info → classiq-0.76.0.dist-info}/WHEEL +1 -1
@@ -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,24 +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)
74
+ if len(self._get_classical_vars_in_expression(expr)):
75
+ return None
39
76
  symbols = self._get_symbols_in_expression(expr)
40
77
  expr_str = rename_variables(
41
78
  expr.expr,
@@ -46,16 +83,180 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
46
83
  expr_str = expr_str.replace(
47
84
  symbol.handle.qmod_expr, symbol.handle.identifier
48
85
  )
49
- result_type = compute_arithmetic_result_type(
86
+ return compute_arithmetic_result_type(
50
87
  expr_str,
51
88
  {symbol.handle.identifier: symbol.quantum_type for symbol in symbols},
52
89
  self._machine_precision,
53
90
  )
54
- result_symbol = self._interpreter.evaluate(op.result_var).as_type(QuantumSymbol)
55
91
 
56
- validate_assignment_bool_expression(
57
- result_symbol, op.expression.expr, op.operation_kind
58
- ) # must be here, otherwise copy_type_information will throw a non-indicative error
59
- copy_type_information(
60
- 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
+ )
61
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,
@@ -24,7 +27,6 @@ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_insta
24
27
  from classiq.interface.generator.functions.port_declaration import (
25
28
  PortDeclarationDirection,
26
29
  )
27
- from classiq.interface.generator.generated_circuit_data import OperationLevel
28
30
  from classiq.interface.model.classical_parameter_declaration import (
29
31
  ClassicalParameterDeclaration,
30
32
  )
@@ -63,7 +65,6 @@ from classiq.model_expansions.quantum_operations.emitter import (
63
65
  from classiq.model_expansions.scope import Evaluated, QuantumSymbol, Scope
64
66
  from classiq.model_expansions.transformers.var_splitter import VarSplitter
65
67
  from classiq.model_expansions.utils.text_utils import are, readable_list, s
66
- from classiq.qmod.builtins.functions import free
67
68
  from classiq.qmod.semantics.validation.signature_validation import (
68
69
  validate_function_signature,
69
70
  )
@@ -92,13 +93,15 @@ def _validate_cloning(evaluated_args: list[Evaluated]) -> None:
92
93
 
93
94
 
94
95
  def _is_symbolic(arg: Any) -> bool:
96
+ if isinstance(arg, (ClassicalProxy, AnyClassicalValue)):
97
+ return True
95
98
  if isinstance(arg, list):
96
99
  return any(_is_symbolic(item) for item in arg)
97
100
  if isinstance(arg, QmodStructInstance):
98
101
  return any(_is_symbolic(item) for item in arg.fields.values())
99
102
  if isinstance(arg, sympy.Basic):
100
103
  return len(arg.free_symbols) > 0
101
- return isinstance(arg, (ClassicalProxy, AnyClassicalValue))
104
+ return False
102
105
 
103
106
 
104
107
  class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSplitter):
@@ -188,17 +191,16 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
188
191
  positional_args=new_positional_args,
189
192
  back_ref=self._get_back_ref(propagated_debug_info),
190
193
  )
191
- is_allocate_or_free = new_call.func_name == free.func_decl.name
192
194
 
193
- port_to_passed_variable_map = {
194
- arg_decl.name: str(evaluated_arg.value.handle)
195
- for arg_decl, evaluated_arg in zip(new_positional_arg_decls, evaluated_args)
196
- if isinstance(arg_decl, PortDeclaration)
197
- }
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
+ )
198
202
  self._debug_info[new_call.uuid] = FunctionDebugInfo(
199
203
  name=new_call.func_name,
200
- level=OperationLevel.QMOD_FUNCTION_CALL,
201
- is_allocate_or_free=is_allocate_or_free,
202
204
  port_to_passed_variable_map=port_to_passed_variable_map,
203
205
  node=new_call._as_back_ref(),
204
206
  )
@@ -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
  )
@@ -13,9 +13,13 @@ import sympy
13
13
 
14
14
  from classiq.interface.debug_info.debug_info import (
15
15
  DebugInfoCollection,
16
- new_function_debug_info_by_node,
17
16
  )
18
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
+ )
19
23
  from classiq.interface.generator.expressions.evaluated_expression import (
20
24
  EvaluatedExpression,
21
25
  )
@@ -31,7 +35,11 @@ from classiq.interface.generator.functions.port_declaration import (
31
35
  PortDeclarationDirection,
32
36
  )
33
37
  from classiq.interface.helpers.pydantic_model_helpers import nameables_to_dict
34
- from classiq.interface.model.handle_binding import HandleBinding, NestedHandleBinding
38
+ from classiq.interface.model.handle_binding import (
39
+ FieldHandleBinding,
40
+ HandleBinding,
41
+ NestedHandleBinding,
42
+ )
35
43
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
36
44
  from classiq.interface.model.quantum_function_declaration import (
37
45
  NamedParamsQuantumFunctionDeclaration,
@@ -161,10 +169,7 @@ class Emitter(Generic[QuantumStatementT], ABC):
161
169
  self._update_captured_classical_vars(statement)
162
170
  if isinstance(statement, QuantumOperation):
163
171
  self._update_captured_vars(statement)
164
- if statement.uuid not in self._interpreter._model.debug_info:
165
- self._interpreter._model.debug_info[statement.uuid] = (
166
- new_function_debug_info_by_node(statement) # type:ignore[arg-type]
167
- )
172
+ self._interpreter.add_to_debug_info(statement)
168
173
  self._builder.emit_statement(statement)
169
174
 
170
175
  def _update_captured_classical_vars(self, stmt: QuantumStatement) -> None:
@@ -224,9 +229,17 @@ class Emitter(Generic[QuantumStatementT], ABC):
224
229
  handles = dict.fromkeys(
225
230
  handle
226
231
  for handle in vrc.var_handles
227
- 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)
228
236
  )
229
- 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
+ ]
230
243
 
231
244
  def _get_classical_vars_in_expression(
232
245
  self, expr: Expression
@@ -33,5 +33,6 @@ class ExpressionEvaluator(Emitter[QuantumOperation]):
33
33
  op = op.model_copy(
34
34
  update={self._expression_name: evaluated_expression, "back_ref": op.uuid}
35
35
  )
36
+ self._interpreter.add_to_debug_info(op)
36
37
  self._interpreter.emit(op)
37
38
  return True
@@ -24,5 +24,6 @@ class HandleEvaluator(Emitter[QuantumOperation]):
24
24
  op = op.model_copy(
25
25
  update={self._handle_name: evaluated_handle, "back_ref": op.uuid}
26
26
  )
27
+ self._interpreter.add_to_debug_info(op)
27
28
  self._interpreter.emit(op)
28
29
  return True
@@ -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(