classiq 0.54.0__py3-none-any.whl → 0.56.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 (56) hide show
  1. classiq/interface/_version.py +1 -1
  2. classiq/interface/debug_info/debug_info.py +11 -0
  3. classiq/interface/executor/result.py +0 -3
  4. classiq/interface/generator/functions/builtins/internal_operators.py +9 -1
  5. classiq/interface/generator/generated_circuit_data.py +0 -1
  6. classiq/interface/generator/model/preferences/preferences.py +4 -0
  7. classiq/interface/generator/types/compilation_metadata.py +5 -0
  8. classiq/interface/generator/visitor.py +13 -1
  9. classiq/interface/ide/visual_model.py +5 -1
  10. classiq/interface/interface_version.py +1 -1
  11. classiq/interface/model/control.py +22 -1
  12. classiq/interface/model/handle_binding.py +28 -0
  13. classiq/interface/model/model.py +4 -0
  14. classiq/interface/model/native_function_definition.py +1 -1
  15. classiq/interface/model/quantum_expressions/arithmetic_operation.py +2 -26
  16. classiq/interface/model/quantum_statement.py +6 -0
  17. classiq/model_expansions/capturing/mangling_utils.py +22 -0
  18. classiq/model_expansions/capturing/propagated_var_stack.py +36 -25
  19. classiq/model_expansions/closure.py +77 -12
  20. classiq/model_expansions/function_builder.py +9 -10
  21. classiq/model_expansions/generative_functions.py +2 -2
  22. classiq/model_expansions/interpreter.py +29 -26
  23. classiq/model_expansions/quantum_operations/control.py +114 -29
  24. classiq/model_expansions/quantum_operations/emitter.py +37 -11
  25. classiq/model_expansions/quantum_operations/expression_operation.py +80 -18
  26. classiq/model_expansions/quantum_operations/power.py +5 -0
  27. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +7 -37
  28. classiq/model_expansions/quantum_operations/quantum_function_call.py +2 -32
  29. classiq/model_expansions/quantum_operations/repeat.py +5 -0
  30. classiq/model_expansions/quantum_operations/within_apply.py +0 -16
  31. classiq/model_expansions/scope_initialization.py +2 -3
  32. classiq/qmod/builtins/functions/arithmetic.py +0 -2
  33. classiq/qmod/builtins/functions/discrete_sine_cosine_transform.py +0 -12
  34. classiq/qmod/builtins/functions/exponentiation.py +0 -6
  35. classiq/qmod/builtins/functions/grover.py +0 -17
  36. classiq/qmod/builtins/functions/linear_pauli_rotation.py +0 -5
  37. classiq/qmod/builtins/functions/modular_exponentiation.py +0 -3
  38. classiq/qmod/builtins/functions/qaoa_penalty.py +0 -8
  39. classiq/qmod/builtins/functions/qft_functions.py +0 -3
  40. classiq/qmod/builtins/functions/qpe.py +0 -6
  41. classiq/qmod/builtins/functions/qsvt.py +0 -12
  42. classiq/qmod/builtins/functions/standard_gates.py +0 -88
  43. classiq/qmod/builtins/functions/state_preparation.py +7 -15
  44. classiq/qmod/builtins/functions/swap_test.py +0 -3
  45. classiq/qmod/builtins/operations.py +152 -17
  46. classiq/qmod/create_model_function.py +10 -12
  47. classiq/qmod/model_state_container.py +5 -1
  48. classiq/qmod/native/pretty_printer.py +6 -1
  49. classiq/qmod/pretty_print/pretty_printer.py +25 -11
  50. classiq/qmod/qmod_constant.py +31 -3
  51. classiq/qmod/quantum_function.py +25 -19
  52. classiq/qmod/synthesize_separately.py +1 -2
  53. {classiq-0.54.0.dist-info → classiq-0.56.0.dist-info}/METADATA +2 -3
  54. {classiq-0.54.0.dist-info → classiq-0.56.0.dist-info}/RECORD +55 -55
  55. classiq/model_expansions/call_to_model_converter.py +0 -190
  56. {classiq-0.54.0.dist-info → classiq-0.56.0.dist-info}/WHEEL +0 -0
