classiq 0.66.1__py3-none-any.whl → 0.68.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 (89) hide show
  1. classiq/_internals/api_wrapper.py +5 -1
  2. classiq/_internals/async_utils.py +1 -1
  3. classiq/_internals/authentication/password_manager.py +1 -1
  4. classiq/_internals/client.py +1 -1
  5. classiq/applications/finance/finance_model_constructor.py +9 -0
  6. classiq/applications/grover/grover_model_constructor.py +10 -0
  7. classiq/applications/qnn/qlayer.py +8 -2
  8. classiq/applications/qsvm/qsvm_model_constructor.py +9 -0
  9. classiq/execution/execution_session.py +7 -3
  10. classiq/executor.py +7 -2
  11. classiq/interface/_version.py +1 -1
  12. classiq/interface/ast_node.py +1 -1
  13. classiq/interface/chemistry/operator.py +1 -1
  14. classiq/interface/debug_info/debug_info.py +27 -0
  15. classiq/interface/exceptions.py +2 -5
  16. classiq/interface/generator/arith/argument_utils.py +1 -1
  17. classiq/interface/generator/arith/arithmetic.py +99 -2
  18. classiq/interface/generator/arith/arithmetic_expression_parser.py +1 -1
  19. classiq/interface/generator/arith/binary_ops.py +3 -0
  20. classiq/interface/generator/function_param_list_without_self_reference.py +2 -0
  21. classiq/interface/generator/functions/type_name.py +2 -2
  22. classiq/interface/generator/generated_circuit_data.py +38 -3
  23. classiq/interface/generator/hardware_efficient_ansatz.py +1 -1
  24. classiq/interface/generator/hva.py +1 -1
  25. classiq/interface/generator/model/preferences/preferences.py +8 -1
  26. classiq/interface/generator/quantum_program.py +18 -1
  27. classiq/interface/generator/reset.py +14 -0
  28. classiq/interface/generator/types/enum_declaration.py +33 -2
  29. classiq/interface/generator/ucc.py +1 -1
  30. classiq/interface/interface_version.py +1 -1
  31. classiq/interface/model/classical_if.py +2 -2
  32. classiq/interface/model/control.py +2 -2
  33. classiq/interface/model/invert.py +2 -2
  34. classiq/interface/model/power.py +2 -2
  35. classiq/interface/model/quantum_function_call.py +4 -0
  36. classiq/interface/model/quantum_statement.py +13 -0
  37. classiq/interface/model/repeat.py +2 -2
  38. classiq/interface/model/statement_block.py +1 -1
  39. classiq/interface/model/within_apply_operation.py +2 -2
  40. classiq/model_expansions/atomic_expression_functions_defs.py +2 -1
  41. classiq/model_expansions/capturing/captured_vars.py +184 -54
  42. classiq/model_expansions/closure.py +6 -3
  43. classiq/model_expansions/evaluators/control.py +14 -38
  44. classiq/model_expansions/function_builder.py +19 -14
  45. classiq/model_expansions/generative_functions.py +7 -11
  46. classiq/model_expansions/interpreters/base_interpreter.py +14 -5
  47. classiq/model_expansions/interpreters/generative_interpreter.py +87 -26
  48. classiq/model_expansions/quantum_operations/allocate.py +9 -3
  49. classiq/model_expansions/quantum_operations/assignment_result_processor.py +52 -0
  50. classiq/model_expansions/quantum_operations/bind.py +67 -14
  51. classiq/model_expansions/quantum_operations/block_evaluator.py +76 -0
  52. classiq/model_expansions/quantum_operations/call_emitter.py +79 -10
  53. classiq/model_expansions/quantum_operations/classicalif.py +10 -6
  54. classiq/model_expansions/quantum_operations/composite_emitter.py +27 -0
  55. classiq/model_expansions/quantum_operations/emitter.py +24 -3
  56. classiq/model_expansions/quantum_operations/expression_evaluator.py +33 -0
  57. classiq/model_expansions/quantum_operations/handle_evaluator.py +28 -0
  58. classiq/model_expansions/quantum_operations/quantum_function_call.py +3 -2
  59. classiq/model_expansions/quantum_operations/repeat.py +9 -3
  60. classiq/model_expansions/quantum_operations/variable_decleration.py +13 -2
  61. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +1 -1
  62. classiq/open_library/functions/__init__.py +1 -2
  63. classiq/open_library/functions/amplitude_amplification.py +12 -9
  64. classiq/open_library/functions/discrete_sine_cosine_transform.py +16 -13
  65. classiq/open_library/functions/grover.py +11 -15
  66. classiq/open_library/functions/modular_exponentiation.py +7 -13
  67. classiq/open_library/functions/state_preparation.py +16 -17
  68. classiq/open_library/functions/swap_test.py +1 -1
  69. classiq/open_library/functions/utility_functions.py +10 -2
  70. classiq/qmod/builtins/functions/__init__.py +3 -0
  71. classiq/qmod/builtins/functions/chemistry.py +6 -38
  72. classiq/qmod/builtins/functions/mid_circuit_measurement.py +15 -0
  73. classiq/qmod/quantum_expandable.py +30 -6
  74. classiq/qmod/quantum_function.py +4 -0
  75. classiq/qmod/semantics/annotation/call_annotation.py +8 -2
  76. classiq/qmod/semantics/annotation/model_annotation.py +9 -0
  77. classiq/qmod/semantics/error_manager.py +0 -6
  78. classiq/qmod/semantics/static_semantics_visitor.py +0 -347
  79. classiq/qmod/semantics/validation/types_validation.py +1 -1
  80. classiq/qmod/symbolic.py +2 -1
  81. classiq/qmod/utilities.py +2 -1
  82. classiq/qmod/write_qmod.py +10 -7
  83. classiq/synthesis.py +20 -7
  84. {classiq-0.66.1.dist-info → classiq-0.68.0.dist-info}/METADATA +1 -1
  85. {classiq-0.66.1.dist-info → classiq-0.68.0.dist-info}/RECORD +86 -81
  86. classiq/model_expansions/quantum_operations/shallow_emitter.py +0 -166
  87. classiq/qmod/semantics/validation/func_call_validation.py +0 -99
  88. classiq/qmod/semantics/validation/handle_validation.py +0 -85
  89. {classiq-0.66.1.dist-info → classiq-0.68.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,14 @@
1
+ from classiq.interface.generator.arith.register_user_input import (
2
+ RegisterArithmeticInfo,
3
+ RegisterUserInput,
4
+ )
5
+ from classiq.interface.generator.function_params import FunctionParams
6
+
7
+
8
+ class Reset(FunctionParams):
9
+ target: RegisterUserInput
10
+
11
+ def _create_ios(self) -> None:
12
+ mapping: dict[str, RegisterArithmeticInfo] = {self.target.name: self.target}
13
+ self._inputs = mapping
14
+ self._outputs = mapping
@@ -1,5 +1,6 @@
1
1
  from collections import Counter
2
2
  from enum import Enum, EnumMeta, IntEnum
3
+ from typing import Any, Callable
3
4
 
4
5
  import pydantic
5
6
 
@@ -7,6 +8,33 @@ from classiq.interface.ast_node import HashableASTNode
7
8
  from classiq.interface.exceptions import ClassiqValueError
8
9
 
9
10
 
11
+ def rebuild_dynamic_enum(name: str, members: dict[str, int]) -> type[IntEnum]:
12
+ """
13
+ Rebuilds the dynamic enum from its name and members.
14
+ Returns a new enum type.
15
+ """
16
+ new_enum = IntEnum(name, members) # type: ignore[misc]
17
+ setattr(new_enum, "__members_data__", members) # noqa: B010
18
+ setattr(new_enum, "__reduce_ex__", dynamic_enum_reduce_ex) # noqa: B010
19
+ return new_enum
20
+
21
+
22
+ def dynamic_enum_reduce_ex(
23
+ obj: Any, protocol: int
24
+ ) -> tuple[Callable[..., type[IntEnum]], tuple[str, dict[str, int]]]:
25
+ """
26
+ Custom __reduce_ex__ for dynamic enums.
27
+ This function will be used when pickling an enum type or one of its members.
28
+ """
29
+ # Get the enum type and its member data.
30
+ enum_type = type(obj)
31
+ members = getattr(enum_type, "__members_data__", None)
32
+ if members is None:
33
+ raise ValueError("Dynamic enum is missing __members_data__ attribute")
34
+ # Return the callable and arguments needed to rebuild the enum type.
35
+ return rebuild_dynamic_enum, (enum_type.__name__, members)
36
+
37
+
10
38
  class EnumDeclaration(HashableASTNode):
11
39
  name: str
12
40
 
@@ -39,8 +67,11 @@ class EnumDeclaration(HashableASTNode):
39
67
 
40
68
  return members
41
69
 
42
- def create_enum(self) -> IntEnum:
43
- return IntEnum(self.name, self.members)
70
+ def create_enum(self) -> type[IntEnum]:
71
+ dynamic_enum = IntEnum(self.name, self.members) # type: ignore[misc]
72
+ setattr(dynamic_enum, "__members_data__", self.members) # noqa: B010
73
+ setattr(dynamic_enum, "__reduce_ex__", dynamic_enum_reduce_ex) # noqa: B010
74
+ return dynamic_enum
44
75
 
45
76
 
46
77
  def declaration_from_enum(enum_type: EnumMeta) -> EnumDeclaration:
@@ -38,7 +38,7 @@ class UCC(ChemistryFunctionParams):
38
38
  description="Maximum depth of the generated quantum circuit ansatz",
39
39
  )
