classiq 0.58.0__py3-none-any.whl → 0.59.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/_internals/api_wrapper.py +8 -3
- classiq/_internals/jobs.py +3 -5
- classiq/execution/execution_session.py +36 -20
- classiq/executor.py +2 -1
- classiq/interface/_version.py +1 -1
- classiq/interface/generator/arith/arithmetic_operations.py +1 -0
- classiq/interface/generator/register_role.py +8 -0
- classiq/interface/model/handle_binding.py +22 -3
- classiq/model_expansions/capturing/captured_vars.py +316 -0
- classiq/model_expansions/capturing/mangling_utils.py +18 -9
- classiq/model_expansions/closure.py +29 -74
- classiq/model_expansions/function_builder.py +51 -66
- classiq/model_expansions/interpreter.py +4 -7
- classiq/model_expansions/quantum_operations/bind.py +1 -3
- classiq/model_expansions/quantum_operations/call_emitter.py +46 -11
- classiq/model_expansions/quantum_operations/classicalif.py +2 -5
- classiq/model_expansions/quantum_operations/control.py +13 -16
- classiq/model_expansions/quantum_operations/emitter.py +36 -8
- classiq/model_expansions/quantum_operations/expression_operation.py +9 -19
- classiq/model_expansions/quantum_operations/inplace_binary_operation.py +4 -6
- classiq/model_expansions/quantum_operations/invert.py +5 -8
- classiq/model_expansions/quantum_operations/power.py +5 -10
- classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +1 -3
- classiq/model_expansions/quantum_operations/quantum_function_call.py +1 -3
- classiq/model_expansions/quantum_operations/repeat.py +3 -3
- classiq/model_expansions/quantum_operations/variable_decleration.py +1 -1
- classiq/model_expansions/quantum_operations/within_apply.py +1 -5
- classiq/model_expansions/scope.py +2 -2
- classiq/model_expansions/transformers/var_splitter.py +32 -19
- classiq/model_expansions/utils/handles_collector.py +33 -0
- classiq/model_expansions/visitors/variable_references.py +18 -2
- classiq/qmod/qfunc.py +9 -13
- classiq/qmod/quantum_expandable.py +1 -21
- classiq/qmod/quantum_function.py +16 -0
- {classiq-0.58.0.dist-info → classiq-0.59.0.dist-info}/METADATA +2 -2
- {classiq-0.58.0.dist-info → classiq-0.59.0.dist-info}/RECORD +37 -38
- classiq/interface/executor/aws_execution_cost.py +0 -90
- classiq/model_expansions/capturing/captured_var_manager.py +0 -48
- classiq/model_expansions/capturing/propagated_var_stack.py +0 -194
- {classiq-0.58.0.dist-info → classiq-0.59.0.dist-info}/WHEEL +0 -0
@@ -113,14 +113,19 @@ class ApiWrapper:
|
|
113
113
|
return _parse_job_response(result, generator_result.QuantumProgram)
|
114
114
|
|
115
115
|
@classmethod
|
116
|
-
async def
|
116
|
+
async def call_convert_quantum_program(
|
117
117
|
cls, circuit: generator_result.QuantumProgram
|
118
|
-
) ->
|
119
|
-
|
118
|
+
) -> dict:
|
119
|
+
return await cls._call_task_pydantic(
|
120
120
|
http_method=HTTPMethod.POST,
|
121
121
|
url=routes.CONVERSION_GENERATED_CIRCUIT_TO_EXECUTION_INPUT_FULL,
|
122
122
|
model=circuit,
|
123
123
|
)
|
124
|
+
|
125
|
+
@classmethod
|
126
|
+
async def call_execute_execution_input(
|
127
|
+
cls, execution_input: dict
|
128
|
+
) -> execution_request.ExecutionJobDetails:
|
124
129
|
headers = {
|
125
130
|
_ACCEPT_HEADER: "v1",
|
126
131
|
_CONTENT_TYPE_HEADER: execution_input["version"],
|
classiq/_internals/jobs.py
CHANGED
@@ -45,9 +45,9 @@ def _general_job_description_parser(
|
|
45
45
|
|
46
46
|
|
47
47
|
class JobPoller:
|
48
|
-
INITIAL_INTERVAL_SEC = 1
|
49
|
-
INTERVAL_FACTOR =
|
50
|
-
FINAL_INTERVAL_SEC =
|
48
|
+
INITIAL_INTERVAL_SEC = 0.1
|
49
|
+
INTERVAL_FACTOR = 1.5
|
50
|
+
FINAL_INTERVAL_SEC = 25
|
51
51
|
DEV_INTERVAL = 0.05
|
52
52
|
|
53
53
|
def __init__(
|
@@ -107,8 +107,6 @@ class JobPoller:
|
|
107
107
|
while True:
|
108
108
|
yield self.DEV_INTERVAL
|
109
109
|
else:
|
110
|
-
for _ in range(10):
|
111
|
-
yield self.INITIAL_INTERVAL_SEC
|
112
110
|
interval = self.INITIAL_INTERVAL_SEC
|
113
111
|
while True:
|
114
112
|
yield interval
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import json
|
2
|
+
from functools import cached_property
|
2
3
|
from typing import Any, Callable, Optional, Union, cast
|
3
4
|
|
4
5
|
import numpy as np
|
@@ -15,12 +16,13 @@ from classiq.interface.executor.result import (
|
|
15
16
|
from classiq.interface.generator.functions.qmod_python_interface import QmodPyStruct
|
16
17
|
from classiq.interface.generator.quantum_program import QuantumProgram
|
17
18
|
|
19
|
+
from classiq._internals import async_utils
|
20
|
+
from classiq._internals.api_wrapper import ApiWrapper
|
18
21
|
from classiq.applications.combinatorial_helpers.pauli_helpers.pauli_utils import (
|
19
22
|
_pauli_dict_to_pauli_terms,
|
20
23
|
_pauli_terms_to_qmod,
|
21
24
|
)
|
22
25
|
from classiq.execution.jobs import ExecutionJob
|
23
|
-
from classiq.executor import execute
|
24
26
|
from classiq.qmod.builtins import PauliTerm
|
25
27
|
from classiq.qmod.builtins.classical_execution_primitives import (
|
26
28
|
CARRAY_SEPARATOR,
|
@@ -158,14 +160,28 @@ class ExecutionSession:
|
|
158
160
|
):
|
159
161
|
self.program: QuantumProgram = _deserialize_program(quantum_program)
|
160
162
|
self.update_execution_preferences(execution_preferences)
|
163
|
+
# When the primitives are called, we always override the
|
164
|
+
# classical_execution_code, and we don't want the conversion route to fail
|
165
|
+
# because cmain is expected in some cases
|
166
|
+
self.program.model.classical_execution_code = "dummy"
|
161
167
|
|
162
|
-
@
|
163
|
-
def
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
168
|
+
@cached_property
|
169
|
+
def _execution_input(self) -> dict:
|
170
|
+
return async_utils.run(ApiWrapper.call_convert_quantum_program(self.program))
|
171
|
+
|
172
|
+
def _execute(
|
173
|
+
self, classical_execution_code: str, primitives_input: PrimitivesInput
|
174
|
+
) -> ExecutionJob:
|
175
|
+
execution_input = self._execution_input.copy()
|
176
|
+
execution_input["classical_execution_code"] = classical_execution_code
|
177
|
+
# The use of `model_dump_json` is necessary for complex numbers serialization
|
178
|
+
execution_input["primitives_input"] = json.loads(
|
179
|
+
primitives_input.model_dump_json()
|
180
|
+
)
|
181
|
+
result = async_utils.run(
|
182
|
+
ApiWrapper.call_execute_execution_input(execution_input)
|
183
|
+
)
|
184
|
+
return ExecutionJob(details=result)
|
169
185
|
|
170
186
|
def update_execution_preferences(
|
171
187
|
self, execution_preferences: Optional[ExecutionPreferences]
|
@@ -210,13 +226,13 @@ class ExecutionSession:
|
|
210
226
|
Returns:
|
211
227
|
The execution job.
|
212
228
|
"""
|
213
|
-
|
229
|
+
classical_execution_code = generate_code_snippet(
|
214
230
|
SupportedPrimitives.SAMPLE, parameters=format_parameters(parameters)
|
215
231
|
)
|
216
|
-
|
232
|
+
execution_primitives_input = PrimitivesInput(
|
217
233
|
sample=[parse_params(parameters)] if parameters is not None else [{}]
|
218
234
|
)
|
219
|
-
return
|
235
|
+
return self._execute(classical_execution_code, execution_primitives_input)
|
220
236
|
|
221
237
|
def batch_sample(self, parameters: list[ExecutionParams]) -> list[ExecutionDetails]:
|
222
238
|
"""
|
@@ -244,13 +260,13 @@ class ExecutionSession:
|
|
244
260
|
Returns:
|
245
261
|
The execution job.
|
246
262
|
"""
|
247
|
-
|
263
|
+
classical_execution_code = generate_code_snippet(
|
248
264
|
SupportedPrimitives.BATCH_SAMPLE, parameters=format_parameters(parameters)
|
249
265
|
)
|
250
|
-
|
266
|
+
execution_primitives_input = PrimitivesInput(
|
251
267
|
sample=[parse_params(params) for params in parameters]
|
252
268
|
)
|
253
|
-
return
|
269
|
+
return self._execute(classical_execution_code, execution_primitives_input)
|
254
270
|
|
255
271
|
def estimate(
|
256
272
|
self, hamiltonian: Hamiltonian, parameters: Optional[ExecutionParams] = None
|
@@ -284,12 +300,12 @@ class ExecutionSession:
|
|
284
300
|
Returns:
|
285
301
|
The execution job.
|
286
302
|
"""
|
287
|
-
|
303
|
+
classical_execution_code = generate_code_snippet(
|
288
304
|
SupportedPrimitives.ESTIMATE,
|
289
305
|
parameters=format_parameters(parameters),
|
290
306
|
hamiltonian=to_hamiltonian_str(hamiltonian),
|
291
307
|
)
|
292
|
-
|
308
|
+
execution_primitives_input = PrimitivesInput(
|
293
309
|
estimate=EstimateInput(
|
294
310
|
hamiltonian=_hamiltonian_to_pauli_operator(hamiltonian),
|
295
311
|
parameters=(
|
@@ -297,7 +313,7 @@ class ExecutionSession:
|
|
297
313
|
),
|
298
314
|
)
|
299
315
|
)
|
300
|
-
return
|
316
|
+
return self._execute(classical_execution_code, execution_primitives_input)
|
301
317
|
|
302
318
|
def batch_estimate(
|
303
319
|
self, hamiltonian: Hamiltonian, parameters: list[ExecutionParams]
|
@@ -331,18 +347,18 @@ class ExecutionSession:
|
|
331
347
|
Returns:
|
332
348
|
The execution job.
|
333
349
|
"""
|
334
|
-
|
350
|
+
classical_execution_code = generate_code_snippet(
|
335
351
|
SupportedPrimitives.BATCH_ESTIMATE,
|
336
352
|
parameters=format_parameters(parameters),
|
337
353
|
hamiltonian=to_hamiltonian_str(hamiltonian),
|
338
354
|
)
|
339
|
-
|
355
|
+
execution_primitives_input = PrimitivesInput(
|
340
356
|
estimate=EstimateInput(
|
341
357
|
hamiltonian=_hamiltonian_to_pauli_operator(hamiltonian),
|
342
358
|
parameters=[parse_params(params) for params in parameters],
|
343
359
|
)
|
344
360
|
)
|
345
|
-
return
|
361
|
+
return self._execute(classical_execution_code, execution_primitives_input)
|
346
362
|
|
347
363
|
def estimate_cost(
|
348
364
|
self,
|
classiq/executor.py
CHANGED
@@ -32,7 +32,8 @@ def _parse_serialized_qprog(
|
|
32
32
|
|
33
33
|
async def execute_async(quantum_program: SerializedQuantumProgram) -> ExecutionJob:
|
34
34
|
circuit = _parse_serialized_qprog(quantum_program)
|
35
|
-
|
35
|
+
execution_input = await ApiWrapper.call_convert_quantum_program(circuit)
|
36
|
+
result = await ApiWrapper.call_execute_execution_input(execution_input)
|
36
37
|
return ExecutionJob(details=result)
|
37
38
|
|
38
39
|
|
classiq/interface/_version.py
CHANGED
@@ -10,6 +10,7 @@ from classiq.interface.generator.arith.machine_precision import (
|
|
10
10
|
from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
|
11
11
|
from classiq.interface.generator.function_params import FunctionParams
|
12
12
|
|
13
|
+
IMPLICIT_OUTPUTS: Final[str] = "implicit_outputs"
|
13
14
|
DEFAULT_GARBAGE_OUT_NAME: Final[str] = "extra_qubits"
|
14
15
|
MODULO_WITH_FRACTION_PLACES_ERROR_MSG: Final[str] = (
|
15
16
|
"Modulo with fraction places not supported"
|
@@ -29,3 +29,11 @@ class RegisterRole(StrEnum):
|
|
29
29
|
RegisterRole.AUXILIARY,
|
30
30
|
RegisterRole.EXPLICIT_ZERO_INPUT,
|
31
31
|
}
|
32
|
+
|
33
|
+
@staticmethod
|
34
|
+
def clean_output_roles() -> set["RegisterRole"]:
|
35
|
+
return {RegisterRole.ZERO_OUTPUT, RegisterRole.AUXILIARY}
|
36
|
+
|
37
|
+
@staticmethod
|
38
|
+
def dirty_output_roles() -> set["RegisterRole"]:
|
39
|
+
return {RegisterRole.OUTPUT, RegisterRole.GARBAGE_OUTPUT}
|
@@ -19,6 +19,10 @@ class HandleBinding(ASTNode):
|
|
19
19
|
def __str__(self) -> str:
|
20
20
|
return self.name
|
21
21
|
|
22
|
+
@property
|
23
|
+
def qmod_expr(self) -> str:
|
24
|
+
return self.name
|
25
|
+
|
22
26
|
def is_bindable(self) -> bool:
|
23
27
|
return True
|
24
28
|
|
@@ -57,6 +61,9 @@ class HandleBinding(ASTNode):
|
|
57
61
|
return replacement
|
58
62
|
return self
|
59
63
|
|
64
|
+
def __contains__(self, other_handle: "HandleBinding") -> bool:
|
65
|
+
return self.collapse() in other_handle.collapse().prefixes()
|
66
|
+
|
60
67
|
|
61
68
|
class NestedHandleBinding(HandleBinding):
|
62
69
|
base_handle: "ConcreteHandleBinding"
|
@@ -66,8 +73,8 @@ class NestedHandleBinding(HandleBinding):
|
|
66
73
|
def _set_name(cls, values: Any) -> dict[str, Any]:
|
67
74
|
if isinstance(values, dict):
|
68
75
|
orig = values
|
69
|
-
while "base_handle" in values:
|
70
|
-
values = values["base_handle"]
|
76
|
+
while "base_handle" in dict(values):
|
77
|
+
values = dict(values)["base_handle"]
|
71
78
|
orig["name"] = dict(values).get("name")
|
72
79
|
return orig
|
73
80
|
if isinstance(values, NestedHandleBinding):
|
@@ -106,6 +113,10 @@ class SubscriptHandleBinding(NestedHandleBinding):
|
|
106
113
|
def __str__(self) -> str:
|
107
114
|
return f"{self.base_handle}[{self.index}]"
|
108
115
|
|
116
|
+
@property
|
117
|
+
def qmod_expr(self) -> str:
|
118
|
+
return f"{self.base_handle.qmod_expr}[{self.index}]"
|
119
|
+
|
109
120
|
@property
|
110
121
|
def identifier(self) -> str:
|
111
122
|
return f"{self.base_handle.identifier}{HANDLE_ID_SEPARATOR}{self.index}"
|
@@ -157,6 +168,10 @@ class SlicedHandleBinding(NestedHandleBinding):
|
|
157
168
|
def __str__(self) -> str:
|
158
169
|
return f"{self.base_handle}[{self.start}:{self.end}]"
|
159
170
|
|
171
|
+
@property
|
172
|
+
def qmod_expr(self) -> str:
|
173
|
+
return f"{self.base_handle.qmod_expr}[{self.start}:{self.end}]"
|
174
|
+
|
160
175
|
@property
|
161
176
|
def identifier(self) -> str:
|
162
177
|
return (
|
@@ -165,7 +180,7 @@ class SlicedHandleBinding(NestedHandleBinding):
|
|
165
180
|
|
166
181
|
def collapse(self) -> HandleBinding:
|
167
182
|
if isinstance(self.base_handle, SlicedHandleBinding):
|
168
|
-
return
|
183
|
+
return SlicedHandleBinding(
|
169
184
|
base_handle=self.base_handle.base_handle,
|
170
185
|
start=self._get_collapsed_start(),
|
171
186
|
end=self._get_collapsed_stop(),
|
@@ -224,6 +239,10 @@ class FieldHandleBinding(NestedHandleBinding):
|
|
224
239
|
def __str__(self) -> str:
|
225
240
|
return f"{self.base_handle}.{self.field}"
|
226
241
|
|
242
|
+
@property
|
243
|
+
def qmod_expr(self) -> str:
|
244
|
+
return f"get_field({self.base_handle.qmod_expr}, '{self.field}')"
|
245
|
+
|
227
246
|
@property
|
228
247
|
def identifier(self) -> str:
|
229
248
|
return f"{self.base_handle.identifier}{HANDLE_ID_SEPARATOR}{self.field}"
|
@@ -0,0 +1,316 @@
|
|
1
|
+
import dataclasses
|
2
|
+
from collections.abc import Iterator, Sequence
|
3
|
+
from contextlib import contextmanager
|
4
|
+
from dataclasses import dataclass, field
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
|
7
|
+
from classiq.interface.enum_utils import StrEnum
|
8
|
+
from classiq.interface.exceptions import (
|
9
|
+
ClassiqExpansionError,
|
10
|
+
ClassiqInternalExpansionError,
|
11
|
+
)
|
12
|
+
from classiq.interface.generator.functions.port_declaration import (
|
13
|
+
PortDeclarationDirection,
|
14
|
+
)
|
15
|
+
from classiq.interface.model.handle_binding import HandleBinding, NestedHandleBinding
|
16
|
+
from classiq.interface.model.port_declaration import PortDeclaration
|
17
|
+
from classiq.interface.model.quantum_function_call import ArgValue
|
18
|
+
from classiq.interface.model.quantum_type import QuantumType
|
19
|
+
|
20
|
+
from classiq.model_expansions.capturing.mangling_utils import (
|
21
|
+
demangle_handle,
|
22
|
+
mangle_captured_var_name,
|
23
|
+
)
|
24
|
+
from classiq.model_expansions.scope import QuantumSymbol
|
25
|
+
from classiq.model_expansions.transformers.var_splitter import SymbolPart, SymbolParts
|
26
|
+
|
27
|
+
if TYPE_CHECKING:
|
28
|
+
from classiq.model_expansions.closure import FunctionClosure
|
29
|
+
|
30
|
+
|
31
|
+
class PortDirection(StrEnum):
|
32
|
+
Input = "input"
|
33
|
+
Inout = "inout"
|
34
|
+
Output = "output"
|
35
|
+
Outin = "outin"
|
36
|
+
|
37
|
+
def negate(self) -> "PortDirection":
|
38
|
+
if self == PortDirection.Input:
|
39
|
+
return PortDirection.Output
|
40
|
+
if self == PortDirection.Output:
|
41
|
+
return PortDirection.Input
|
42
|
+
return self
|
43
|
+
|
44
|
+
@staticmethod
|
45
|
+
def load(direction: PortDeclarationDirection) -> "PortDirection":
|
46
|
+
if direction == PortDeclarationDirection.Input:
|
47
|
+
return PortDirection.Input
|
48
|
+
if direction == PortDeclarationDirection.Output:
|
49
|
+
return PortDirection.Output
|
50
|
+
if direction == PortDeclarationDirection.Inout:
|
51
|
+
return PortDirection.Inout
|
52
|
+
raise ClassiqInternalExpansionError
|
53
|
+
|
54
|
+
def dump(self) -> PortDeclarationDirection:
|
55
|
+
if self == PortDirection.Input:
|
56
|
+
return PortDeclarationDirection.Input
|
57
|
+
if self == PortDirection.Output:
|
58
|
+
return PortDeclarationDirection.Output
|
59
|
+
if self == PortDirection.Inout:
|
60
|
+
return PortDeclarationDirection.Inout
|
61
|
+
raise ClassiqInternalExpansionError
|
62
|
+
|
63
|
+
|
64
|
+
@dataclass(frozen=True)
|
65
|
+
class _CapturedHandle:
|
66
|
+
handle: HandleBinding
|
67
|
+
quantum_type: QuantumType
|
68
|
+
defining_function: "FunctionClosure"
|
69
|
+
direction: PortDirection
|
70
|
+
is_propagated: bool
|
71
|
+
|
72
|
+
@property
|
73
|
+
def mangled_name(self) -> str:
|
74
|
+
return mangle_captured_var_name(self.handle.identifier, self.defining_function)
|
75
|
+
|
76
|
+
@property
|
77
|
+
def port(self) -> PortDeclaration:
|
78
|
+
return PortDeclaration(
|
79
|
+
name=self.mangled_name,
|
80
|
+
quantum_type=self.quantum_type,
|
81
|
+
direction=self.direction.dump(),
|
82
|
+
)
|
83
|
+
|
84
|
+
def is_same_var(self, other: "_CapturedHandle") -> bool:
|
85
|
+
return self.handle.name == other.handle.name and _same_closure(
|
86
|
+
self.defining_function, other.defining_function
|
87
|
+
)
|
88
|
+
|
89
|
+
def change_direction(self, new_direction: PortDirection) -> "_CapturedHandle":
|
90
|
+
return dataclasses.replace(self, direction=new_direction)
|
91
|
+
|
92
|
+
def set_propagated(self) -> "_CapturedHandle":
|
93
|
+
return dataclasses.replace(self, is_propagated=True)
|
94
|
+
|
95
|
+
def update_propagation(
|
96
|
+
self, other_captured_handle: "_CapturedHandle"
|
97
|
+
) -> "_CapturedHandle":
|
98
|
+
if self.is_propagated and not other_captured_handle.is_propagated:
|
99
|
+
return dataclasses.replace(self, is_propagated=False)
|
100
|
+
return self
|
101
|
+
|
102
|
+
|
103
|
+
@dataclass
|
104
|
+
class CapturedVars:
|
105
|
+
_captured_handles: list[_CapturedHandle] = field(default_factory=list)
|
106
|
+
|
107
|
+
def capture_handle(
|
108
|
+
self,
|
109
|
+
handle: HandleBinding,
|
110
|
+
quantum_type: QuantumType,
|
111
|
+
defining_function: "FunctionClosure",
|
112
|
+
direction: PortDeclarationDirection,
|
113
|
+
) -> None:
|
114
|
+
self._capture_handle(
|
115
|
+
_CapturedHandle(
|
116
|
+
handle=handle,
|
117
|
+
quantum_type=quantum_type,
|
118
|
+
defining_function=defining_function,
|
119
|
+
direction=PortDirection.load(direction),
|
120
|
+
is_propagated=False,
|
121
|
+
)
|
122
|
+
)
|
123
|
+
|
124
|
+
def _capture_handle(self, captured_handle: _CapturedHandle) -> None:
|
125
|
+
if (
|
126
|
+
isinstance(captured_handle.handle, NestedHandleBinding)
|
127
|
+
and captured_handle.direction != PortDirection.Inout
|
128
|
+
):
|
129
|
+
raise ClassiqInternalExpansionError("Captured nested handles must be inout")
|
130
|
+
|
131
|
+
new_captured_handles = []
|
132
|
+
for existing_captured_handle in self._captured_handles:
|
133
|
+
if not existing_captured_handle.is_same_var(captured_handle):
|
134
|
+
new_captured_handles.append(existing_captured_handle)
|
135
|
+
continue
|
136
|
+
captured_handle = captured_handle.update_propagation(
|
137
|
+
existing_captured_handle
|
138
|
+
)
|
139
|
+
if existing_captured_handle.handle == captured_handle.handle:
|
140
|
+
captured_handle = self._conjugate_direction(
|
141
|
+
existing_captured_handle, captured_handle
|
142
|
+
)
|
143
|
+
elif captured_handle.handle in existing_captured_handle.handle:
|
144
|
+
if existing_captured_handle.direction in (
|
145
|
+
PortDirection.Input,
|
146
|
+
PortDirection.Outin,
|
147
|
+
):
|
148
|
+
raise ClassiqInternalExpansionError(
|
149
|
+
"Captured handle is already freed"
|
150
|
+
)
|
151
|
+
captured_handle = existing_captured_handle
|
152
|
+
elif existing_captured_handle.handle in captured_handle.handle:
|
153
|
+
if captured_handle.direction in (
|
154
|
+
PortDirection.Output,
|
155
|
+
PortDirection.Outin,
|
156
|
+
):
|
157
|
+
raise ClassiqInternalExpansionError(
|
158
|
+
"Captured handle is already allocated"
|
159
|
+
)
|
160
|
+
else:
|
161
|
+
new_captured_handles.append(existing_captured_handle)
|
162
|
+
new_captured_handles.append(captured_handle)
|
163
|
+
self._captured_handles = new_captured_handles
|
164
|
+
|
165
|
+
def _conjugate_direction(
|
166
|
+
self,
|
167
|
+
existing_captured_handle: _CapturedHandle,
|
168
|
+
captured_handle: _CapturedHandle,
|
169
|
+
) -> _CapturedHandle:
|
170
|
+
if existing_captured_handle.direction == PortDirection.Input:
|
171
|
+
if captured_handle.direction == PortDirection.Output:
|
172
|
+
return captured_handle.change_direction(PortDirection.Inout)
|
173
|
+
if captured_handle.direction == PortDirection.Outin:
|
174
|
+
return captured_handle.change_direction(PortDirection.Input)
|
175
|
+
raise ClassiqInternalExpansionError("Captured handle is already freed")
|
176
|
+
if existing_captured_handle.direction == PortDirection.Output:
|
177
|
+
if captured_handle.direction == PortDirection.Input:
|
178
|
+
return captured_handle.change_direction(PortDirection.Outin)
|
179
|
+
if captured_handle.direction in (
|
180
|
+
PortDirection.Output,
|
181
|
+
PortDirection.Outin,
|
182
|
+
):
|
183
|
+
raise ClassiqInternalExpansionError(
|
184
|
+
"Captured handle is already allocated"
|
185
|
+
)
|
186
|
+
return captured_handle.change_direction(PortDirection.Output)
|
187
|
+
if existing_captured_handle.direction == PortDirection.Inout:
|
188
|
+
if captured_handle.direction in (
|
189
|
+
PortDirection.Output,
|
190
|
+
PortDirection.Outin,
|
191
|
+
):
|
192
|
+
raise ClassiqInternalExpansionError(
|
193
|
+
"Captured handle is already allocated"
|
194
|
+
)
|
195
|
+
elif captured_handle.direction in (
|
196
|
+
PortDirection.Input,
|
197
|
+
PortDirection.Inout,
|
198
|
+
):
|
199
|
+
raise ClassiqInternalExpansionError("Captured handle is already freed")
|
200
|
+
return captured_handle
|
201
|
+
|
202
|
+
def update(self, other_captured_vars: "CapturedVars") -> None:
|
203
|
+
for captured_handle in other_captured_vars._captured_handles:
|
204
|
+
self._capture_handle(captured_handle)
|
205
|
+
|
206
|
+
def negate(self) -> "CapturedVars":
|
207
|
+
return CapturedVars(
|
208
|
+
_captured_handles=[
|
209
|
+
captured_handle.change_direction(captured_handle.direction.negate())
|
210
|
+
for captured_handle in self._captured_handles
|
211
|
+
]
|
212
|
+
)
|
213
|
+
|
214
|
+
def filter(self, current_function: "FunctionClosure") -> "CapturedVars":
|
215
|
+
return CapturedVars(
|
216
|
+
_captured_handles=[
|
217
|
+
captured_handle
|
218
|
+
for captured_handle in self._captured_handles
|
219
|
+
if not _same_closure(
|
220
|
+
captured_handle.defining_function, current_function
|
221
|
+
)
|
222
|
+
]
|
223
|
+
)
|
224
|
+
|
225
|
+
def set_propagated(self) -> "CapturedVars":
|
226
|
+
return CapturedVars(
|
227
|
+
_captured_handles=[
|
228
|
+
captured_handle.set_propagated()
|
229
|
+
for captured_handle in self._captured_handles
|
230
|
+
]
|
231
|
+
)
|
232
|
+
|
233
|
+
def get_captured_ports(self) -> list[PortDeclaration]:
|
234
|
+
return [captured_handle.port for captured_handle in self._captured_handles]
|
235
|
+
|
236
|
+
def get_captured_args(
|
237
|
+
self, current_function: "FunctionClosure"
|
238
|
+
) -> list[HandleBinding]:
|
239
|
+
return [
|
240
|
+
(
|
241
|
+
captured_handle.handle
|
242
|
+
if _same_closure(current_function, captured_handle.defining_function)
|
243
|
+
else HandleBinding(name=captured_handle.mangled_name)
|
244
|
+
)
|
245
|
+
for captured_handle in self._captured_handles
|
246
|
+
]
|
247
|
+
|
248
|
+
def get_captured_mapping(self) -> SymbolParts:
|
249
|
+
return {
|
250
|
+
QuantumSymbol(
|
251
|
+
handle=captured_handle.handle,
|
252
|
+
quantum_type=captured_handle.quantum_type,
|
253
|
+
): [
|
254
|
+
SymbolPart(
|
255
|
+
source_handle=captured_handle.handle,
|
256
|
+
target_var_name=captured_handle.mangled_name,
|
257
|
+
target_var_type=captured_handle.quantum_type,
|
258
|
+
)
|
259
|
+
]
|
260
|
+
for captured_handle in self._captured_handles
|
261
|
+
if not captured_handle.is_propagated
|
262
|
+
}
|
263
|
+
|
264
|
+
@contextmanager
|
265
|
+
def freeze(self) -> Iterator[None]:
|
266
|
+
previous = self._captured_handles
|
267
|
+
yield
|
268
|
+
self._captured_handles = previous
|
269
|
+
|
270
|
+
|
271
|
+
def _same_closure(closure_1: "FunctionClosure", closure_2: "FunctionClosure") -> bool:
|
272
|
+
return closure_1.depth == closure_2.depth
|
273
|
+
|
274
|
+
|
275
|
+
def validate_args_are_not_propagated(
|
276
|
+
args: Sequence[ArgValue], captured_vars: Sequence[HandleBinding]
|
277
|
+
) -> None:
|
278
|
+
if not captured_vars:
|
279
|
+
return
|
280
|
+
captured_handles = {demangle_handle(handle) for handle in captured_vars}
|
281
|
+
arg_handles = {
|
282
|
+
demangle_handle(arg) for arg in args if isinstance(arg, HandleBinding)
|
283
|
+
}
|
284
|
+
if any(
|
285
|
+
arg_handle.overlaps(captured_handle)
|
286
|
+
for arg_handle in arg_handles
|
287
|
+
for captured_handle in captured_handles
|
288
|
+
):
|
289
|
+
captured_handles_str = {str(handle) for handle in captured_handles}
|
290
|
+
arg_handles_str = {str(handle) for handle in arg_handles}
|
291
|
+
vars_msg = f"Explicitly passed variables: {arg_handles_str}, captured variables: {captured_handles_str}"
|
292
|
+
raise ClassiqExpansionError(
|
293
|
+
f"Cannot capture variables that are explicitly passed as arguments. "
|
294
|
+
f"{vars_msg}"
|
295
|
+
)
|
296
|
+
|
297
|
+
|
298
|
+
def validate_captured_directions(captured_vars: CapturedVars) -> None:
|
299
|
+
captured_inputs = [
|
300
|
+
captured_handle.handle.name
|
301
|
+
for captured_handle in captured_vars._captured_handles
|
302
|
+
if captured_handle.direction == PortDirection.Input
|
303
|
+
]
|
304
|
+
captured_outputs = [
|
305
|
+
captured_handle.handle.name
|
306
|
+
for captured_handle in captured_vars._captured_handles
|
307
|
+
if captured_handle.direction == PortDirection.Output
|
308
|
+
]
|
309
|
+
if len(captured_inputs) > 0:
|
310
|
+
raise ClassiqExpansionError(
|
311
|
+
f"Captured quantum variables {captured_inputs!r} cannot be used as inputs"
|
312
|
+
)
|
313
|
+
if len(captured_outputs) > 0:
|
314
|
+
raise ClassiqExpansionError(
|
315
|
+
f"Captured quantum variables {captured_outputs!r} cannot be used as outputs"
|
316
|
+
)
|
@@ -1,17 +1,25 @@
|
|
1
1
|
import re
|
2
|
+
from typing import TYPE_CHECKING
|
2
3
|
|
3
4
|
from classiq.interface.generator.compiler_keywords import CAPTURE_SUFFIX
|
4
5
|
from classiq.interface.model.handle_binding import HANDLE_ID_SEPARATOR, HandleBinding
|
5
6
|
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from classiq.model_expansions.closure import FunctionClosure
|
9
|
+
|
6
10
|
IDENTIFIER_PATTERN = r"[a-zA-Z_][a-zA-Z0-9_]*"
|
7
11
|
CAPTURE_PATTERN = re.compile(
|
8
|
-
rf"({IDENTIFIER_PATTERN}){CAPTURE_SUFFIX}{IDENTIFIER_PATTERN}__"
|
12
|
+
rf"({IDENTIFIER_PATTERN}){CAPTURE_SUFFIX}{IDENTIFIER_PATTERN}__\d*"
|
9
13
|
)
|
10
14
|
ARRAY_CAST_SUFFIX = HANDLE_ID_SEPARATOR + "array_cast"
|
11
15
|
|
12
16
|
|
13
|
-
def mangle_captured_var_name(
|
14
|
-
|
17
|
+
def mangle_captured_var_name(
|
18
|
+
var_name: str, defining_function: "FunctionClosure"
|
19
|
+
) -> str:
|
20
|
+
return (
|
21
|
+
f"{var_name}{CAPTURE_SUFFIX}{defining_function.name}__{defining_function.depth}"
|
22
|
+
)
|
15
23
|
|
16
24
|
|
17
25
|
def demangle_name(name: str) -> str:
|
@@ -25,15 +33,16 @@ def demangle_handle(handle: HandleBinding) -> HandleBinding:
|
|
25
33
|
return handle
|
26
34
|
if ARRAY_CAST_SUFFIX in name:
|
27
35
|
return HandleBinding(name=name.split(ARRAY_CAST_SUFFIX)[0])
|
28
|
-
name = re.sub(r"_\d+$", "", name)
|
36
|
+
name = re.sub(r"([^_])_\d+$", r"\1", name)
|
29
37
|
name_parts = name.split(HANDLE_ID_SEPARATOR)
|
30
|
-
|
38
|
+
new_name_parts = [name_parts[0]]
|
31
39
|
for part in name_parts[1:]:
|
32
40
|
if re.fullmatch(r"\d+", part):
|
33
|
-
|
41
|
+
new_name_parts.append(f"[{part}]")
|
34
42
|
elif re.fullmatch(r"\d+_\d+", part):
|
35
43
|
part_left, part_right = part.split("_")
|
36
|
-
|
44
|
+
new_name_parts.append(f"[{part_left}:{part_right}]")
|
37
45
|
else:
|
38
|
-
|
39
|
-
|
46
|
+
new_name_parts.append(f".{part}")
|
47
|
+
new_name_parts = list(map(demangle_name, new_name_parts))
|
48
|
+
return handle.rename("".join(new_name_parts))
|