classiq 0.66.0__py3-none-any.whl → 0.67.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 (51) hide show
  1. classiq/applications/finance/finance_model_constructor.py +9 -0
  2. classiq/applications/grover/grover_model_constructor.py +10 -0
  3. classiq/applications/qnn/qlayer.py +8 -2
  4. classiq/applications/qsvm/qsvm_model_constructor.py +9 -0
  5. classiq/interface/_version.py +1 -1
  6. classiq/interface/debug_info/debug_info.py +12 -0
  7. classiq/interface/exceptions.py +2 -5
  8. classiq/interface/generator/arith/argument_utils.py +1 -1
  9. classiq/interface/generator/arith/arithmetic.py +3 -1
  10. classiq/interface/generator/arith/binary_ops.py +3 -0
  11. classiq/interface/generator/function_param_list_without_self_reference.py +2 -0
  12. classiq/interface/generator/functions/type_name.py +2 -2
  13. classiq/interface/generator/generated_circuit_data.py +34 -1
  14. classiq/interface/generator/hardware_efficient_ansatz.py +1 -1
  15. classiq/interface/generator/hva.py +1 -1
  16. classiq/interface/generator/model/preferences/preferences.py +8 -1
  17. classiq/interface/generator/reset.py +14 -0
  18. classiq/interface/generator/ucc.py +1 -1
  19. classiq/interface/interface_version.py +1 -1
  20. classiq/interface/model/quantum_statement.py +13 -0
  21. classiq/model_expansions/atomic_expression_functions_defs.py +2 -1
  22. classiq/model_expansions/capturing/captured_vars.py +184 -54
  23. classiq/model_expansions/closure.py +6 -3
  24. classiq/model_expansions/evaluators/control.py +14 -38
  25. classiq/model_expansions/function_builder.py +19 -14
  26. classiq/model_expansions/generative_functions.py +7 -11
  27. classiq/model_expansions/interpreters/base_interpreter.py +14 -5
  28. classiq/model_expansions/interpreters/generative_interpreter.py +9 -8
  29. classiq/model_expansions/quantum_operations/allocate.py +6 -2
  30. classiq/model_expansions/quantum_operations/bind.py +65 -13
  31. classiq/model_expansions/quantum_operations/call_emitter.py +79 -10
  32. classiq/model_expansions/quantum_operations/classicalif.py +5 -2
  33. classiq/model_expansions/quantum_operations/emitter.py +8 -1
  34. classiq/model_expansions/quantum_operations/repeat.py +7 -2
  35. classiq/model_expansions/quantum_operations/shallow_emitter.py +1 -1
  36. classiq/model_expansions/quantum_operations/variable_decleration.py +11 -1
  37. classiq/open_library/functions/__init__.py +8 -0
  38. classiq/open_library/functions/amplitude_amplification.py +92 -0
  39. classiq/open_library/functions/grover.py +5 -5
  40. classiq/qmod/builtins/functions/__init__.py +3 -0
  41. classiq/qmod/builtins/functions/mid_circuit_measurement.py +15 -0
  42. classiq/qmod/quantum_function.py +4 -0
  43. classiq/qmod/semantics/annotation/call_annotation.py +8 -2
  44. classiq/qmod/semantics/annotation/model_annotation.py +9 -0
  45. classiq/qmod/semantics/error_manager.py +0 -6
  46. classiq/qmod/semantics/static_semantics_visitor.py +0 -347
  47. {classiq-0.66.0.dist-info → classiq-0.67.0.dist-info}/METADATA +1 -1
  48. {classiq-0.66.0.dist-info → classiq-0.67.0.dist-info}/RECORD +49 -47
  49. classiq/qmod/semantics/validation/func_call_validation.py +0 -99
  50. classiq/qmod/semantics/validation/handle_validation.py +0 -85
  51. {classiq-0.66.0.dist-info → classiq-0.67.0.dist-info}/WHEEL +0 -0
@@ -1,7 +1,7 @@
1
1
  import dataclasses