40
40
  parameter_prefix: str = pydantic.Field(
41
- default="param_",
41
+ default="ucc_param_",
42
42
  description="Prefix for the generated parameters",
43
43
  )
44
44
 
@@ -1 +1 @@
1
- INTERFACE_VERSION = "7"
1
+ INTERFACE_VERSION = "8"
@@ -1,6 +1,6 @@
1
1
  from typing import TYPE_CHECKING, Literal
2
2
 
3
- from classiq.interface.ast_node import ASTNodeType, without_statement_blocks
3
+ from classiq.interface.ast_node import ASTNodeType, reset_lists
4
4
  from classiq.interface.generator.expressions.expression import Expression
5
5
  from classiq.interface.model.quantum_statement import QuantumOperation
6
6
 
@@ -16,4 +16,4 @@ class ClassicalIf(QuantumOperation):
16
16
  else_: "StatementBlock"
17
17
 
18
18
  def _as_back_ref(self: ASTNodeType) -> ASTNodeType:
19
- return without_statement_blocks(self, ["then", "else_"])
19
+ return reset_lists(self, ["then", "else_"])
@@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Literal, Optional
2
2
 
3
3
  import pydantic
4
4
 
5
- from classiq.interface.ast_node import ASTNodeType, without_statement_blocks
5
+ from classiq.interface.ast_node import ASTNodeType, reset_lists
6
6
  from classiq.interface.generator.arith.arithmetic import compute_arithmetic_result_type