@@ -19,7 +19,6 @@ from classiq.interface.generator.functions.port_declaration import (
19
19
  )
20
20
  from classiq.interface.model.model import MAIN_FUNCTION_NAME
21
21
  from classiq.interface.model.native_function_definition import (
22
- FunctionSynthesisData,
23
22
  NativeFunctionDefinition,
24
23
  )
25
24
  from classiq.interface.model.port_declaration import PortDeclaration
@@ -31,6 +30,7 @@ from classiq.interface.model.quantum_statement import QuantumStatement
31
30
  from classiq.model_expansions.capturing.captured_var_manager import update_captured_vars
32
31
  from classiq.model_expansions.capturing.mangling_utils import demangle_name
33
32
  from classiq.model_expansions.closure import Closure, FunctionClosure
33
+ from classiq.model_expansions.scope import Scope
34
34
 
35
35
  ClosureType = TypeVar("ClosureType", bound=Closure)
36
36
 
@@ -76,16 +76,12 @@ class FunctionContext(OperationContext[FunctionClosure]):
76
76
  def is_lambda(self) -> bool:
77
77
  return self.closure.is_lambda
78
78
 
79
- @property
80
- def synthesis_data(self) -> FunctionSynthesisData:
81
- return self.closure.synthesis_data
82
-
83
79
 
84
80
  class OperationBuilder:
85
- def __init__(self) -> None:
81
+ def __init__(self, functions_scope: Scope) -> None:
86
82
  self._operations: list[OperationContext] = []
87
83
  self._blocks: list[str] = []
88
- self._counter = 0
84
+ self._functions_scope = functions_scope
89
85
 
90
86
  @property
91
87
  def current_operation(self) -> Closure:
@@ -163,8 +159,12 @@ class OperationBuilder:
163
159
  ) -> NativeFunctionDefinition:
164
160
  name = function_context.name
165
161
  if name != MAIN_FUNCTION_NAME:
166
- name = f"{name}_{LAMBDA_KEYWORD + '_0_0_' if function_context.is_lambda else ''}{EXPANDED_KEYWORD}_{self._counter}"
167
- self._counter += 1
162
+ idx = 0
163
+ new_name = name
164
+ while idx == 0 or new_name in self._functions_scope:
165
+ new_name = f"{name}_{LAMBDA_KEYWORD + '_0_0_' if function_context.is_lambda else ''}{EXPANDED_KEYWORD}_{idx}"
166
+ idx += 1
167
+ name = new_name
168
168
 
169
169
  new_parameters: list[PortDeclaration] = [
170
170
  param
@@ -176,7 +176,6 @@ class OperationBuilder:
176
176
  name=name,
177
177
  body=function_context.body,
178
178
  positional_arg_declarations=new_parameters,
179
- synthesis_data=function_context.synthesis_data,
180
179
  )
181
180
 
182
181
 
@@ -186,7 +186,7 @@ def _expand_operand_as_declarative(
186
186
 
187
187
 
188
188
  def _register_declarative_function(interpreter: "Interpreter", func_name: str) -> None:
189
- if func_name in nameables_to_dict(interpreter._expanded_functions):
189
+ if func_name in nameables_to_dict(list(interpreter._expanded_functions.values())):
190
190
  return
191
191
 
192
192
  for user_gen_func in interpreter._generative_functions:
@@ -199,7 +199,7 @@ def _register_declarative_function(interpreter: "Interpreter", func_name: str) -
199
199
  dec_func = QFunc(user_gen_func._py_callable)
200
200
  dec_func.expand()
201
201
  dec_func_def = QMODULE.native_defs[func_name]
202
- interpreter._expanded_functions.append(dec_func_def)
202
+ interpreter._expanded_functions[func_name] = dec_func_def
203
203
  _DecFuncVisitor(interpreter).visit(dec_func_def)
204
204
 
205
205
 
@@ -11,7 +11,9 @@ from classiq.interface.exceptions import (
11
11
  ClassiqExpansionError,
12
12
  ClassiqInternalExpansionError,
13
13
  )
14
+ from classiq.interface.generator.constant import Constant
14
15
  from classiq.interface.generator.expressions.expression import Expression
16
+ from classiq.interface.generator.types.compilation_metadata import CompilationMetadata
15
17
  from classiq.interface.model.bind_operation import BindOperation
16
18
  from classiq.interface.model.classical_if import ClassicalIf
17
19
  from classiq.interface.model.control import Control
@@ -46,7 +48,6 @@ from classiq.interface.model.variable_declaration_statement import (
46
48
  )
47
49
  from classiq.interface.model.within_apply_operation import WithinApply
48
50
 
49
- from classiq.model_expansions.call_to_model_converter import BlockFunctionInfo
50
51
  from classiq.model_expansions.capturing.propagated_var_stack import PropagatedVarStack
51
52
  from classiq.model_expansions.closure import (
52
53
  Closure,
@@ -81,6 +82,7 @@ from classiq.model_expansions.quantum_operations import (
81
82
  from classiq.model_expansions.quantum_operations.phase import PhaseEmitter
82
83
  from classiq.model_expansions.scope import Evaluated, QuantumSymbol, Scope
83
84
  from classiq.model_expansions.scope_initialization import (
85
+ add_constants_to_scope,
84
86
  add_entry_point_params_to_scope,
85
87
  get_main_renamer,
86
88
  init_top_level_scope,
@@ -107,8 +109,9 @@ class Interpreter:
107
109
  self._is_frontend = is_frontend
108
110
  self._model = model
109
111
  self._current_scope = Scope()
110
- self._builder = OperationBuilder()
111
- self._expanded_functions: list[NativeFunctionDefinition] = []
112
+ self._top_level_scope = self._current_scope
113
+ self._builder = OperationBuilder(self._top_level_scope)
114
+ self._expanded_functions: dict[str, NativeFunctionDefinition] = {}
112
115
  self._propagated_var_stack = PropagatedVarStack(
113
116
  self._current_scope, self._builder
114
117
  )
@@ -119,10 +122,11 @@ class Interpreter:
119
122
  generative_functions = []
120
123
  self._generative_functions = generative_functions
121
124
  init_top_level_scope(model, generative_functions, self._current_scope)
122
-
125
+ self._expanded_functions_compilation_metadata: dict[
126
+ str, CompilationMetadata
127
+ ] = dict()
123
128
  self._counted_name_allocator = CountedNameAllocator()
124
129
  self._error_manager: ErrorManager = ErrorManager()
125
- self._synthesized_separately_blocks: dict[str, BlockFunctionInfo] = {}
126
130
 
127
131
  @contextmanager
128
132
  def _scope_guard(self, scope: Scope) -> Iterator[None]:
@@ -153,14 +157,13 @@ class Interpreter:
153
157
  main_closure.positional_arg_declarations, main_closure
154
158
  )
155
159
  context = self._expand_operation(main_closure)
156
- self._expanded_functions.append(
160
+ self._expanded_functions[main_closure.closure_id] = (
157
161
  self._builder.create_definition(cast(FunctionContext, context))
158
162
  )
159
163
 
160
- def expand(self) -> tuple[Model, dict[str, BlockFunctionInfo]]:
164
+ def expand(self) -> Model:
161
165
  try:
162
166
  with self._error_manager.call("main"):
163
- self._synthesized_separately_blocks = {}
164
167
  self._expand_main_func()
165
168
  except Exception as e:
166
169
  if isinstance(e, ClassiqInternalExpansionError) or debug_mode.get():
@@ -172,20 +175,18 @@ class Interpreter:
172
175
  finally:
173
176
  self._error_manager.report_errors(ClassiqExpansionError)
174
177
 
175
- return (
176
- Model(
177
- constraints=self._model.constraints,
178
- preferences=self._model.preferences,
179
- classical_execution_code=self._model.classical_execution_code,
180
- execution_preferences=self._model.execution_preferences,
181
- functions=self._expanded_functions,
182
- constants=self._model.constants,
183
- enums=self._model.enums,
184
- types=self._model.types,
185
- qstructs=self._model.qstructs,
186
- debug_info=self._model.debug_info,
187
- ),
188
- self._synthesized_separately_blocks,
178
+ return Model(
179
+ constraints=self._model.constraints,
180
+ preferences=self._model.preferences,
181
+ classical_execution_code=self._model.classical_execution_code,
182
+ execution_preferences=self._model.execution_preferences,
183
+ functions=list(self._expanded_functions.values()),
184
+ constants=self._model.constants,
185
+ enums=self._model.enums,
186
+ types=self._model.types,
187
+ qstructs=self._model.qstructs,
188
+ debug_info=self._model.debug_info,
189
+ functions_compilation_metadata=self._expanded_functions_compilation_metadata,
189
190
  )
190
191
 
191
192
  @singledispatchmethod
@@ -346,9 +347,8 @@ class Interpreter:
346
347
 
347
348
  def _expand_operation(self, operation: Closure) -> OperationContext:
348
349
  with self._builder.operation_context(operation) as context:
349
- if (
350
- isinstance(operation, FunctionClosure)
351
- and operation.synthesis_data.should_synthesize_separately
350
+ if isinstance(operation, FunctionClosure) and (
351
+ self._expanded_functions.get(operation.closure_id) is not None
352
352
  ):
353
353
  pass
354
354
  elif isinstance(operation, FunctionClosure) and operation.name == "permute":
@@ -385,5 +385,8 @@ class Interpreter:
385
385
  return (
386
386
  self._model.functions
387
387
  + [gen_func.func_decl for gen_func in self._generative_functions]
388
- + self._expanded_functions # type:ignore[operator]
388
+ + list(self._expanded_functions.values())
389
389
  )
390
+
391
+ def add_constant(self, constant: Constant) -> None:
392
+ add_constants_to_scope([constant], self._top_level_scope)
@@ -9,6 +9,7 @@ from classiq.interface.exceptions import (
9
9
  from classiq.interface.generator.compiler_keywords import INPLACE_ARITH_AUX_VAR_PREFIX
10
10
  from classiq.interface.generator.expressions.expression import Expression
11
11
  from classiq.interface.generator.expressions.expression_types import ExpressionValue
12
+ from classiq.interface.generator.expressions.qmod_qarray_proxy import QmodQArrayProxy
12
13
  from classiq.interface.generator.expressions.qmod_qscalar_proxy import QmodQNumProxy
13
14
  from classiq.interface.generator.expressions.qmod_sized_proxy import QmodSizedProxy
14
15
  from classiq.interface.generator.functions.builtins.internal_operators import (
@@ -16,18 +17,24 @@ from classiq.interface.generator.functions.builtins.internal_operators import (
16
17
  )
17
18
  from classiq.interface.model.bind_operation import BindOperation
18
19
  from classiq.interface.model.control import Control
19
- from classiq.interface.model.handle_binding import HANDLE_ID_SEPARATOR, HandleBinding
20
+ from classiq.interface.model.handle_binding import HandleBinding
20
21
  from classiq.interface.model.quantum_expressions.arithmetic_operation import (
21
22
  ArithmeticOperation,
22
23
  ArithmeticOperationKind,
23
24
  )
24
- from classiq.interface.model.quantum_type import QuantumBit, QuantumBitvector
25
+ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
26
+ from classiq.interface.model.quantum_type import (
27
+ QuantumBit,
28
+ QuantumBitvector,
29
+ QuantumType,
30
+ )
25
31
  from classiq.interface.model.statement_block import ConcreteQuantumStatement
26
32
  from classiq.interface.model.variable_declaration_statement import (
27
33
  VariableDeclarationStatement,
28
34
  )
29
35
  from classiq.interface.model.within_apply_operation import WithinApply
30
36
 
37
+ from classiq.model_expansions.capturing.mangling_utils import ARRAY_CAST_SUFFIX
31
38
  from classiq.model_expansions.capturing.propagated_var_stack import (
32
39
  validate_args_are_not_propagated,
33
40
  )
@@ -40,39 +47,71 @@ from classiq.model_expansions.quantum_operations.expression_operation import (
40
47
  ExpressionOperationEmitter,
41
48
  )
42
49
  from classiq.model_expansions.scope import Scope
43
-
44
- ARRAY_CAST_SUFFIX = HANDLE_ID_SEPARATOR + "array_cast"
50
+ from classiq.qmod.builtins.functions.standard_gates import X
45
51
 
46
52
 
47
53
  class ControlEmitter(ExpressionOperationEmitter[Control]):
48
54
  def emit(self, control: Control, /) -> None:
49
55
  condition = self._evaluate_op_expression(control)
50
- control = control.model_copy(update=dict(expression=condition))
51
56
 
52
57
  arrays_with_subscript = self._get_symbols_to_split(condition)
53
58
  if len(arrays_with_subscript) > 0:
59
+ if control.is_generative():
60
+ with self._propagated_var_stack.capture_variables(control):
61
+ control = self._expand_generative_control(control)
54
62
  self._emit_with_split(control, condition, arrays_with_subscript)
55
63
  return
56
64
 
65
+ control = self._evaluate_types_in_expression(control, condition)
57
66
  condition_val = condition.value.value
58
67
  if isinstance(condition_val, QmodSizedProxy):
59
- self._validate_canonical_condition(condition_val)
60
- self._emit_canonical_control(control)
61
- return
68
+ if control.else_block is None:
69
+ self._validate_canonical_condition(condition_val)
70
+ self._emit_canonical_control(control)
71
+ return
72
+ else:
73
+ self._interpreter.emit_statement(
74
+ control.model_copy(
75
+ update=dict(
76
+ expression=self._uncanonize_condition(condition_val)
77
+ )
78
+ )
79
+ )
80
+ return
62
81
 
63
- self._validate_condition(condition_val)
82
+ self._validate_condition(control)
64
83
  self._emit_with_boolean(control)
65
84
 
85
+ def _uncanonize_condition(self, condition_val: QmodSizedProxy) -> Expression:
86
+ lhs = (
87
+ " & ".join(
88
+ f"{condition_val.handle}[{idx}]" for idx in range(condition_val.size)
89
+ )
90
+ if isinstance(condition_val, QmodQArrayProxy)
91
+ else condition_val.handle
92
+ )
93
+ return Expression(expr=f"{lhs} == 1")
94
+
95
+ def _expand_generative_control(self, control: Control) -> Control:
96
+ block_names = ["body"]
97
+ if control.has_generative_block("else_block"):
98
+ block_names += ["else_block"]
99
+ context = self._register_generative_context(
100
+ control, CONTROL_OPERATOR_NAME, block_names
101
+ )
102
+ new_blocks = {"body": context.statements("body")}
103
+ if "else_block" in block_names:
104
+ new_blocks["else_block"] = context.statements("else_block")
105
+ return control.model_copy(update=new_blocks)
106
+
66
107
  def _emit_canonical_control(self, control: Control) -> None:
67
108
  # canonical means control(q, body) where q is a single quantum variable
68
- control = self._evaluate_types_in_expression(control, control.expression)
69
109
  with self._propagated_var_stack.capture_variables(control):
70
110
  self._emit_propagated(control)
71
111
 
72
112
  def _emit_propagated(self, control: Control) -> None:
73
113
  if control.is_generative():
74
- context = self._register_generative_context(control, CONTROL_OPERATOR_NAME)
75
- control = control.model_copy(update={"body": context.statements("body")})
114
+ control = self._expand_generative_control(control)
76
115
 
77
116
  if self._should_wrap_control(control):
78
117
  self._emit_wrapped(control)
@@ -98,7 +137,7 @@ class ControlEmitter(ExpressionOperationEmitter[Control]):
98
137
  context = self._expand_operation(control_operation)
99
138
  validate_args_are_not_propagated(
100
139
  control.var_handles,
101
- self._propagated_var_stack.get_propagated_variables(),
140
+ self._propagated_var_stack.get_propagated_variables(flatten=False),
102
141
  )
103
142
  self._update_control_state(control)
104
143
  self._builder.emit_statement(
@@ -111,7 +150,7 @@ class ControlEmitter(ExpressionOperationEmitter[Control]):
111
150
  )
112
151
  validate_args_are_not_propagated(
113
152
  control.var_handles,
114
- self._propagated_var_stack.get_propagated_variables(),
153
+ self._propagated_var_stack.get_propagated_variables(flatten=False),
115
154
  )
116
155
  self._update_control_state(control)
117
156
  self._builder.emit_statement(
@@ -129,9 +168,10 @@ class ControlEmitter(ExpressionOperationEmitter[Control]):
129
168
  condition_val = control.expression.value.value
130
169
  if self._is_simple_equality(condition_val):
131
170
  ctrl, ctrl_state = resolve_num_condition(condition_val)
132
- self._emit_with_x_gates(control, ctrl, ctrl_state)
133
- else:
134
- self._emit_with_arithmetic(control)
171
+ if control.else_block is None or ctrl.size == 1:
172
+ self._emit_with_x_gates(control, ctrl, ctrl_state)
173
+ return
174
+ self._emit_with_arithmetic(control)
135
175
 
136
176
  @staticmethod
137
177
  def _is_simple_equality(condition_val: ExpressionValue) -> TypeGuard[Equality]:
@@ -151,13 +191,38 @@ class ControlEmitter(ExpressionOperationEmitter[Control]):
151
191
 
152
192
  def _create_canonical_control_op(
153
193
  self, control: Control, handle_name: str
154
- ) -> Control:
194
+ ) -> list[ConcreteQuantumStatement]:
155
195
  handle_expr = self._interpreter.evaluate(Expression(expr=handle_name)).emit()
156
- return control.model_copy(update=dict(expression=handle_expr))
196
+ control_then = control.model_copy(
197
+ update=dict(expression=handle_expr, else_block=None)
198
+ )
199
+ if control.else_block is None:
200
+ return [control_then]
201
+ else_compute_call = QuantumFunctionCall(
202
+ function="X", positional_args=[HandleBinding(name=handle_name)]
203
+ )
204
+ else_compute_call.set_func_decl(X.func_decl)
205
+ control_else_inner = control.model_copy(
206
+ update=dict(
207
+ expression=handle_expr, body=control.else_block, else_block=None
208
+ ),
209
+ deep=True,
210
+ )
211
+ control_else = WithinApply(
212
+ compute=[else_compute_call],
213
+ action=[control_else_inner],
214
+ )
215
+ if control_else_inner.is_generative():
216
+ control_then.remove_generative_block("else_block")
217
+ control_else_inner.set_generative_block(
218
+ "body", control_else_inner.get_generative_block("else_block")
219
+ )
220
+ control_else_inner.remove_generative_block("else_block")
221
+ return [control_then, control_else]
157
222
 
158
- def _emit_with_x_gates(
223
+ def _control_with_x_gates(
159
224
  self, control: Control, ctrl: QmodSizedProxy, ctrl_state: str
160
- ) -> None:
225
+ ) -> ConcreteQuantumStatement:
161
226
  compute_op: list[ConcreteQuantumStatement] = []
162
227
 
163
228
  x_gate_value = self._get_x_gate_value(ctrl_state)
@@ -175,12 +240,19 @@ class ControlEmitter(ExpressionOperationEmitter[Control]):
175
240
  cast_decl, bind_op = self._get_array_cast_ops(ctrl)
176
241
  self._interpreter.emit_statement(cast_decl)
177
242
  compute_op.append(bind_op)
178
- control_op = self._create_canonical_control_op(control, str(cast_decl.name))
243
+ control_ops = self._create_canonical_control_op(
244
+ control, str(cast_decl.name)
245
+ )
179
246
  else:
180
- control_op = self._create_canonical_control_op(control, str(ctrl.handle))
247
+ control_ops = self._create_canonical_control_op(control, str(ctrl.handle))
248
+
249
+ return WithinApply(compute=compute_op, action=control_ops)
181
250
 
251
+ def _emit_with_x_gates(
252
+ self, control: Control, ctrl: QmodSizedProxy, ctrl_state: str
253
+ ) -> None:
182
254
  self._interpreter.emit_statement(
183
- WithinApply(compute=compute_op, action=[control_op])
255
+ self._control_with_x_gates(control, ctrl, ctrl_state)
184
256
  )
185
257
 
186
258
  @staticmethod
@@ -217,20 +289,33 @@ class ControlEmitter(ExpressionOperationEmitter[Control]):
217
289
  self._interpreter.emit_statement(
218
290
  WithinApply(
219
291
  compute=[arith_expression],
220
- action=[self._create_canonical_control_op(control, aux_var)],
292
+ action=self._create_canonical_control_op(control, aux_var),
221
293
  )
222
294
  )
223
295
 
224
- @staticmethod
225
- def _validate_condition(condition_val: ExpressionValue) -> None:
226
- if not isinstance(condition_val, Boolean):
227
- raise ClassiqExpansionError(_condition_err_msg(condition_val))
296
+ def _validate_condition(self, control: Control) -> None:
297
+ condition_value = control.expression.value.value
298
+ if not (
299
+ isinstance(condition_value, Boolean)
300
+ or self._all_vars_boolean(control)
301
+ and self._is_res_boolean(control)
302
+ ):
303
+ raise ClassiqExpansionError(_condition_err_msg(condition_value))
228
304
 
229
305
  @staticmethod
230
306
  def _validate_canonical_condition(condition_val: ExpressionValue) -> None:
231
307
  if isinstance(condition_val, QmodQNumProxy):
232
308
  raise ClassiqExpansionError(_condition_err_msg(condition_val))
233
309
 
310
+ def _get_updated_op_split_symbols(
311
+ self, op: Control, symbol_mapping: dict[HandleBinding, tuple[str, QuantumType]]
312
+ ) -> Control:
313
+ new_body = self._rewrite(op.body, symbol_mapping)
314
+ new_else = None
315
+ if op.else_block is not None:
316
+ new_else = self._rewrite(op.else_block, symbol_mapping)
317
+ return op.model_copy(update=dict(body=new_body, else_block=new_else))
318
+
234
319
 
235
320
  def _condition_err_msg(condition_val: ExpressionValue) -> str:
236
321
  return (
@@ -70,7 +70,12 @@ class Emitter(Generic[QuantumStatementT]):
70
70
 
71
71
  self._scope_guard = self._interpreter._scope_guard
72
72
  self._machine_precision = self._interpreter._model.preferences.machine_precision
73
-
73
+ self._expanded_functions_compilation_metadata = (
74
+ self._interpreter._expanded_functions_compilation_metadata
75
+ )
76
+ self._functions_compilation_metadata = (
77
+ self._interpreter._model.functions_compilation_metadata
78
+ )
74
79
  self._generative_contexts: dict[str, OperationContext] = {}
75
80
 
76
81
  @abstractmethod
@@ -100,7 +105,11 @@ class Emitter(Generic[QuantumStatementT]):
100
105
  return self._interpreter._current_scope
101
106
 
102
107
  @property
103
- def _expanded_functions(self) -> list[NativeFunctionDefinition]:
108
+ def _top_level_scope(self) -> Scope:
109
+ return self._interpreter._top_level_scope
110
+
111
+ @property
112
+ def _expanded_functions(self) -> dict[str, NativeFunctionDefinition]:
104
113
  return self._interpreter._expanded_functions
105
114
 
106
115
  @property
@@ -137,23 +146,38 @@ class Emitter(Generic[QuantumStatementT]):
137
146
  )
138
147
  new_positional_arg_decls = new_declaration.positional_arg_declarations
139
148
  is_atomic = function.is_atomic
140
- if not is_atomic: # perform monomorphization
149
+ new_function_name = function.name
150
+ if not is_atomic: # perform monomorphization per interpreted parameters set
141
151
  self._add_params_to_scope(
142
152
  new_positional_arg_decls, evaluated_args, function
143
153
  )
144
154
  context = self._expand_operation(
145
155
  function.with_new_declaration(new_declaration)
146
156
  )
147
- self._expanded_functions.append(
148
- self._builder.create_definition(cast(FunctionContext, context))
157
+ function_context = cast(FunctionContext, context)
158
+ closure_id = function_context.closure.closure_id
159
+ function_def = self._expanded_functions.get(closure_id)
160
+ if function_def is None:
161
+ function_def = self._builder.create_definition(function_context)
162
+ self._expanded_functions[closure_id] = function_def
163
+ self._top_level_scope[function_def.name] = Evaluated(
164
+ value=function_context.closure.with_new_declaration(function_def)
165
+ )
166
+ new_declaration = function_def
167
+ new_function_name = function_def.name
168
+ compilation_metadata = self._functions_compilation_metadata.get(
169
+ function.name
149
170
  )
171
+ if compilation_metadata is not None:
172
+ self._expanded_functions_compilation_metadata[new_function_name] = (
173
+ compilation_metadata
174
+ )
175
+
150
176
  new_positional_args = self._get_new_positional_args(
151
177
  evaluated_args, is_atomic, new_positional_arg_decls
152
178
  )
153
179
  new_call = QuantumFunctionCall(
154
- function=(
155
- new_declaration.name if is_atomic else self._expanded_functions[-1].name
156
- ),
180
+ function=new_function_name,
157
181
  positional_args=new_positional_args,
158
182
  )
159
183
  is_allocate_or_free = (
@@ -178,8 +202,7 @@ class Emitter(Generic[QuantumStatementT]):
178
202
  is_allocate_or_free=is_allocate_or_free,
179
203
  port_to_passed_variable_map=port_to_passed_variable_map,
180
204
  )
181
- if is_atomic:
182
- new_call.set_func_decl(new_declaration)
205
+ new_call.set_func_decl(new_declaration)
183
206
  return new_call
184
207
 
185
208
  @staticmethod
@@ -220,7 +243,9 @@ class Emitter(Generic[QuantumStatementT]):
220
243
  arg.emit() for arg in evaluated_args if isinstance(arg.value, QuantumSymbol)
221
244
  ]
222
245
 
223
- propagated_variables = self._propagated_var_stack.get_propagated_variables()
246
+ propagated_variables = self._propagated_var_stack.get_propagated_variables(
247
+ flatten=True
248
+ )
224
249
  validate_args_are_not_propagated(positional_args, propagated_variables)
225
250
  positional_args.extend(propagated_variables)
226
251
 
@@ -279,6 +304,7 @@ class Emitter(Generic[QuantumStatementT]):
279
304
  )
280
305
  context = self._interpreter._expand_operation(gen_closure)
281
306
  self._generative_contexts[context_name] = context
307
+ op.clear_generative_blocks()
282
308
  return context
283
309
 
284
310
  def _evaluate_expression(self, expression: Expression) -> Expression: