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.
- classiq/interface/_version.py +1 -1
- classiq/interface/debug_info/debug_info.py +11 -0
- classiq/interface/executor/result.py +0 -3
- classiq/interface/generator/functions/builtins/internal_operators.py +9 -1
- classiq/interface/generator/generated_circuit_data.py +0 -1
- classiq/interface/generator/model/preferences/preferences.py +4 -0
- classiq/interface/generator/types/compilation_metadata.py +5 -0
- classiq/interface/generator/visitor.py +13 -1
- classiq/interface/ide/visual_model.py +5 -1
- classiq/interface/interface_version.py +1 -1
- classiq/interface/model/control.py +22 -1
- classiq/interface/model/handle_binding.py +28 -0
- classiq/interface/model/model.py +4 -0
- classiq/interface/model/native_function_definition.py +1 -1
- classiq/interface/model/quantum_expressions/arithmetic_operation.py +2 -26
- classiq/interface/model/quantum_statement.py +6 -0
- classiq/model_expansions/capturing/mangling_utils.py +22 -0
- classiq/model_expansions/capturing/propagated_var_stack.py +36 -25
- classiq/model_expansions/closure.py +77 -12
- classiq/model_expansions/function_builder.py +9 -10
- classiq/model_expansions/generative_functions.py +2 -2
- classiq/model_expansions/interpreter.py +29 -26
- classiq/model_expansions/quantum_operations/control.py +114 -29
- classiq/model_expansions/quantum_operations/emitter.py +37 -11
- classiq/model_expansions/quantum_operations/expression_operation.py +80 -18
- classiq/model_expansions/quantum_operations/power.py +5 -0
- classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +7 -37
- classiq/model_expansions/quantum_operations/quantum_function_call.py +2 -32
- classiq/model_expansions/quantum_operations/repeat.py +5 -0
- classiq/model_expansions/quantum_operations/within_apply.py +0 -16
- classiq/model_expansions/scope_initialization.py +2 -3
- classiq/qmod/builtins/functions/arithmetic.py +0 -2
- classiq/qmod/builtins/functions/discrete_sine_cosine_transform.py +0 -12
- classiq/qmod/builtins/functions/exponentiation.py +0 -6
- classiq/qmod/builtins/functions/grover.py +0 -17
- classiq/qmod/builtins/functions/linear_pauli_rotation.py +0 -5
- classiq/qmod/builtins/functions/modular_exponentiation.py +0 -3
- classiq/qmod/builtins/functions/qaoa_penalty.py +0 -8
- classiq/qmod/builtins/functions/qft_functions.py +0 -3
- classiq/qmod/builtins/functions/qpe.py +0 -6
- classiq/qmod/builtins/functions/qsvt.py +0 -12
- classiq/qmod/builtins/functions/standard_gates.py +0 -88
- classiq/qmod/builtins/functions/state_preparation.py +7 -15
- classiq/qmod/builtins/functions/swap_test.py +0 -3
- classiq/qmod/builtins/operations.py +152 -17
- classiq/qmod/create_model_function.py +10 -12
- classiq/qmod/model_state_container.py +5 -1
- classiq/qmod/native/pretty_printer.py +6 -1
- classiq/qmod/pretty_print/pretty_printer.py +25 -11
- classiq/qmod/qmod_constant.py +31 -3
- classiq/qmod/quantum_function.py +25 -19
- classiq/qmod/synthesize_separately.py +1 -2
- {classiq-0.54.0.dist-info → classiq-0.56.0.dist-info}/METADATA +2 -3
- {classiq-0.54.0.dist-info → classiq-0.56.0.dist-info}/RECORD +55 -55
- classiq/model_expansions/call_to_model_converter.py +0 -190
- {classiq-0.54.0.dist-info → classiq-0.56.0.dist-info}/WHEEL +0 -0
classiq/interface/_version.py
CHANGED
@@ -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). ",
|
@@ -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:
|
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 = "
|
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
|
classiq/interface/model/model.py
CHANGED
@@ -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
|
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
|
-
|
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
|
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
|
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
|
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,
|
114
|
+
self, var_handle: HandleBinding, direction: PortDeclarationDirection
|
109
115
|
) -> PropagatedVariable:
|
110
|
-
defining_function = self._current_scope[
|
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[
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
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
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
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
|
174
|
-
|
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
|
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
|
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
|
-
|
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
|
-
"
|
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
|