7
7
  from classiq.interface.model.quantum_expressions.quantum_expression import (
8
8
  QuantumExpressionOperation,
@@ -46,4 +46,4 @@ class Control(QuantumExpressionOperation):
46
46
  )
47
47
 
48
48
  def _as_back_ref(self: ASTNodeType) -> ASTNodeType:
49
- return without_statement_blocks(self, ["body", "else_block"])
49
+ return reset_lists(self, ["body", "else_block"])
@@ -1,6 +1,6 @@
1
1
  from typing import TYPE_CHECKING, Literal
2
2
 
3
- from classiq.interface.ast_node import ASTNodeType, without_statement_blocks
3
+ from classiq.interface.ast_node import ASTNodeType, reset_lists
4
4
  from classiq.interface.model.quantum_statement import QuantumOperation
5
5
 
6
6
  if TYPE_CHECKING:
@@ -13,4 +13,4 @@ class Invert(QuantumOperation):
13
13
  body: "StatementBlock"
14
14
 
15
15
  def _as_back_ref(self: ASTNodeType) -> ASTNodeType:
16
- return without_statement_blocks(self, ["body"])
16
+ return reset_lists(self, ["body"])
@@ -1,6 +1,6 @@
1
1
  from typing import TYPE_CHECKING, Literal
2
2
 
3
- from classiq.interface.ast_node import ASTNodeType, without_statement_blocks
3
+ from classiq.interface.ast_node import ASTNodeType, reset_lists
4
4
  from classiq.interface.generator.expressions.expression import Expression
5
5
  from classiq.interface.model.quantum_statement import QuantumOperation
6
6
 
@@ -15,4 +15,4 @@ class Power(QuantumOperation):
15
15
  body: "StatementBlock"
16
16
 
17
17
  def _as_back_ref(self: ASTNodeType) -> ASTNodeType:
18
- return without_statement_blocks(self, ["body"])
18
+ return reset_lists(self, ["body"])
@@ -7,6 +7,7 @@ from typing import (
7
7
 
8
8
  import pydantic
9
9
 
10
+ from classiq.interface.ast_node import ASTNodeType, reset_lists
10
11
  from classiq.interface.exceptions import ClassiqError, ClassiqValueError
11
12
  from classiq.interface.generator.expressions.expression import Expression
12
13
  from classiq.interface.generator.functions.port_declaration import (
@@ -45,6 +46,9 @@ class QuantumFunctionCall(QuantumOperation):
45
46
  default=None
46
47
  )
47
48
 
49
+ def _as_back_ref(self: ASTNodeType) -> ASTNodeType:
50
+ return reset_lists(self, ["positional_args"])
51
+
48
52
  @property
49
53
  def func_decl(self) -> QuantumFunctionDeclaration:
50
54
  if self._func_decl is None:
@@ -5,6 +5,7 @@ from uuid import UUID, uuid4
5
5
 
6
6
  import pydantic
7
7
  from pydantic import ConfigDict
8
+ from typing_extensions import Self
8
9
 
9
10
  from classiq.interface.ast_node import ASTNode
10
11
  from classiq.interface.helpers.pydantic_model_helpers import values_with_discriminator
@@ -21,6 +22,18 @@ class QuantumStatement(ASTNode):
21
22
  description="A unique identifier for this operation", default_factory=uuid4
22
23
  )
23
24
 
25
+ def model_copy(
26
+ self,
27
+ *,
28
+ update: Optional[dict[str, Any]] = None,
29
+ deep: bool = False,
30
+ keep_uuid: bool = False
31
+ ) -> Self:
32
+ if not keep_uuid:
33
+ update = update or dict()
34
+ update.setdefault("uuid", uuid4())
35
+ return super().model_copy(update=update, deep=deep)
36
+
24
37
  @pydantic.model_validator(mode="before")
25
38
  @classmethod
26
39
  def _set_kind(cls, values: Any) -> dict[str, Any]:
@@ -1,6 +1,6 @@
1
1
  from typing import TYPE_CHECKING, Literal
2
2
 
3
- from classiq.interface.ast_node import ASTNodeType, without_statement_blocks
3
+ from classiq.interface.ast_node import ASTNodeType, reset_lists
4
4
  from classiq.interface.generator.expressions.expression import Expression
5
5
  from classiq.interface.model.quantum_statement import QuantumOperation
6
6
 
@@ -16,4 +16,4 @@ class Repeat(QuantumOperation):
16
16
  body: "StatementBlock"
17
17
 
18
18
  def _as_back_ref(self: ASTNodeType) -> ASTNodeType:
19
- return without_statement_blocks(self, ["body"])
19
+ return reset_lists(self, ["body"])
@@ -27,8 +27,8 @@ from classiq.interface.model.within_apply_operation import WithinApply
27
27
 
28
28
  ConcreteQuantumStatement = Annotated[
29
29
  Union[
30
- Allocate,
31
30
  QuantumFunctionCall,
31
+ Allocate,
32
32
  ArithmeticOperation,
33
33
  AmplitudeLoadingOperation,
34
34
  VariableDeclarationStatement,
@@ -1,6 +1,6 @@
1
1
  from typing import TYPE_CHECKING, Literal
2
2
 
3
- from classiq.interface.ast_node import ASTNodeType, without_statement_blocks
3
+ from classiq.interface.ast_node import ASTNodeType, reset_lists
4
4
  from classiq.interface.model.quantum_statement import QuantumOperation
5
5
 
6
6
  if TYPE_CHECKING:
@@ -14,4 +14,4 @@ class WithinApply(QuantumOperation):
14
14
  action: "StatementBlock"
15
15
 
16
16
  def _as_back_ref(self: ASTNodeType) -> ASTNodeType:
17
- return without_statement_blocks(self, ["compute", "action"])
17
+ return reset_lists(self, ["compute", "action"])
@@ -21,6 +21,7 @@ from classiq.interface.generator.functions.classical_function_declaration import
21
21
  )
22
22
  from classiq.interface.generator.functions.classical_type import (
23
23
  Bool,
24
+ ClassicalArray,
24
25
  ClassicalList,
25
26
  ClassicalType,
26
27
  OpaqueHandle,
@@ -64,7 +65,7 @@ def qmod_val_to_python(val: ExpressionValue, qmod_type: ClassicalType) -> Any:
64
65
  if isinstance(val, (Enum, int)):
65
66
  return val
66
67
 
67
- elif isinstance(qmod_type, ClassicalList):
68
+ elif isinstance(qmod_type, (ClassicalArray, ClassicalList)):
68
69
  if isinstance(val, list):
69
70
  return [qmod_val_to_python(elem, qmod_type.element_type) for elem in val]
70
71
 
@@ -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
  )