2
2
  from collections.abc import Sequence
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Optional
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  from classiq.interface.enum_utils import StrEnum
7
7
  from classiq.interface.exceptions import (
@@ -34,8 +34,8 @@ if TYPE_CHECKING:
34
34
  from classiq.model_expansions.closure import FunctionClosure
35
35
 
36
36
 
37
- ALREADY_ALLOCATED_MESSAGE = "Cannot allocate variable '{}', it is already initialized"
38
- ALREADY_FREED_MESSAGE = "Cannot free variable '{}', it is already uninitialized"
37
+ INITIALIZED_VAR_MESSAGE = "Variable '{}' should be uninitialized here"
38
+ UNINITIALIZED_VAR_MESSAGE = "Variable '{}' should be initialized here"
39
39
 
40
40
 
41
41
  class PortDirection(StrEnum):
@@ -103,6 +103,11 @@ class _CapturedHandle:
103
103
  def change_direction(self, new_direction: PortDirection) -> "_CapturedHandle":
104
104
  return dataclasses.replace(self, direction=new_direction)
105
105
 
106
+ def change_defining_function(
107
+ self, new_defining_function: "FunctionClosure"
108
+ ) -> "_CapturedHandle":
109
+ return dataclasses.replace(self, defining_function=new_defining_function)
110
+
106
111
  def set_propagated(self) -> "_CapturedHandle":
107
112
  return dataclasses.replace(self, is_propagated=True)
108
113
 
@@ -119,9 +124,13 @@ class _CapturedHandle:
119
124
  return dataclasses.replace(self, handle=handle, quantum_type=quantum_type)
120
125
 
121
126
 
127
+ HandleState = tuple[str, "FunctionClosure", bool]
128
+
129
+
122
130
  @dataclass
123
131
  class CapturedVars:
124
132
  _captured_handles: list[_CapturedHandle] = field(default_factory=list)
133
+ _handle_states: list[HandleState] = field(default_factory=list)
125
134
 
126
135
  def capture_handle(
127
136
  self,
@@ -151,9 +160,25 @@ class CapturedVars:
151
160
  else "allocate"
152
161
  )
153
162
  raise ClassiqExpansionError(
154
- f"Cannot partially {verb} variable {captured_handle.handle.name}"
163
+ f"Cannot {verb} partial variable {str(captured_handle.handle)!r}"
155
164
  )
156
165
 
166
+ # A handle should be in either _captured_handles or _handle_states, but never
167
+ # both
168
+
169
+ new_handle_states = []
170
+ for var_name, defining_function, handle_state in self._handle_states:
171
+ if captured_handle.handle.name == var_name and _same_closure(
172
+ captured_handle.defining_function, defining_function
173
+ ):
174
+ # verify variable state
175
+ self._conjugate_direction(
176
+ handle_state, captured_handle.direction, var_name
177
+ )
178
+ else:
179
+ new_handle_states.append((var_name, defining_function, handle_state))
180
+ self._handle_states = new_handle_states
181
+
157
182
  new_captured_handles = []
158
183
  for existing_captured_handle in self._captured_handles:
159
184
  if not existing_captured_handle.is_same_var(captured_handle):
@@ -163,8 +188,12 @@ class CapturedVars:
163
188
  existing_captured_handle
164
189
  )
165
190
  if existing_captured_handle.handle == captured_handle.handle:
