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
@@ -3,5 +3,5 @@ from packaging.version import Version
3
3
  # This file was generated automatically
4
4
  # Please don't track in version control (DONTTRACK)
5
5
 
6
- SEMVER_VERSION = '0.54.0'
6
+ SEMVER_VERSION = '0.56.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -5,6 +5,7 @@ from uuid import UUID
5
5
 
6
6
  from pydantic import BaseModel, Field
7
7
 
8
+ from classiq.interface.enum_utils import StrEnum
8
9
  from classiq.interface.generator.generated_circuit_data import (
9
10
  FunctionDebugInfoInterface,
10
11
  OperationLevel,
@@ -13,11 +14,21 @@ from classiq.interface.generator.generated_circuit_data import (
13
14
  ParameterValue = Union[float, int, str, None]
14
15
 
15
16
 
17
+ class StatementType(StrEnum):
18
+ CONTROL = "control"
19
+ POWER = "power"
20
+ INVERT = "invert"
21
+ WITHIN_APPLY = "within_apply"
22
+ ASSIGNMENT = "assignment"
23
+ REPEAT = "repeat"
24
+
25
+
16
26
  class FunctionDebugInfo(BaseModel):
17
27
  name: str
18
28
  # Parameters describe classical parameters passed to function
19
29
  parameters: dict[str, str]
20
30
  level: OperationLevel
31
+ statement_type: Union[StatementType, None] = None
21
32
  is_allocate_or_free: bool = Field(default=False)
22
33
  is_inverse: bool = Field(default=False)
23
34
  port_to_passed_variable_map: dict[str, str] = Field(default_factory=dict)
@@ -315,9 +315,6 @@ class EstimationMetadata(BaseModel, extra="allow"):
315
315
 
316
316
  class EstimationResult(BaseModel, QmodPyObject):
317
317
  value: Complex = pydantic.Field(..., description="Estimation for the operator")
318
- variance: Optional[Complex] = pydantic.Field(
319
- description="Variance of the estimation", default=None
320
- )
321
318
  metadata: EstimationMetadata = pydantic.Field(
322
319
  ..., description="Metadata for the estimation"
323
320
  )
@@ -3,6 +3,14 @@ CONTROL_OPERATOR_NAME = "control"
3
3
  INVERT_OPERATOR_NAME = "invert"
4
4
  REPEAT_OPERATOR_NAME = "iteration"
5
5
  POWER_OPERATOR_NAME = "power"
6
- COMPUTE_OPERATOR_NAME = "compute"
7
6
  UNCOMPUTE_OPERATOR_NAME = "uncompute"
8
7
  WITHIN_APPLY_NAME = "within_apply"
8
+
9
+ All_BUILTINS_OPERATORS = {
10
+ CONTROL_OPERATOR_NAME,
11
+ INVERT_OPERATOR_NAME,
12
+ REPEAT_OPERATOR_NAME,
13
+ POWER_OPERATOR_NAME,
14
+ UNCOMPUTE_OPERATOR_NAME,
15
+ WITHIN_APPLY_NAME,
16
+ }
@@ -59,7 +59,6 @@ class GeneratedFunction(pydantic.BaseModel):
59
59
  registers: list[GeneratedRegister] = list()
60
60
  depth: Optional[int] = pydantic.Field(default=None)
61
61
  width: Optional[int] = pydantic.Field(default=None)
62
- released_auxiliary_qubits: list[int] = list()
63
62
  dangling_inputs: dict[str, GeneratedRegister] = dict()
64
63
  dangling_outputs: dict[str, GeneratedRegister] = dict()
65
64
 
@@ -154,6 +154,10 @@ class Preferences(pydantic.BaseModel, extra="forbid"):
154
154
  "Setting this option to False can potentially speed up the synthesis, and is "
155
155
  "recommended for executing iterative algorithms.",
156
156
  )
157
+ synthesize_all_separately: bool = pydantic.Field(
158
+ default=False,
159
+ description="If true, all functions will be synthesized separately",
160
+ )
157
161
  output_format: PydanticConstrainedQuantumFormatList = pydantic.Field(
158
162
  default=[QuantumFormat.QASM],
159
163
  description="The quantum circuit output format(s). ",
@@ -0,0 +1,5 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class CompilationMetadata(BaseModel):
5
+ should_synthesize_separately: bool = Field(default=False)
@@ -1,8 +1,9 @@
1
- from collections import abc
1
+ from collections import abc, defaultdict
2
2
  from collections.abc import Collection, Mapping, Sequence
3
3
  from typing import (
4
4
  TYPE_CHECKING,
5
5
  Any,
6
+ Callable,
6
7
  Optional,
7
8
  TypeVar,
8
9
  Union,
@@ -84,6 +85,17 @@ class Transformer(Visitor):
84
85
  def visit_dict(self, node: dict[Key, NodeType]) -> dict[Key, RetType]:
85
86
  return {key: self.visit(value) for key, value in node.items()}
86
87
 
88
+ def visit_defaultdict(
89
+ self, node: defaultdict[Key, NodeType]
90
+ ) -> defaultdict[Key, RetType]:
91
+ new_default_factory: Callable[[], RetType] | None = None
92
+ if (default_factory := node.default_factory) is not None:
93
+
94
+ def new_default_factory() -> RetType:
95
+ return self.visit(default_factory()) # type: ignore[misc]
96
+
97
+ return defaultdict(new_default_factory, self.visit_dict(node))
98
+
87
99
  def visit_tuple(self, node: tuple[NodeType, ...]) -> tuple[RetType, ...]:
88
100
  return tuple(self.visit(value) for value in node)
89
101
 
@@ -1,3 +1,4 @@
1
+ from collections import Counter
1
2
  from typing import Any, Optional
2
3
 
3
4
  import pydantic
@@ -23,7 +24,7 @@ class OperationType(StrEnum):
23
24
  class OperationData(pydantic.BaseModel):
24
25
  approximated_depth: Optional[int] = None
25
26
  width: int
26
- gate_count: dict[str, int] = pydantic.Field(default_factory=dict)
27
+ gate_count: Counter[str] = pydantic.Field(default_factory=dict)
27
28
 
28
29
 
29
30
  class CircuitMetrics(pydantic.BaseModel):
@@ -104,6 +105,8 @@ class AtomicGate(StrEnum):
104
105
 
105
106
  class Operation(pydantic.BaseModel):
106
107
  name: str
108
+ qasm_name: str = pydantic.Field(default="")
109
+ details: str = pydantic.Field(default="")
107
110
  children: list["Operation"]
108
111
  operation_data: Optional[OperationData] = None
109
112
  operation_links: OperationLinks
@@ -118,6 +121,7 @@ class Operation(pydantic.BaseModel):
118
121
  gate: AtomicGate = pydantic.Field(
119
122
  default=AtomicGate.UNKNOWN, description="Gate type"
120
123
  )
124
+ is_daggered: bool = pydantic.Field(default=False)
121
125
 
122
126
 
123
127
  class ProgramVisualModel(VersionedModel):
@@ -1 +1 @@
1
- INTERFACE_VERSION = "4"
1
+ INTERFACE_VERSION = "5"
@@ -1,10 +1,12 @@
1
- from typing import TYPE_CHECKING, Literal
1
+ from typing import TYPE_CHECKING, Literal, Optional
2
2
 
3
3
  import pydantic
4
4
 
5
+ from classiq.interface.generator.arith.arithmetic import compute_arithmetic_result_type
5
6
  from classiq.interface.model.quantum_expressions.quantum_expression import (
6
7
  QuantumExpressionOperation,
7
8
  )
9
+ from classiq.interface.model.quantum_type import QuantumType
8
10
 
9
11
  if TYPE_CHECKING:
10
12
  from classiq.interface.model.statement_block import StatementBlock
@@ -13,8 +15,12 @@ if TYPE_CHECKING:
13
15
  class Control(QuantumExpressionOperation):
14
16
  kind: Literal["Control"]
15
17
  body: "StatementBlock"
18
+ else_block: Optional["StatementBlock"] = None
16
19
 
17
20
  _ctrl_size: int = pydantic.PrivateAttr(default=0)
21
+ _result_type: Optional[QuantumType] = pydantic.PrivateAttr(
22
+ default=None,
23
+ )
18
24
 
19
25
  @property
20
26
  def ctrl_size(self) -> int:
@@ -22,3 +28,18 @@ class Control(QuantumExpressionOperation):
22
28
 
23
29
  def set_ctrl_size(self, ctrl_size: int) -> None:
24
30
  self._ctrl_size = ctrl_size
31
+
32
+ @property
33
+ def result_type(self) -> QuantumType:
34
+ assert self._result_type is not None
35
+ return self._result_type
36
+
37
+ def initialize_var_types(
38
+ self,
39
+ var_types: dict[str, QuantumType],
40
+ machine_precision: int,
41
+ ) -> None:
42
+ super().initialize_var_types(var_types, machine_precision)
43
+ self._result_type = compute_arithmetic_result_type(
44
+ self.expression.expr, var_types, machine_precision
45
+ )
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any, Union
4
4
 
5
5
  import pydantic
6
6
  from pydantic import ConfigDict, Field
7
+ from typing_extensions import Self
7
8
 
8
9
  from classiq.interface.ast_node import ASTNode
9
10
  from classiq.interface.generator.expressions.expression import Expression
@@ -46,6 +47,16 @@ class HandleBinding(ASTNode):
46
47
  for self_prefix, other_prefix in zip(self_prefixes, other_prefixes)
47
48
  )
48
49
 
50
+ def rename(self, name: str) -> Self:
51
+ return self.model_copy(update=dict(name=name))
52
+
53
+ def replace_prefix(
54
+ self, prefix: "HandleBinding", replacement: "HandleBinding"
55
+ ) -> "HandleBinding":
56
+ if self == prefix:
57
+ return replacement
58
+ return self
59
+
49
60
 
50
61
  class NestedHandleBinding(HandleBinding):
51
62
  base_handle: "ConcreteHandleBinding"
@@ -70,6 +81,23 @@ class NestedHandleBinding(HandleBinding):
70
81
  def prefixes(self) -> Sequence["HandleBinding"]:
71
82
  return list(chain.from_iterable([self.base_handle.prefixes(), [self]]))
72
83
 
84
+ def rename(self, name: str) -> Self:
85
+ return self.model_copy(
86
+ update=dict(name=name, base_handle=self.base_handle.rename(name))
87
+ )
88
+
89
+ def replace_prefix(
90
+ self, prefix: HandleBinding, replacement: HandleBinding
91
+ ) -> HandleBinding:
92
+ if self == prefix:
93
+ return replacement
94
+ new_base_handle = self.base_handle.replace_prefix(prefix, replacement)
95
+ if new_base_handle is not self.base_handle:
96
+ return self.model_copy(
97
+ update=dict(name=new_base_handle.name, base_handle=new_base_handle)
98
+ )
99
+ return self
100
+
73
101
 
74
102
  class SubscriptHandleBinding(NestedHandleBinding):
75
103
  index: Expression
@@ -15,6 +15,7 @@ from classiq.interface.generator.functions.port_declaration import (
15
15
  from classiq.interface.generator.model.constraints import Constraints
16
16
  from classiq.interface.generator.model.preferences.preferences import Preferences
17
17
  from classiq.interface.generator.quantum_function_call import SUFFIX_RANDOMIZER
18
+ from classiq.interface.generator.types.compilation_metadata import CompilationMetadata
18
19
  from classiq.interface.generator.types.enum_declaration import EnumDeclaration
19
20
  from classiq.interface.generator.types.qstruct_declaration import QStructDeclaration
20
21
  from classiq.interface.generator.types.struct_declaration import StructDeclaration
@@ -98,6 +99,9 @@ class Model(VersionedModel, ASTNode):
98
99
  debug_info: DebugInfoCollection = pydantic.Field(
99
100
  default_factory=DebugInfoCollection
100
101
  )
102
+ functions_compilation_metadata: dict[str, CompilationMetadata] = pydantic.Field(
103
+ default_factory=dict
104
+ )
101
105
 
102
106
  @property
103
107
  def main_func(self) -> NativeFunctionDefinition:
@@ -29,5 +29,5 @@ class NativeFunctionDefinition(NamedParamsQuantumFunctionDeclaration):
29
29
  default_factory=list, description="List of function calls to perform."
30
30
  )
31
31
  synthesis_data: FunctionSynthesisData = pydantic.Field(
32
- default_factory=FunctionSynthesisData,
32
+ default_factory=FunctionSynthesisData, deprecated=True, exclude=True
33
33
  )
@@ -1,8 +1,5 @@
1
1
  from collections.abc import Mapping, Sequence
2
- from typing import Literal, Optional
3
-
4
- import pydantic
5
- from pydantic_core.core_schema import ValidationInfo
2
+ from typing import Literal
6
3
 
7
4
  from classiq.interface.enum_utils import StrEnum
8
5
  from classiq.interface.generator.arith.arithmetic import (
@@ -29,28 +26,7 @@ class ArithmeticOperationKind(StrEnum):
29
26
  class ArithmeticOperation(QuantumAssignmentOperation):
30
27
  kind: Literal["ArithmeticOperation"]
31
28
 
32
- inplace_result: Optional[bool] = pydantic.Field(
33
- description="Determines whether the result variable is initialized",
34
- default=None,
35
- exclude=True,
36
- )
37
-
38
- operation_kind: ArithmeticOperationKind = pydantic.Field(
39
- default=None, validate_default=True
40
- )
41
-
42
- @pydantic.field_validator("operation_kind", mode="before")
43
- @classmethod
44
- def _propagate_inplace_result(
45
- cls, operation_kind: Optional[ArithmeticOperationKind], info: ValidationInfo
46
- ) -> ArithmeticOperationKind:
47
- if operation_kind is None:
48
- operation_kind = (
49
- ArithmeticOperationKind.InplaceXor
50
- if info.data["inplace_result"]
51
- else ArithmeticOperationKind.Assignment
52
- )
53
- return operation_kind
29
+ operation_kind: ArithmeticOperationKind
54
30
 
55
31
  @property
56
32
  def is_inplace(self) -> bool:
@@ -75,6 +75,9 @@ class QuantumOperation(QuantumStatement):
75
75
  def set_generative_block(self, block_name: str, py_callable: Callable) -> None:
76
76
  self._generative_blocks[block_name] = py_callable
77
77
 
78
+ def remove_generative_block(self, block_name: str) -> None:
79
+ self._generative_blocks.pop(block_name)
80
+
78
81
  def get_generative_block(self, block_name: str) -> Callable:
79
82
  return self._generative_blocks[block_name]
80
83
 
@@ -83,3 +86,6 @@ class QuantumOperation(QuantumStatement):
83
86
 
84
87
  def is_generative(self) -> bool:
85
88
  return len(self._generative_blocks) > 0
89
+
90
+ def clear_generative_blocks(self) -> None:
91
+ self._generative_blocks.clear()
@@ -1,11 +1,13 @@
1
1
  import re
2
2
 
3
3
  from classiq.interface.generator.compiler_keywords import CAPTURE_SUFFIX
4
+ from classiq.interface.model.handle_binding import HANDLE_ID_SEPARATOR, HandleBinding
4
5
 
5
6
  IDENTIFIER_PATTERN = r"[a-zA-Z_][a-zA-Z0-9_]*"
6
7
  CAPTURE_PATTERN = re.compile(
7
8
  rf"({IDENTIFIER_PATTERN}){CAPTURE_SUFFIX}{IDENTIFIER_PATTERN}__"
8
9
  )
10
+ ARRAY_CAST_SUFFIX = HANDLE_ID_SEPARATOR + "array_cast"
9
11
 
10
12
 
11
13
  def mangle_captured_var_name(var_name: str, defining_function: str) -> str:
@@ -15,3 +17,23 @@ def mangle_captured_var_name(var_name: str, defining_function: str) -> str:
15
17
  def demangle_name(name: str) -> str:
16
18
  match = re.match(CAPTURE_PATTERN, name)
17
19
  return match.group(1) if match else name
20
+
21
+
22
+ def demangle_handle(handle: HandleBinding) -> HandleBinding:
23
+ name = handle.name
24
+ if HANDLE_ID_SEPARATOR not in name:
25
+ return handle
26
+ if ARRAY_CAST_SUFFIX in name:
27
+ return HandleBinding(name=name.split(ARRAY_CAST_SUFFIX)[0])
28
+ name = re.sub(r"_\d+$", "", name)
29
+ name_parts = name.split(HANDLE_ID_SEPARATOR)
30
+ new_name = name_parts[0]
31
+ for part in name_parts[1:]:
32
+ if re.fullmatch(r"\d+", part):
33
+ new_name += f"[{part}]"
34
+ elif re.fullmatch(r"\d+_\d+", part):
35
+ part_left, part_right = part.split("_")
36
+ new_name += f"[{part_left}:{part_right}]"
37
+ else:
38
+ new_name += f".{part}"
39
+ return handle.rename(new_name)
@@ -9,12 +9,15 @@ from classiq.interface.exceptions import (
9
9
  from classiq.interface.generator.functions.port_declaration import (
10
10
  PortDeclarationDirection,
11
11
  )
12
- from classiq.interface.model.handle_binding import HANDLE_ID_SEPARATOR, HandleBinding
12
+ from classiq.interface.model.handle_binding import HandleBinding
13
13
  from classiq.interface.model.port_declaration import PortDeclaration
14
14
  from classiq.interface.model.quantum_function_call import ArgValue
15
15
  from classiq.interface.model.quantum_statement import QuantumOperation
16
16
 
17
- from classiq.model_expansions.capturing.mangling_utils import mangle_captured_var_name
17
+ from classiq.model_expansions.capturing.mangling_utils import (
18
+ demangle_handle,
19
+ mangle_captured_var_name,
20
+ )
18
21
  from classiq.model_expansions.closure import FunctionClosure, GenerativeFunctionClosure
19
22
  from classiq.model_expansions.function_builder import OperationBuilder
20
23
  from classiq.model_expansions.scope import QuantumSymbol, Scope
@@ -25,9 +28,12 @@ class PropagatedVariable:
25
28
  symbol: QuantumSymbol
26
29
  direction: PortDeclarationDirection
27
30
  defining_function: str
31
+ handle: HandleBinding
28
32
 
29
33
  @property
30
34
  def name(self) -> str:
35
+ name = self.symbol.handle.name
36
+ assert name == self.handle.name
31
37
  return self.symbol.handle.name
32
38
 
33
39
 
@@ -99,21 +105,22 @@ class PropagatedVarStack:
99
105
  direction: PortDeclarationDirection,
100
106
  ) -> dict[PropagatedVariable, None]:
101
107
  return {
102
- self._get_captured_var_with_direction(var.name, direction): None
108
+ self._get_captured_var_with_direction(var, direction): None
103
109
  for var in variables
104
110
  if self._is_captured(var.name)
105
111
  }
106
112
 
107
113
  def _get_captured_var_with_direction(
108
- self, var_name: str, direction: PortDeclarationDirection
114
+ self, var_handle: HandleBinding, direction: PortDeclarationDirection
109
115
  ) -> PropagatedVariable:
110
- defining_function = self._current_scope[var_name].defining_function
116
+ defining_function = self._current_scope[var_handle.name].defining_function
111
117
  if defining_function is None:
112
118
  raise ClassiqInternalExpansionError
113
119
  return PropagatedVariable(
114
- symbol=self._current_scope[var_name].as_type(QuantumSymbol),
120
+ symbol=self._current_scope[var_handle.name].as_type(QuantumSymbol),
115
121
  direction=direction,
116
122
  defining_function=defining_function.name,
123
+ handle=var_handle,
117
124
  )
118
125
 
119
126
  def _is_captured(self, var_name: str) -> bool:
@@ -133,15 +140,16 @@ class PropagatedVarStack:
133
140
  for var in self._stack[-1]
134
141
  )
135
142
 
136
- def get_propagated_variables(self) -> list[HandleBinding]:
137
- propagated_var_names: list[str] = [
138
- self._get_propagated_var_name(var) for var in self._stack[-1]
139
- ]
140
- return [
141
- HandleBinding(name=name) for name in dict.fromkeys(propagated_var_names)
142
- ]
143
+ def get_propagated_variables(self, flatten: bool) -> list[HandleBinding]:
144
+ return list(
145
+ dict.fromkeys(
146
+ [self._get_propagated_handle(var, flatten) for var in self._stack[-1]]
147
+ )
148
+ )
143
149
 
144
- def _get_propagated_var_name(self, var: PropagatedVariable) -> str:
150
+ def _get_propagated_handle(
151
+ self, var: PropagatedVariable, flatten: bool
152
+ ) -> HandleBinding:
145
153
  if (
146
154
  var.defining_function == self._builder.current_function.name
147
155
  or not isinstance(
@@ -155,7 +163,9 @@ class PropagatedVarStack:
155
163
  else:
156
164
  handle_name = mangle_captured_var_name(var.name, var.defining_function)
157
165
  self._to_mangle[var] = handle_name
158
- return handle_name
166
+ if flatten:
167
+ return HandleBinding(name=handle_name)
168
+ return var.handle.rename(handle_name)
159
169
 
160
170
  def _no_name_conflict(self, var: PropagatedVariable) -> bool:
161
171
  return var.name not in self._builder.current_function.colliding_variables
@@ -166,18 +176,19 @@ def validate_args_are_not_propagated(
166
176
  ) -> None:
167
177
  if not captured_vars:
168
178
  return
169
- captured_var_names = {var.name for var in captured_vars}
170
- arg_names = {
171
- demangle_suffixes(arg.name) for arg in args if isinstance(arg, HandleBinding)
179
+ captured_handles = {demangle_handle(handle) for handle in captured_vars}
180
+ arg_handles = {
181
+ demangle_handle(arg) for arg in args if isinstance(arg, HandleBinding)
172
182
  }
173
- if not captured_var_names.isdisjoint(arg_names):
174
- vars_msg = f"Explicitly passed variables: {arg_names}, captured variables: {captured_var_names}"
183
+ if any(
184
+ arg_handle.overlaps(captured_handle)
185
+ for arg_handle in arg_handles
186
+ for captured_handle in captured_handles
187
+ ):
188
+ captured_handles_str = {str(handle) for handle in captured_handles}
189
+ arg_handles_str = {str(handle) for handle in arg_handles}
190
+ vars_msg = f"Explicitly passed variables: {arg_handles_str}, captured variables: {captured_handles_str}"
175
191
  raise ClassiqExpansionError(
176
192
  f"Cannot capture variables that are explicitly passed as arguments. "
177
193
  f"{vars_msg}"
178
194
  )
179
-
180
-
181
- # TODO this is not a good long-term solution
182
- def demangle_suffixes(name: str) -> str:
183
- return name.split(HANDLE_ID_SEPARATOR)[0]
@@ -1,14 +1,21 @@
1
+ import json
2
+ import uuid
1
3
  from collections import defaultdict
2
- from collections.abc import Sequence
4
+ from collections.abc import Collection, Sequence
3
5
  from dataclasses import dataclass, field
4
- from functools import cached_property
6
+ from functools import cached_property, singledispatch
7
+ from symtable import Symbol
5
8
  from typing import Any, Optional, Union
6
9
 
7
10
  from typing_extensions import Self
8
11
 
9
- from classiq.interface.exceptions import ClassiqInternalExpansionError
12
+ from classiq.interface.exceptions import (
13
+ ClassiqInternalExpansionError,
14
+ )
15
+ from classiq.interface.generator.functions.builtins.internal_operators import (
16
+ All_BUILTINS_OPERATORS,
17
+ )
10
18
  from classiq.interface.generator.visitor import Visitor
11
- from classiq.interface.model.native_function_definition import FunctionSynthesisData
12
19
  from classiq.interface.model.port_declaration import PortDeclaration
13
20
  from classiq.interface.model.quantum_function_call import QuantumFunctionCall
14
21
  from classiq.interface.model.quantum_function_declaration import (
@@ -20,8 +27,14 @@ from classiq.interface.model.variable_declaration_statement import (
20
27
  VariableDeclarationStatement,
21
28
  )
22
29
 
30
+ from classiq import ClassicalParameterDeclaration
23
31
  from classiq.model_expansions.expression_renamer import ExpressionRenamer
24
- from classiq.model_expansions.scope import Scope
32
+ from classiq.model_expansions.scope import (
33
+ Evaluated,
34
+ QuantumSymbol,
35
+ Scope,
36
+ evaluated_to_str as evaluated_classical_param_to_str,
37
+ )
25
38
  from classiq.qmod.builtins.functions import permute
26
39
  from classiq.qmod.quantum_function import GenerativeQFunc
27
40
 
@@ -52,7 +65,21 @@ class FunctionClosure(Closure):
52
65
  is_lambda: bool = False
53
66
  is_atomic: bool = False
54
67
  signature_scope: Scope = field(default_factory=Scope)
55
- synthesis_data: FunctionSynthesisData = field(default_factory=FunctionSynthesisData)
68
+
69
+ # creates a unique id for the function closure based on the arguments values.
70
+ # The closure is changing across the interpreter flow so it's closure_id may change
71
+ @property
72
+ def closure_id(self) -> str:
73
+ # builtins operators have side effects, so generate a unique id for than
74
+ # may create a bugs. Therefore, with each call to closure_id a new id is
75
+ # created
76
+ if self.is_lambda or self.name in All_BUILTINS_OPERATORS:
77
+ signature = str(uuid.uuid4())
78
+ else:
79
+ signature = _generate_closure_id(
80
+ self.positional_arg_declarations, self.scope.data.values()
81
+ )
82
+ return f"{self.name}__{signature}"
56
83
 
57
84
  @property
58
85
  def body(self) -> Sequence[QuantumStatement]:
@@ -77,7 +104,6 @@ class FunctionClosure(Closure):
77
104
  expr_renamer: Optional[ExpressionRenamer] = None,
78
105
  is_lambda: bool = False,
79
106
  is_atomic: bool = False,
80
- synthesis_data: Optional[FunctionSynthesisData] = None,
81
107
  **kwargs: Any,
82
108
  ) -> Self:
83
109
  if expr_renamer:
@@ -90,9 +116,6 @@ class FunctionClosure(Closure):
90
116
  body = expr_renamer.visit(body)
91
117
 
92
118
  blocks = {"body": body} if body is not None else {}
93
- synthesis_data = (
94
- synthesis_data if synthesis_data is not None else FunctionSynthesisData()
95
- )
96
119
  return cls(
97
120
  name,
98
121
  blocks,
@@ -100,7 +123,6 @@ class FunctionClosure(Closure):
100
123
  positional_arg_declarations,
101
124
  is_lambda,
102
125
  is_atomic,
103
- synthesis_data=synthesis_data,
104
126
  **kwargs,
105
127
  )
106
128
 
@@ -108,8 +130,10 @@ class FunctionClosure(Closure):
108
130
  self, declaration: NamedParamsQuantumFunctionDeclaration
109
131
  ) -> Self:
110
132
  fields: dict = self.__dict__ | {
111
- "positional_arg_declarations": declaration.positional_arg_declarations
133
+ "name": declaration.name,
134
+ "positional_arg_declarations": declaration.positional_arg_declarations,
112
135
  }
136
+ fields.pop("colliding_variables", 0)
113
137
  return type(self)(**fields)
114
138
 
115
139
 
@@ -171,3 +195,44 @@ class VariableCollector(Visitor):
171
195
  defining_function = lambda_environment[var].defining_function
172
196
  if defining_function is not None:
173
197
  self._variables[var].add(defining_function.name)
198
+
199
+
200
+ def _generate_closure_id(
201
+ args_declaration: Sequence[PositionalArg], evaluated_args: Collection[Evaluated]
202
+ ) -> str:
203
+ args_signature: dict = {}
204
+ for arg_declara, eval_arg in zip(args_declaration, evaluated_args):
205
+ args_signature |= _generate_arg_id(arg_declara, eval_arg)
206
+ return json.dumps(args_signature)
207
+
208
+
209
+ def _generate_arg_id(
210
+ arg_declaration: PositionalArg, evaluated_arg: Evaluated
211
+ ) -> dict[str, str]:
212
+ arg_value = evaluated_arg.value
213
+ arg_name = arg_declaration.name
214
+ if isinstance(arg_declaration, ClassicalParameterDeclaration):
215
+ return {arg_name: evaluated_classical_param_to_str(arg_value)}
216
+ return {arg_name: _evaluated_arg_to_str(arg_value)}
217
+
218
+
219
+ @singledispatch
220
+ def _evaluated_arg_to_str(arg: Any) -> str:
221
+ if isinstance(arg, str):
222
+ return arg
223
+ return str(uuid.uuid4())
224
+
225
+
226
+ @_evaluated_arg_to_str.register
227
+ def _evaluated_quantum_symbol_to_str(port: QuantumSymbol) -> str:
228
+ return port.quantum_type.model_dump_json(exclude_none=True, exclude={"name"})
229
+
230
+
231
+ @_evaluated_arg_to_str.register
232
+ def _evaluated_symbol_to_str(port: Symbol) -> str:
233
+ return repr(port)
234
+
235
+
236
+ @_evaluated_arg_to_str.register
237
+ def _evaluated_operand_to_str(operand: FunctionClosure) -> str:
238
+ return operand.closure_id