166
- captured_handle = self._conjugate_direction(
167
- existing_captured_handle, captured_handle
191
+ captured_handle = captured_handle.change_direction(
192
+ self._conjugate_direction(
193
+ existing_captured_handle.direction,
194
+ captured_handle.direction,
195
+ str(captured_handle.handle),
196
+ )
168
197
  )
169
198
  elif captured_handle.handle.overlaps(existing_captured_handle.handle):
170
199
  captured_handle = self._intersect_handles(
@@ -177,44 +206,43 @@ class CapturedVars:
177
206
 
178
207
  def _conjugate_direction(
179
208
  self,
180
- existing_captured_handle: _CapturedHandle,
181
- captured_handle: _CapturedHandle,
182
- ) -> _CapturedHandle:
183
- if existing_captured_handle.direction == PortDirection.Input:
184
- if captured_handle.direction == PortDirection.Output:
185
- return captured_handle.change_direction(PortDirection.Inout)
186
- if captured_handle.direction == PortDirection.Outin:
187
- return captured_handle.change_direction(PortDirection.Input)
188
- raise ClassiqExpansionError(
189
- ALREADY_FREED_MESSAGE.format(captured_handle.handle)
209
+ source_direction: PortDirection | bool,
210
+ target_direction: PortDirection,
211
+ var_name: str,
212
+ ) -> PortDirection:
213
+ if isinstance(source_direction, bool):
214
+ source_direction = (
215
+ PortDirection.Inout if source_direction else PortDirection.Outin
190
216
  )
191
- if existing_captured_handle.direction == PortDirection.Output:
192
- if captured_handle.direction == PortDirection.Input:
193
- return captured_handle.change_direction(PortDirection.Outin)
194
- if captured_handle.direction in (
195
- PortDirection.Output,
196
- PortDirection.Outin,
197
- ):
198
- raise ClassiqExpansionError(
199
- ALREADY_ALLOCATED_MESSAGE.format(captured_handle.handle)
200
- )
201
- return captured_handle.change_direction(PortDirection.Output)
202
- if existing_captured_handle.direction == PortDirection.Inout:
203
- if captured_handle.direction in (
217
+
218
+ if source_direction == PortDirection.Input:
219
+ if target_direction == PortDirection.Output:
220
+ return PortDirection.Inout
221
+ if target_direction == PortDirection.Outin:
222
+ return PortDirection.Input
223
+ raise ClassiqExpansionError(UNINITIALIZED_VAR_MESSAGE.format(var_name))
224
+
225
+ if source_direction == PortDirection.Output:
226
+ if target_direction == PortDirection.Input:
227
+ return PortDirection.Outin
228
+ if target_direction in (
204
229
  PortDirection.Output,
205
230
  PortDirection.Outin,
206
231
  ):
207
- raise ClassiqExpansionError(
208
- ALREADY_ALLOCATED_MESSAGE.format(captured_handle.handle)
209
- )
210
- elif captured_handle.direction in (
211
- PortDirection.Input,
212
- PortDirection.Inout,
213
- ):
214
- raise ClassiqExpansionError(
215
- ALREADY_FREED_MESSAGE.format(captured_handle.handle)
216
- )
217
- return captured_handle
232
+ raise ClassiqExpansionError(INITIALIZED_VAR_MESSAGE.format(var_name))
233
+ return PortDirection.Output
234
+
235
+ if source_direction == PortDirection.Inout:
236
+ if target_direction in (PortDirection.Input, PortDirection.Inout):
237
+ return target_direction
238
+ raise ClassiqExpansionError(INITIALIZED_VAR_MESSAGE.format(var_name))
239
+
240
+ if source_direction == PortDirection.Outin:
241
+ if target_direction in (PortDirection.Output, PortDirection.Outin):
242
+ return target_direction
243
+ raise ClassiqExpansionError(UNINITIALIZED_VAR_MESSAGE.format(var_name))
244
+
245
+ raise ClassiqInternalExpansionError(f"Unexpected direction {source_direction}")
218
246
 
219
247
  def _intersect_handles(
220
248
  self,
@@ -227,7 +255,7 @@ class CapturedVars:
227
255
  PortDirection.Outin,
228
256
  ):
229
257
  raise ClassiqExpansionError(
230
- ALREADY_FREED_MESSAGE.format(captured_handle.handle)
258
+ UNINITIALIZED_VAR_MESSAGE.format(captured_handle.handle)
231
259
  )
232
260
  return existing_captured_handle
233
261
 
@@ -237,7 +265,7 @@ class CapturedVars:
237
265
  PortDirection.Outin,
238
266
  ):
239
267
  raise ClassiqExpansionError(
240
- ALREADY_ALLOCATED_MESSAGE.format(captured_handle.handle)
268
+ INITIALIZED_VAR_MESSAGE.format(captured_handle.handle)
241
269
  )
242
270
  return captured_handle
243
271
 
@@ -316,16 +344,7 @@ class CapturedVars:
316
344
  ]
317
345
  )
318
346
 
319
- def filter_vars(
320
- self,
321
- current_function: "FunctionClosure",
322
- current_declarations: Optional[list[VariableDeclarationStatement]] = None,
323
- ) -> "CapturedVars":
324
- current_declared_vars = (
325
- None
326
- if current_declarations is None
327
- else {decl.name for decl in current_declarations}
328
- )
347
+ def filter_vars(self, current_function: "FunctionClosure") -> "CapturedVars":
329
348
  return CapturedVars(
330
349
  _captured_handles=[
331
350
  captured_handle
@@ -333,7 +352,18 @@ class CapturedVars:
333
352
  if not _same_closure(
334
353
  captured_handle.defining_function, current_function
335
354
  )
336
- or (
355
+ ]
356
+ )
357
+
358
+ def filter_var_decls(
359
+ self, current_declarations: list[VariableDeclarationStatement]
360
+ ) -> "CapturedVars":
361
+ current_declared_vars = {decl.name for decl in current_declarations}
362
+ return CapturedVars(
363
+ _captured_handles=[
364
+ captured_handle
365
+ for captured_handle in self._captured_handles
366
+ if (
337
367
  current_declared_vars is not None
338
368
  and captured_handle.handle.name not in current_declared_vars
339
369
  )
@@ -376,8 +406,92 @@ class CapturedVars:
376
406
  if not captured_handle.is_propagated
377
407
  }
378
408
 
409
+ def init_var(self, var_name: str, defining_function: "FunctionClosure") -> None:
410
+ self._handle_states.append((var_name, defining_function, False))
411
+
412
+ def init_params(self, func: "FunctionClosure") -> None:
413
+ ports = {
414
+ param.name: param.direction
415
+ for param in func.positional_arg_declarations
416
+ if isinstance(param, PortDeclaration)
417
+ }
418
+ new_handle_states = [
419
+ handle_state
420
+ for handle_state in self._handle_states
421
+ if handle_state[0] not in ports
422
+ ]
423
+ for var_name, direction in ports.items():
424
+ new_handle_states.append(
425
+ (
426
+ var_name,
427
+ func,
428
+ PortDirection.load(direction)
429
+ in (PortDirection.Input, PortDirection.Inout),
430
+ )
431
+ )
432
+ self._handle_states = new_handle_states
433
+
434
+ def _get_handle_states(self) -> list[HandleState]:
435
+ return self._handle_states + list(
436
+ {
437
+ (
438
+ captured_handle.handle.name,
439
+ captured_handle.defining_function.depth,
440
+ ): (
441
+ captured_handle.handle.name,
442
+ captured_handle.defining_function,
443
+ captured_handle.direction
444
+ in (PortDirection.Output, PortDirection.Inout),
445
+ )
446
+ for captured_handle in self._captured_handles
447
+ }.values()
448
+ )
449
+
450
+ def set_parent(self, parent: "CapturedVars") -> None:
451
+ self._handle_states += parent._get_handle_states()
452
+
453
+ def get_state(self, var_name: str, defining_function: "FunctionClosure") -> bool:
454
+ for name, func, state in self._handle_states:
455
+ if name == var_name and _same_closure(func, defining_function):
456
+ return state
457
+ for captured_handle in self._captured_handles:
458
+ if captured_handle.handle.name == var_name and _same_closure(
459
+ captured_handle.defining_function, defining_function
460
+ ):
461
+ return captured_handle.direction in (
462
+ PortDirection.Output,
463
+ PortDirection.Inout,
464
+ )
465
+ raise ClassiqInternalExpansionError(
466
+ f"Cannot find {var_name!r} from {defining_function.name!r}"
467
+ )
468
+
379
469
  def clone(self) -> "CapturedVars":
380
- return CapturedVars(_captured_handles=list(self._captured_handles))
470
+ return CapturedVars(
471
+ _captured_handles=list(self._captured_handles),
472
+ _handle_states=list(self._handle_states),
473
+ )
474
+
475
+ def set(
476
+ self,
477
+ other: "CapturedVars",
478
+ source_func: "FunctionClosure",
479
+ target_func: "FunctionClosure",
480
+ ) -> None:
481
+ self._captured_handles = []
482
+ for captured_handle in other._captured_handles:
483
+ if _same_closure(captured_handle.defining_function, source_func):
484
+ self._captured_handles.append(
485
+ captured_handle.change_defining_function(target_func)
486
+ )
487
+ else:
488
+ self._captured_handles.append(captured_handle)
489
+ self._handle_states = []
490
+ for var, defining_function, state in other._handle_states:
491
+ if _same_closure(defining_function, source_func):
492
+ self._handle_states.append((var, target_func, state))
493
+ else:
494
+ self._handle_states.append((var, defining_function, state))
381
495
 
382
496
 
383
497
  def _same_closure(closure_1: "FunctionClosure", closure_2: "FunctionClosure") -> bool:
@@ -433,3 +547,19 @@ def validate_captured_directions(
433
547
  raise ClassiqExpansionError(
434
548
  f"Captured quantum variables {captured_outputs!r} cannot be used as outputs"
435
549
  )
550
+
551
+
552
+ def validate_end_state(func: "FunctionClosure", captured_vars: CapturedVars) -> None:
553
+ for param in func.positional_arg_declarations:
554
+ if isinstance(param, PortDeclaration):
555
+ state = captured_vars.get_state(param.name, func)
556
+ expected_state = param.direction in (
557
+ PortDeclarationDirection.Output,
558
+ PortDeclarationDirection.Inout,
559
+ )
560
+ if state != expected_state:
561
+ status = "initialized" if expected_state else "uninitialized"
562
+ raise ClassiqExpansionError(
563
+ f"At the end of function {func.name}, variable {param.name!r} "
564
+ f"should be {status}"
565
+ )
@@ -83,18 +83,21 @@ class FunctionClosure(Closure):
83
83
  scope: Scope,
84
84
  body: Optional[Sequence[QuantumStatement]] = None,
85
85
  positional_arg_declarations: Sequence[PositionalArg] = tuple(),
86
- is_lambda: bool = False,
86
+ lambda_external_vars: Optional[CapturedVars] = None,
87
87
  is_atomic: bool = False,
88
88
  **kwargs: Any,
89
89
  ) -> Self:
90
90
  blocks = {"body": body} if body is not None else {}
91
+ captured_vars = CapturedVars()
92
+ if lambda_external_vars is not None:
93
+ captured_vars.set_parent(lambda_external_vars)
91
94
  return cls(
92
95
  name,
93
96
  blocks,
94
97
  scope,
95
98
  positional_arg_declarations,
96
- CapturedVars(),
97
- is_lambda,
99
+ captured_vars,
100
+ lambda_external_vars is not None,
98
101
  is_atomic,
99
102
  **kwargs,
100
103
  )
@@ -4,6 +4,10 @@ from sympy import Equality
4
4
  from sympy.core.numbers import Number
5
5
 
6
6
  from classiq.interface.exceptions import ClassiqExpansionError
7
+ from classiq.interface.generator.arith.argument_utils import (
8
+ unsigned_integer_interpretation,
9
+ )
10
+ from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
7
11
  from classiq.interface.generator.expressions.qmod_qscalar_proxy import (
8
12
  QmodQNumProxy,
9
13
  QmodQScalarProxy,
@@ -31,14 +35,15 @@ def resolve_num_condition(condition: Equality) -> tuple[QmodSizedProxy, str]:
31
35
  def _calculate_ctrl_state(ctrl: QmodSizedProxy, ctrl_val: float) -> str:
32
36
  is_signed, fraction_places = _get_numeric_attributes(ctrl)
33
37
 
34
- integer_ctrl_val = _get_integer_ctrl_val(ctrl, ctrl_val, fraction_places)
35
-
36
- _validate_control_value_sign(ctrl, integer_ctrl_val, is_signed)
37
- _validate_control_var_qubits(
38
- ctrl, integer_ctrl_val, is_signed, fraction_places, ctrl_val
38
+ reg = RegisterArithmeticInfo(
39
+ size=ctrl.size, is_signed=is_signed, fraction_places=fraction_places
39
40
  )
41
+ uint_ctrl_val = unsigned_integer_interpretation(ctrl_val, reg)
40
42
 
41
- return _to_twos_complement(integer_ctrl_val, ctrl.size)
43
+ _validate_control_value_sign(ctrl, ctrl_val, is_signed)
44
+ _validate_control_var_qubits(ctrl, uint_ctrl_val, fraction_places, ctrl_val)
45
+
46
+ return _to_twos_complement(uint_ctrl_val, ctrl.size)
42
47
 
43
48
 
44
49
  def _get_numeric_attributes(ctrl: QmodSizedProxy) -> tuple[bool, int]:
@@ -49,20 +54,8 @@ def _get_numeric_attributes(ctrl: QmodSizedProxy) -> tuple[bool, int]:
49
54
  )
50
55
 
51
56
 
52
- def _get_integer_ctrl_val(
53
- ctrl: QmodSizedProxy, ctrl_val: float, fraction_places: int
54
- ) -> int:
55
- unfractioned_ctrl_val = ctrl_val * 2**fraction_places
56
- if unfractioned_ctrl_val != int(unfractioned_ctrl_val):
57
- raise ClassiqExpansionError(
58
- f"Variable {str(ctrl)!r} doesne't have enough fraction digits to "
59
- f"represent control value {ctrl_val}"
60
- )
61
- return int(unfractioned_ctrl_val)
62
-
63
-
64
57
  def _validate_control_value_sign(
65
- ctrl: QmodSizedProxy, ctrl_val: int, is_signed: bool
58
+ ctrl: QmodSizedProxy, ctrl_val: float, is_signed: bool
66
59
  ) -> None:
67
60
  if not is_signed and ctrl_val < 0:
68
61
  raise ClassiqExpansionError(
@@ -74,18 +67,17 @@ def _validate_control_value_sign(
74
67
  def _validate_control_var_qubits(
75
68
  ctrl: QmodSizedProxy,
76
69
  ctrl_val: int,
77
- is_signed: bool,
78
70
  fraction_places: int,
79
71
  orig_ctrl_val: float,
80
72
  ) -> None:
81
- required_qubits = _min_bit_length(ctrl_val, is_signed)
73
+ required_qubits = _min_unsigned_bit_length(ctrl_val)
82
74
  fraction_places_message = (
83
75
  f" with {fraction_places} fraction digits" if fraction_places else ""
84
76
  )
85
77
  if ctrl.size < required_qubits:
86
78
  raise ClassiqExpansionError(
87
79
  f"Variable {str(ctrl)!r} has {ctrl.size} qubits{fraction_places_message} but control value "
88
- f"{str(orig_ctrl_val if fraction_places else ctrl_val)!r} requires at least {required_qubits} qubits{fraction_places_message}"
80
+ f"{str(orig_ctrl_val if fraction_places else int(orig_ctrl_val))!r} requires at least {required_qubits} qubits{fraction_places_message}"
89
81
  )
90
82
 
91
83
 
@@ -116,22 +108,6 @@ def _min_unsigned_bit_length(number: int) -> int:
116
108
  raise e
117
109
 
118
110
 
119
- def _min_signed_bit_length(number: int) -> int:
120
- pos_val = abs(number)
121
- is_whole = pos_val & (pos_val - 1) == 0
122
- if number <= 0 and is_whole:
123
- return _min_unsigned_bit_length(pos_val)
124
- return _min_unsigned_bit_length(pos_val) + 1
125
-
126
-
127
- def _min_bit_length(number: int, is_signed: bool) -> int:
128
- return (
129
- _min_signed_bit_length(number)
130
- if is_signed
131
- else _min_unsigned_bit_length(number)
132
- )
133
-
134
-
135
111
  def _to_twos_complement(value: int, bits: int) -> str:
136
112
  if value >= 0:
137
113
  return bin(value)[2:].zfill(bits)[::-1]
@@ -28,6 +28,7 @@ from classiq.interface.source_reference import SourceReference
28
28
  from classiq.model_expansions.capturing.captured_vars import (
29
29
  CapturedVars,
30
30
  validate_captured_directions,
31
+ validate_end_state,
31
32
  )
32
33
  from classiq.model_expansions.closure import (
33
34
  Closure,
@@ -142,7 +143,9 @@ class OperationBuilder:
142
143
  @contextmanager
143
144
  def block_context(self, block_name: str) -> Iterator[None]:
144
145
  self._blocks.append(block_name)
145
- self._operations[-1].blocks[block_name] = Block()
146
+ block = Block()
147
+ block.captured_vars.set_parent(self.current_operation.captured_vars)
148
+ self._operations[-1].blocks[block_name] = block
146
149
  yield
147
150
  captured_vars = self.current_block.captured_vars
148
151
  if (
@@ -150,14 +153,12 @@ class OperationBuilder:
150
153
  and self.current_operation.name != WITHIN_APPLY_NAME
151
154
  ):
152
155
  validate_captured_directions(
153
- captured_vars.filter_vars(
154
- self.current_function, self.current_block.variable_declarations
156
+ captured_vars.filter_var_decls(
157
+ self.current_block.variable_declarations
155
158
  ),
156
159
  report_outin=False,
157
160
  )
158
- self.current_operation.captured_vars.update(
159
- captured_vars.filter_vars(self.current_function)
160
- )
161
+ self.current_operation.captured_vars.update(captured_vars)
161
162
  self._blocks.pop()
162
163
 
163
164
  @contextmanager
@@ -167,11 +168,17 @@ class OperationBuilder:
167
168
  context: OperationContext
168
169
  if isinstance(original_operation, FunctionClosure):
169
170
  context = FunctionContext.create(original_operation)
171
+ context.closure.captured_vars.init_params(original_operation)
170
172
  else:
171
173
  context = OperationContext(closure=original_operation)
174
+ context.closure.captured_vars.set_parent(self.current_block.captured_vars)
172
175
  self._operations.append(context)
173
176
  yield context
174
177
  self._finalize_within_apply()
178
+ if isinstance(self.current_operation, FunctionClosure):
179
+ validate_end_state(
180
+ self.current_operation, self.current_operation.captured_vars
181
+ )
175
182
  self._propagate_captured_vars()
176
183
  self._operations.pop()
177
184
 
@@ -179,21 +186,19 @@ class OperationBuilder:
179
186
  if self.current_operation.name != WITHIN_APPLY_NAME:
180
187
  return
181
188
  within_captured_vars = self._operations[-1].blocks["within"].captured_vars
182
- self.current_operation.captured_vars.update(
183
- within_captured_vars.filter_vars(self.current_function).negate()
184
- )
189
+ self.current_operation.captured_vars.update(within_captured_vars.negate())
185
190
 
186
191
  def _propagate_captured_vars(self) -> None:
187
192
  captured_vars = self.current_operation.captured_vars
188
193
  if isinstance(self.current_operation, FunctionClosure):
189
- captured_vars = captured_vars.set_propagated()
190
- validate_captured_directions(captured_vars)
194
+ captured_vars = captured_vars.filter_vars(
195
+ self.current_function
196
+ ).set_propagated()
197
+ validate_captured_directions(captured_vars)
191
198
  if len(self._operations) < 2:
192
199
  return
193
200
  parent_block = self._operations[-2].blocks[self._blocks[-1]]
194
- parent_block.captured_vars.update(
195
- captured_vars.filter_vars(self.parent_function)
196
- )
201
+ parent_block.captured_vars.update(captured_vars)
197
202
 
198
203
  @contextmanager
199
204
  def source_ref_context(
@@ -24,9 +24,7 @@ from classiq.qmod.generative import generative_mode_context, set_frontend_interp
24
24
  from classiq.qmod.model_state_container import QMODULE
25
25
  from classiq.qmod.qmod_parameter import CParamStruct
26
26
  from classiq.qmod.qmod_variable import QNum, _create_qvar_for_qtype
27
- from classiq.qmod.quantum_callable import QCallable
28
27
  from classiq.qmod.quantum_expandable import (
29
- QExpandable,
30
28
  QTerminalCallable,
31
29
  )
32
30
  from classiq.qmod.quantum_function import QFunc
@@ -147,12 +145,10 @@ def emit_generative_statements(
147
145
  translate_ast_arg_to_python_qmod(param, arg)
148
146
  for param, arg in zip(operation.positional_arg_declarations, args)
149
147
  ]
150
- interpreter_expandable = _InterpreterExpandable(interpreter)
151
- QExpandable.STACK.append(interpreter_expandable)
152
- QCallable.CURRENT_EXPANDABLE = interpreter_expandable
153
- set_frontend_interpreter(interpreter)
154
- for block_name, generative_function in operation.generative_blocks.items():
155
- with interpreter._builder.block_context(block_name), generative_mode_context(
156
- True
157
- ):
158
- generative_function._py_callable(*python_qmod_args)
148
+ with _InterpreterExpandable(interpreter):
149
+ set_frontend_interpreter(interpreter)
150
+ for block_name, generative_function in operation.generative_blocks.items():
151
+ with interpreter._builder.block_context(
152
+ block_name
153
+ ), generative_mode_context(True):
154
+ generative_function._py_callable(*python_qmod_args)
@@ -144,7 +144,7 @@ class BaseInterpreter:
144
144
 
145
145
  def process_exception(self, e: Exception) -> None:
146
146
  if not isinstance(e, (ClassiqError, ValidationError)):
147
- raise ClassiqInternalExpansionError(str(e)) from None
147
+ raise ClassiqInternalExpansionError(str(e)) from e
148
148
  prefix = ""
149
149
  if not isinstance(e, ClassiqExpansionError):
150
150
  prefix = f"{type(e).__name__}: "
@@ -212,8 +212,15 @@ class BaseInterpreter:
212
212
 
213
213
  @evaluate.register
214
214
  def evaluate_field_access(self, field_access: FieldHandleBinding) -> Evaluated:
215
- base_value = self.evaluate(field_access.base_handle)
216
- return Evaluated(value=base_value.value.fields[field_access.field])
215
+ base_value = self.evaluate(field_access.base_handle).as_type(QuantumSymbol)
216
+ fields = base_value.fields
217
+ field_name = field_access.field
218
+ if field_name not in fields:
219
+ raise ClassiqExpansionError(
220
+ f"Struct {base_value.quantum_type.type_name} has no field "
221
+ f"{field_name!r}. Available fields: {', '.join(fields.keys())}"
222
+ )
223
+ return Evaluated(value=fields[field_name])
217
224
 
218
225
  @abstractmethod
219
226
  def emit(self, statement: QuantumStatement) -> None:
@@ -251,8 +258,10 @@ class BaseInterpreter:
251
258
  (func_def := self._expanded_functions.get(operation.closure_id))
252
259
  is not None
253
260
  ):
254
- captured_vars = self._top_level_scope[func_def.name].value.captured_vars
255
- operation.captured_vars.update(captured_vars)
261
+ cached_closure = self._top_level_scope[func_def.name].value
262
+ operation.captured_vars.set(
263
+ cached_closure.captured_vars, cached_closure, operation
264
+ )
256
265
  else:
257
266
  self._expand_body(operation)
258
267
 
@@ -101,15 +101,16 @@ class GenerativeInterpreter(BaseInterpreter):
101
101
  closure_class = FunctionClosure
102
102
  extra_args = {}
103
103
 
104
+ closure = closure_class.create(
105
+ name=func_decl.name,
106
+ positional_arg_declarations=func_decl.positional_arg_declarations,
107
+ body=function.body,
108
+ scope=Scope(parent=self._builder.current_scope),
109
+ lambda_external_vars=self._builder.current_block.captured_vars,
110
+ **extra_args,
111
+ )
104
112
  return Evaluated(
105
- value=closure_class.create(
106
- name=func_decl.name,
107
- positional_arg_declarations=func_decl.positional_arg_declarations,
108
- body=function.body,
109
- scope=Scope(parent=self._builder.current_scope),
110
- is_lambda=True,
111
- **extra_args,
112
- ),
113
+ value=closure,
113
114
  defining_function=self._builder.current_function,
114
115
  )
115
116
 
@@ -13,7 +13,6 @@ from classiq.model_expansions.scope import QuantumSymbol
13
13
 
14
14
  class AllocateEmitter(Emitter[Allocate]):
15
15
  def emit(self, allocate: Allocate, /) -> None:
16
- self._register_debug_info(allocate)
17
16
  target: QuantumSymbol = self._interpreter.evaluate(allocate.target).as_type(
18
17
  QuantumSymbol
19
18
  )
@@ -25,8 +24,13 @@ class AllocateEmitter(Emitter[Allocate]):
25
24
 
26
25
  size = self._get_var_size(target, allocate.size)
27
26
  allocate = allocate.model_copy(
28
- update=dict(size=Expression(expr=str(size)), target=target.handle)
27
+ update=dict(
28
+ size=Expression(expr=str(size)),
29
+ target=target.handle,
30
+ back_ref=allocate.uuid,
31
+ )
29
32
  )
33
+ self._register_debug_info(allocate)
30
34
  self.emit_statement(allocate)
31
35
 
32
36
  def _get_var_size(self, target: QuantumSymbol, size: Expression | None) -> int: