classiq 0.104.0__py3-none-any.whl → 1.0.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/__init__.py +2 -0
- classiq/_internals/authentication/auth0.py +29 -0
- classiq/_internals/authentication/auth_flow_factory.py +43 -0
- classiq/_internals/authentication/machine_credentials_flow.py +26 -0
- classiq/_internals/authentication/password_manager.py +84 -0
- classiq/_internals/authentication/token_manager.py +24 -8
- classiq/analyzer/show_interactive_hack.py +0 -8
- classiq/applications/combinatorial_optimization/combinatorial_problem.py +1 -1
- classiq/execution/all_hardware_devices.py +59 -1
- classiq/execution/functions/__init__.py +11 -1
- classiq/execution/functions/expectation_value.py +106 -0
- classiq/execution/functions/minimize.py +90 -0
- classiq/execution/functions/sample.py +8 -189
- classiq/execution/functions/state_vector.py +113 -0
- classiq/execution/functions/util/__init__.py +0 -0
- classiq/execution/functions/util/backend_preferences.py +188 -0
- classiq/interface/_version.py +1 -1
- classiq/interface/backend/backend_preferences.py +66 -0
- classiq/interface/backend/quantum_backend_providers.py +11 -0
- classiq/interface/exceptions.py +0 -4
- classiq/interface/generator/arith/binary_ops.py +24 -0
- classiq/interface/generator/arith/number_utils.py +15 -6
- classiq/interface/generator/compiler_keywords.py +1 -0
- classiq/interface/generator/function_param_list.py +4 -0
- classiq/interface/generator/function_params.py +1 -1
- classiq/interface/generator/functions/classical_type.py +15 -0
- classiq/interface/generator/functions/type_name.py +17 -4
- classiq/interface/generator/transpiler_basis_gates.py +1 -0
- classiq/interface/generator/types/compilation_metadata.py +15 -6
- classiq/interface/hardware.py +1 -0
- classiq/interface/interface_version.py +1 -1
- classiq/interface/model/model.py +19 -0
- classiq/interface/model/quantum_type.py +15 -0
- classiq/interface/qubits_mapping/__init__.py +4 -0
- classiq/interface/qubits_mapping/path_expr_range.py +69 -0
- classiq/interface/qubits_mapping/qubits_mapping.py +231 -0
- classiq/interface/qubits_mapping/slices.py +112 -0
- classiq/model_expansions/arithmetic.py +6 -0
- classiq/qmod/builtins/functions/__init__.py +12 -9
- classiq/qmod/builtins/functions/allocation.py +0 -36
- classiq/qmod/builtins/functions/arithmetic.py +52 -0
- classiq/qmod/builtins/functions/gray_code.py +23 -0
- classiq/qmod/builtins/functions/mcx_func.py +10 -0
- classiq/qmod/builtins/structs.py +22 -3
- classiq/qprog_to_cudaq.py +347 -0
- {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/METADATA +4 -1
- {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/RECORD +52 -39
- /classiq/execution/functions/{_logging.py → util/_logging.py} +0 -0
- /classiq/execution/functions/{constants.py → util/constants.py} +0 -0
- /classiq/execution/functions/{parse_provider_backend.py → util/parse_provider_backend.py} +0 -0
- {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/WHEEL +0 -0
- {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -338,6 +338,30 @@ class Adder(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
|
|
|
338
338
|
return isinstance(arg, float) and arg == 0
|
|
339
339
|
|
|
340
340
|
|
|
341
|
+
class CanonicalAdder(FunctionParams):
|
|
342
|
+
left_size: pydantic.PositiveInt
|
|
343
|
+
extend_left: bool
|
|
344
|
+
right_size: pydantic.PositiveInt
|
|
345
|
+
|
|
346
|
+
def _create_ios(self) -> None:
|
|
347
|
+
self._inputs = {
|
|
348
|
+
DEFAULT_LEFT_ARG_NAME: RegisterArithmeticInfo(size=self.left_size),
|
|
349
|
+
DEFAULT_RIGHT_ARG_NAME: RegisterArithmeticInfo(size=self.right_size),
|
|
350
|
+
}
|
|
351
|
+
self._outputs = {**self._inputs}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class CanonicalConstantAdder(FunctionParams):
|
|
355
|
+
left: int
|
|
356
|
+
right_size: pydantic.PositiveInt
|
|
357
|
+
|
|
358
|
+
def _create_ios(self) -> None:
|
|
359
|
+
self._inputs = {
|
|
360
|
+
"right_arg": RegisterArithmeticInfo(size=self.right_size),
|
|
361
|
+
}
|
|
362
|
+
self._outputs = {**self._inputs}
|
|
363
|
+
|
|
364
|
+
|
|
341
365
|
class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
|
|
342
366
|
output_name = "difference"
|
|
343
367
|
|
|
@@ -3,13 +3,22 @@ from typing import Final
|
|
|
3
3
|
MAXIMAL_MACHINE_PRECISION: Final[int] = 20
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def signed_int_to_unsigned(number: int) -> int:
|
|
6
|
+
def signed_int_to_unsigned(number: int, reg_size: int | None = None) -> int:
|
|
7
7
|
"""Return the integer value of a signed int if it would we read as un-signed in binary representation"""
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
signed: bool = False
|
|
9
|
+
if number < 0:
|
|
10
|
+
signed = True
|
|
11
|
+
not_power2 = abs(number) & (abs(number) - 1) != 0
|
|
12
|
+
number = number + 2 ** (number.bit_length() + 1 * not_power2)
|
|
13
|
+
|
|
14
|
+
if reg_size is not None:
|
|
15
|
+
bits = bin(number)[2:][::-1]
|
|
16
|
+
bits = bits[:reg_size]
|
|
17
|
+
if signed and len(bits) < reg_size:
|
|
18
|
+
bits += "1" * (reg_size - len(bits))
|
|
19
|
+
number = int(bits[::-1], 2)
|
|
20
|
+
|
|
21
|
+
return number
|
|
13
22
|
|
|
14
23
|
|
|
15
24
|
def binary_to_int(bin_rep: str, is_signed: bool = False) -> int:
|
|
@@ -6,6 +6,8 @@ from classiq.interface.generator.arith.binary_ops import (
|
|
|
6
6
|
BitwiseAnd,
|
|
7
7
|
BitwiseOr,
|
|
8
8
|
BitwiseXor,
|
|
9
|
+
CanonicalAdder,
|
|
10
|
+
CanonicalConstantAdder,
|
|
9
11
|
CanonicalConstantMultiplier,
|
|
10
12
|
CanonicalMultiplier,
|
|
11
13
|
Equal,
|
|
@@ -59,6 +61,8 @@ function_param_library: FunctionParamLibrary = FunctionParamLibrary(
|
|
|
59
61
|
BitwiseXor,
|
|
60
62
|
BitwiseInvert,
|
|
61
63
|
Adder,
|
|
64
|
+
CanonicalAdder,
|
|
65
|
+
CanonicalConstantAdder,
|
|
62
66
|
Arithmetic,
|
|
63
67
|
Sign,
|
|
64
68
|
Equal,
|
|
@@ -46,7 +46,7 @@ END_BAD_REGISTER_ERROR_MSG = (
|
|
|
46
46
|
)
|
|
47
47
|
|
|
48
48
|
ALPHANUM_AND_UNDERSCORE = r"[0-9a-zA-Z_]*"
|
|
49
|
-
NAME_REGEX = rf"_
|
|
49
|
+
NAME_REGEX = rf"_{{0,2}}[a-zA-Z]{ALPHANUM_AND_UNDERSCORE}"
|
|
50
50
|
|
|
51
51
|
_UNVALIDATED_FUNCTIONS = ["Arithmetic", "CustomFunction"]
|
|
52
52
|
|
|
@@ -83,6 +83,9 @@ class ClassicalType(HashableASTNode):
|
|
|
83
83
|
def get_compile_time_attributes(self, path_expr_prefix: str) -> dict[str, Any]:
|
|
84
84
|
return {}
|
|
85
85
|
|
|
86
|
+
def get_compile_time_attribute_types(self, prefix: str) -> dict[str, type]:
|
|
87
|
+
return {}
|
|
88
|
+
|
|
86
89
|
|
|
87
90
|
class Integer(ClassicalType):
|
|
88
91
|
kind: Literal["int"]
|
|
@@ -244,6 +247,11 @@ class ClassicalArray(ClassicalType):
|
|
|
244
247
|
f"{path_expr_prefix}[0]"
|
|
245
248
|
)
|
|
246
249
|
|
|
250
|
+
def get_compile_time_attribute_types(self, prefix: str) -> dict[str, type]:
|
|
251
|
+
return {
|
|
252
|
+
f"{prefix}.len": int
|
|
253
|
+
} | self.element_type.get_compile_time_attribute_types(f"{prefix}[0]")
|
|
254
|
+
|
|
247
255
|
|
|
248
256
|
class ClassicalTuple(ClassicalType):
|
|
249
257
|
kind: Literal["tuple"]
|
|
@@ -336,6 +344,13 @@ class ClassicalTuple(ClassicalType):
|
|
|
336
344
|
return attrs
|
|
337
345
|
return attrs | raw_type.get_compile_time_attributes(path_expr_prefix)
|
|
338
346
|
|
|
347
|
+
def get_compile_time_attribute_types(self, prefix: str) -> dict[str, type]:
|
|
348
|
+
raw_type = self.get_raw_type(preserve_length=True)
|
|
349
|
+
attrs: dict[str, type] = {f"{prefix}.len": int}
|
|
350
|
+
if isinstance(raw_type, ClassicalTuple):
|
|
351
|
+
return attrs
|
|
352
|
+
return attrs | raw_type.get_compile_time_attribute_types(prefix)
|
|
353
|
+
|
|
339
354
|
|
|
340
355
|
class OpaqueHandle(ClassicalType):
|
|
341
356
|
pass
|
|
@@ -211,16 +211,29 @@ class TypeName(ClassicalType, QuantumType):
|
|
|
211
211
|
if self.has_fields:
|
|
212
212
|
for field_name, field_type in self.fields.items():
|
|
213
213
|
field_prefix = f"{path_expr_prefix}.{field_name}"
|
|
214
|
-
attrs
|
|
215
|
-
field_prefix
|
|
216
|
-
)
|
|
214
|
+
attrs |= field_type.get_compile_time_attributes(field_prefix)
|
|
217
215
|
elif self.has_classical_struct_decl:
|
|
218
216
|
for (
|
|
219
217
|
field_name,
|
|
220
218
|
classical_field_type,
|
|
221
219
|
) in self.classical_struct_decl.variables.items():
|
|
222
220
|
field_prefix = f"{path_expr_prefix}.{field_name}"
|
|
223
|
-
attrs
|
|
221
|
+
attrs |= classical_field_type.get_compile_time_attributes(field_prefix)
|
|
222
|
+
return attrs
|
|
223
|
+
|
|
224
|
+
def get_compile_time_attribute_types(self, prefix: str) -> dict[str, type]:
|
|
225
|
+
attrs: dict[str, type] = {}
|
|
226
|
+
if self.has_fields:
|
|
227
|
+
for field_name, field_type in self.fields.items():
|
|
228
|
+
field_prefix = f"{prefix}.{field_name}"
|
|
229
|
+
attrs |= field_type.get_compile_time_attribute_types(field_prefix)
|
|
230
|
+
elif self.has_classical_struct_decl:
|
|
231
|
+
for (
|
|
232
|
+
field_name,
|
|
233
|
+
classical_field_type,
|
|
234
|
+
) in self.classical_struct_decl.variables.items():
|
|
235
|
+
field_prefix = f"{prefix}.{field_name}"
|
|
236
|
+
attrs |= classical_field_type.get_compile_time_attribute_types(
|
|
224
237
|
field_prefix
|
|
225
238
|
)
|
|
226
239
|
return attrs
|
|
@@ -5,7 +5,8 @@ class CompilationMetadata(BaseModel):
|
|
|
5
5
|
should_synthesize_separately: bool = Field(default=False)
|
|
6
6
|
occurrences_number: NonNegativeInt = Field(default=0)
|
|
7
7
|
_occupation_number: NonNegativeInt = PrivateAttr(default=0)
|
|
8
|
-
|
|
8
|
+
_min_required_clean_qubit: NonNegativeInt = PrivateAttr(default=0)
|
|
9
|
+
_max_required_clean_qubit: NonNegativeInt = PrivateAttr(default=0)
|
|
9
10
|
disable_perm_check: bool = Field(default=False)
|
|
10
11
|
disable_const_checks: list[str] | bool = Field(default=False)
|
|
11
12
|
|
|
@@ -18,12 +19,20 @@ class CompilationMetadata(BaseModel):
|
|
|
18
19
|
self._occupation_number = value
|
|
19
20
|
|
|
20
21
|
@property
|
|
21
|
-
def
|
|
22
|
-
return self.
|
|
22
|
+
def min_required_clean_qubit(self) -> NonNegativeInt:
|
|
23
|
+
return self._min_required_clean_qubit
|
|
23
24
|
|
|
24
|
-
@
|
|
25
|
-
def
|
|
26
|
-
self.
|
|
25
|
+
@min_required_clean_qubit.setter
|
|
26
|
+
def min_required_clean_qubit(self, value: NonNegativeInt) -> None:
|
|
27
|
+
self._min_required_clean_qubit = value
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def max_required_clean_qubit(self) -> NonNegativeInt:
|
|
31
|
+
return self._max_required_clean_qubit
|
|
32
|
+
|
|
33
|
+
@max_required_clean_qubit.setter
|
|
34
|
+
def max_required_clean_qubit(self, value: NonNegativeInt) -> None:
|
|
35
|
+
self._max_required_clean_qubit = value
|
|
27
36
|
|
|
28
37
|
@property
|
|
29
38
|
def has_user_directives(self) -> bool:
|
classiq/interface/hardware.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
INTERFACE_VERSION = "
|
|
1
|
+
INTERFACE_VERSION = "17"
|
classiq/interface/model/model.py
CHANGED
|
@@ -10,6 +10,7 @@ from classiq.interface.debug_info.debug_info import DebugInfoCollection
|
|
|
10
10
|
from classiq.interface.exceptions import ClassiqValueError
|
|
11
11
|
from classiq.interface.executor.execution_preferences import ExecutionPreferences
|
|
12
12
|
from classiq.interface.generator.constant import Constant
|
|
13
|
+
from classiq.interface.generator.function_params import ArithmeticIODict
|
|
13
14
|
from classiq.interface.generator.functions.port_declaration import (
|
|
14
15
|
PortDeclarationDirection,
|
|
15
16
|
)
|
|
@@ -28,6 +29,10 @@ from classiq.interface.model.native_function_definition import (
|
|
|
28
29
|
from classiq.interface.model.quantum_function_declaration import (
|
|
29
30
|
NamedParamsQuantumFunctionDeclaration,
|
|
30
31
|
)
|
|
32
|
+
from classiq.interface.model.quantum_type import (
|
|
33
|
+
RegisterQuantumTypeDict,
|
|
34
|
+
quantum_type_to_register_quantum_type,
|
|
35
|
+
)
|
|
31
36
|
from classiq.interface.model.statement_block import StatementBlock
|
|
32
37
|
|
|
33
38
|
USER_MODEL_MARKER = "user"
|
|
@@ -237,3 +242,17 @@ class Model(VersionedModel, ASTNode):
|
|
|
237
242
|
self.compressed_debug_info = None
|
|
238
243
|
else:
|
|
239
244
|
self.compressed_debug_info = compress_pydantic(self._debug_info)
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def measured_registers(self) -> ArithmeticIODict:
|
|
248
|
+
return self.main_func.outputs_dict
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def measured_registers_type(self) -> RegisterQuantumTypeDict:
|
|
252
|
+
return {
|
|
253
|
+
key: quantum_type_to_register_quantum_type(
|
|
254
|
+
self.main_func.port_declarations_dict[key].quantum_type,
|
|
255
|
+
self.main_func.port_declarations_dict[key].quantum_type.size_in_bits,
|
|
256
|
+
)
|
|
257
|
+
for key in self.measured_registers.keys()
|
|
258
|
+
}
|
|
@@ -81,6 +81,9 @@ class QuantumType(HashableASTNode):
|
|
|
81
81
|
def get_compile_time_attributes(self, path_expr_prefix: str) -> dict[str, Any]:
|
|
82
82
|
return {}
|
|
83
83
|
|
|
84
|
+
def get_compile_time_attribute_types(self, prefix: str) -> dict[str, type]:
|
|
85
|
+
return {}
|
|
86
|
+
|
|
84
87
|
|
|
85
88
|
class QuantumScalar(QuantumType):
|
|
86
89
|
@property
|
|
@@ -273,6 +276,11 @@ class QuantumBitvector(QuantumType):
|
|
|
273
276
|
f"{path_expr_prefix}[0]"
|
|
274
277
|
)
|
|
275
278
|
|
|
279
|
+
def get_compile_time_attribute_types(self, prefix: str) -> dict[str, type]:
|
|
280
|
+
return {
|
|
281
|
+
f"{prefix}.len": int
|
|
282
|
+
} | self.element_type.get_compile_time_attribute_types(f"{prefix}[0]")
|
|
283
|
+
|
|
276
284
|
def without_symbolic_attributes(self) -> "QuantumBitvector":
|
|
277
285
|
length = (
|
|
278
286
|
None
|
|
@@ -322,6 +330,13 @@ class QuantumNumeric(QuantumScalar):
|
|
|
322
330
|
attrs[f"{path_expr_prefix}.fraction_digits"] = self.fraction_digits_value
|
|
323
331
|
return attrs
|
|
324
332
|
|
|
333
|
+
def get_compile_time_attribute_types(self, prefix: str) -> dict[str, type]:
|
|
334
|
+
return {
|
|
335
|
+
f"{prefix}.size": int,
|
|
336
|
+
f"{prefix}.is_signed": bool,
|
|
337
|
+
f"{prefix}.fraction_digits": int,
|
|
338
|
+
}
|
|
339
|
+
|
|
325
340
|
def set_size_in_bits(self, val: int) -> None:
|
|
326
341
|
super().set_size_in_bits(val)
|
|
327
342
|
if self.size is not None:
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from functools import singledispatch
|
|
2
|
+
|
|
3
|
+
from classiq.interface.exceptions import ClassiqInternalExpansionError
|
|
4
|
+
from classiq.interface.generator.functions.concrete_types import ConcreteQuantumType
|
|
5
|
+
from classiq.interface.generator.functions.type_name import TypeName
|
|
6
|
+
from classiq.interface.model.handle_binding import (
|
|
7
|
+
FieldHandleBinding,
|
|
8
|
+
HandleBinding,
|
|
9
|
+
SlicedHandleBinding,
|
|
10
|
+
SubscriptHandleBinding,
|
|
11
|
+
)
|
|
12
|
+
from classiq.interface.model.quantum_type import QuantumBitvector
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_path_expr_range(
|
|
16
|
+
var: HandleBinding, quantum_type: ConcreteQuantumType
|
|
17
|
+
) -> tuple[int, int]:
|
|
18
|
+
start = 0
|
|
19
|
+
stop = quantum_type.size_in_bits
|
|
20
|
+
for var_prefix in var.prefixes()[1:]:
|
|
21
|
+
start, stop, quantum_type = _pop_var_range(var_prefix, quantum_type, start)
|
|
22
|
+
return start, stop
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@singledispatch
|
|
26
|
+
def _pop_var_range(
|
|
27
|
+
var_prefix: HandleBinding, quantum_type: ConcreteQuantumType, start: int
|
|
28
|
+
) -> tuple[int, int, ConcreteQuantumType]:
|
|
29
|
+
raise ClassiqInternalExpansionError("Unexpected path expression")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@_pop_var_range.register
|
|
33
|
+
def _(
|
|
34
|
+
var_prefix: SubscriptHandleBinding, quantum_type: ConcreteQuantumType, start: int
|
|
35
|
+
) -> tuple[int, int, ConcreteQuantumType]:
|
|
36
|
+
if not isinstance(quantum_type, QuantumBitvector):
|
|
37
|
+
raise ClassiqInternalExpansionError("Unexpected path expression")
|
|
38
|
+
index = var_prefix.index.to_int_value()
|
|
39
|
+
element_type = quantum_type.element_type
|
|
40
|
+
start += element_type.size_in_bits * index
|
|
41
|
+
stop = start + element_type.size_in_bits
|
|
42
|
+
return start, stop, element_type
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@_pop_var_range.register
|
|
46
|
+
def _(
|
|
47
|
+
var_prefix: SlicedHandleBinding, quantum_type: ConcreteQuantumType, start: int
|
|
48
|
+
) -> tuple[int, int, ConcreteQuantumType]:
|
|
49
|
+
if not isinstance(quantum_type, QuantumBitvector):
|
|
50
|
+
raise ClassiqInternalExpansionError("Unexpected path expression")
|
|
51
|
+
slice_start = var_prefix.start.to_int_value()
|
|
52
|
+
slice_stop = var_prefix.end.to_int_value()
|
|
53
|
+
stop = start + quantum_type.element_type.size_in_bits * slice_stop
|
|
54
|
+
start += quantum_type.element_type.size_in_bits * slice_start
|
|
55
|
+
return start, stop, quantum_type
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@_pop_var_range.register
|
|
59
|
+
def _(
|
|
60
|
+
var_prefix: FieldHandleBinding, quantum_type: ConcreteQuantumType, start: int
|
|
61
|
+
) -> tuple[int, int, ConcreteQuantumType]:
|
|
62
|
+
if not isinstance(quantum_type, TypeName) or not quantum_type.has_fields:
|
|
63
|
+
raise ClassiqInternalExpansionError("Unexpected path expression")
|
|
64
|
+
for field, field_type in quantum_type.fields.items():
|
|
65
|
+
if field == var_prefix.field:
|
|
66
|
+
stop = start + field_type.size_in_bits
|
|
67
|
+
return start, stop, field_type
|
|
68
|
+
start += field_type.size_in_bits
|
|
69
|
+
raise ClassiqInternalExpansionError("Unexpected path expression")
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import itertools
|
|
3
|
+
from collections.abc import Iterable, Iterator
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import Any, Generic, TypeGuard, TypeVar, cast
|
|
6
|
+
|
|
7
|
+
from classiq.interface.exceptions import ClassiqInternalError
|
|
8
|
+
from classiq.interface.generator.functions.concrete_types import ConcreteQuantumType
|
|
9
|
+
from classiq.interface.generator.functions.port_declaration import (
|
|
10
|
+
PortDeclarationDirection,
|
|
11
|
+
)
|
|
12
|
+
from classiq.interface.generator.visitor import RetType
|
|
13
|
+
from classiq.interface.model.allocate import Allocate
|
|
14
|
+
from classiq.interface.model.bind_operation import BindOperation
|
|
15
|
+
from classiq.interface.model.handle_binding import ConcreteHandleBinding, HandleBinding
|
|
16
|
+
from classiq.interface.model.model import Model
|
|
17
|
+
from classiq.interface.model.model_visitor import ModelStatementsVisitor
|
|
18
|
+
from classiq.interface.model.native_function_definition import NativeFunctionDefinition
|
|
19
|
+
from classiq.interface.model.quantum_function_call import QuantumFunctionCall
|
|
20
|
+
from classiq.interface.model.quantum_function_declaration import (
|
|
21
|
+
NamedParamsQuantumFunctionDeclaration,
|
|
22
|
+
QuantumFunctionDeclaration,
|
|
23
|
+
)
|
|
24
|
+
from classiq.interface.model.quantum_type import QuantumType
|
|
25
|
+
from classiq.interface.model.variable_declaration_statement import (
|
|
26
|
+
VariableDeclarationStatement,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from .path_expr_range import get_path_expr_range
|
|
30
|
+
from .slices import Slices
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclasses.dataclass
|
|
34
|
+
class FunctionScope:
|
|
35
|
+
variables: dict[str, ConcreteQuantumType] = dataclasses.field(default_factory=dict)
|
|
36
|
+
allocated_variables: dict[str, Slices] = dataclasses.field(default_factory=dict)
|
|
37
|
+
total_resources: int = dataclasses.field(default=0)
|
|
38
|
+
|
|
39
|
+
def add_new_var(self, var_name: str, quantum_type: ConcreteQuantumType) -> None:
|
|
40
|
+
self.variables[var_name] = quantum_type
|
|
41
|
+
|
|
42
|
+
def allocate_new_var(self, var_name: str, var_size: int) -> None:
|
|
43
|
+
new_total_resources = self.total_resources + var_size
|
|
44
|
+
self.allocated_variables[var_name] = Slices(
|
|
45
|
+
[(self.total_resources, new_total_resources)]
|
|
46
|
+
)
|
|
47
|
+
self.total_resources = new_total_resources
|
|
48
|
+
|
|
49
|
+
def bind_slices_to_var(self, var_name: str, slices: Slices) -> None:
|
|
50
|
+
self.allocated_variables[var_name] = slices
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
T = TypeVar("T", bound=FunctionScope)
|
|
54
|
+
|
|
55
|
+
FREE_NAME = "free"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class QubitsMapping(Generic[T], ModelStatementsVisitor):
|
|
59
|
+
"""
|
|
60
|
+
Visitor that maps quantum variables to virtual qubit slices in a quantum model.
|
|
61
|
+
|
|
62
|
+
This visitor traverses the model's quantum functions and tracks the mapping of
|
|
63
|
+
quantum variables to their virtual qubit allocations. It maintains scopes for each
|
|
64
|
+
function, tracks the call stack, and maps variables to slices representing ranges
|
|
65
|
+
of qubits. This class assumes that the visited model is a compiled qmod, which mainly
|
|
66
|
+
means that all allocations are in the main function, and the model does not contain within_apply.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(self, scope_type: type[FunctionScope] = FunctionScope) -> None:
|
|
70
|
+
super().__init__()
|
|
71
|
+
self._scope_type: type[T] = cast(type[T], scope_type)
|
|
72
|
+
self.scopes: dict[str, T] = {}
|
|
73
|
+
self._main_func_name: str
|
|
74
|
+
self._current_function: str
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def _current_scope(self) -> T:
|
|
78
|
+
return self.scopes[self._current_function]
|
|
79
|
+
|
|
80
|
+
@contextmanager
|
|
81
|
+
def _function_scoping(
|
|
82
|
+
self, func_def: NamedParamsQuantumFunctionDeclaration
|
|
83
|
+
) -> Iterator[T]:
|
|
84
|
+
func_name = func_def.name
|
|
85
|
+
_previous_function = self._current_function
|
|
86
|
+
self._current_function = func_name
|
|
87
|
+
self.scopes[func_name] = self._initialize_function_scope(func_def)
|
|
88
|
+
yield self.scopes[func_name]
|
|
89
|
+
self._current_function = _previous_function
|
|
90
|
+
|
|
91
|
+
def _is_entry_point(self, func_name: str) -> bool:
|
|
92
|
+
return func_name == self._main_func_name
|
|
93
|
+
|
|
94
|
+
def _is_free_function_call(self, call: QuantumFunctionCall) -> bool:
|
|
95
|
+
return call.func_decl.name == FREE_NAME
|
|
96
|
+
|
|
97
|
+
def _is_function_with_definition(
|
|
98
|
+
self, func_decl: QuantumFunctionDeclaration
|
|
99
|
+
) -> TypeGuard[NativeFunctionDefinition]:
|
|
100
|
+
return isinstance(func_decl, NativeFunctionDefinition)
|
|
101
|
+
|
|
102
|
+
def visit_Model(self, model: Model) -> RetType | None:
|
|
103
|
+
self._main_func_name = model.main_func.name
|
|
104
|
+
self._current_function = self._main_func_name
|
|
105
|
+
self.visit_BaseModel(model)
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def visit_NativeFunctionDefinition(
|
|
109
|
+
self, func: NativeFunctionDefinition
|
|
110
|
+
) -> RetType | None:
|
|
111
|
+
if func.name in self.scopes:
|
|
112
|
+
return None
|
|
113
|
+
with self._function_scoping(func):
|
|
114
|
+
self.visit(func.body)
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
def visit_Allocate(self, stat: Allocate) -> RetType | None:
|
|
118
|
+
if not self._is_entry_point(self._current_function):
|
|
119
|
+
raise ClassiqInternalError(
|
|
120
|
+
"compiled qmod can't have allocation outside of main function"
|
|
121
|
+
)
|
|
122
|
+
var_name = stat.target.name
|
|
123
|
+
var_type = self._current_scope.variables[var_name]
|
|
124
|
+
self._current_scope.allocate_new_var(stat.target.name, var_type.size_in_bits)
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
def visit_VariableDeclarationStatement(
|
|
128
|
+
self, stat: VariableDeclarationStatement
|
|
129
|
+
) -> RetType | None:
|
|
130
|
+
if isinstance(stat.qmod_type, QuantumType):
|
|
131
|
+
self._current_scope.add_new_var(stat.name, stat.qmod_type)
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
def _visit_free_function_call(self, stat: QuantumFunctionCall) -> None | Any:
|
|
135
|
+
if not self._is_entry_point(self._current_function):
|
|
136
|
+
raise ClassiqInternalError(
|
|
137
|
+
"compiled qmod can't have free outside of main function"
|
|
138
|
+
)
|
|
139
|
+
input_name = stat.inputs[0].name
|
|
140
|
+
self._current_scope.allocated_variables.pop(input_name)
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
def _visit_quantum_function_call(self, stat: QuantumFunctionCall) -> None | Any:
|
|
144
|
+
if self._is_free_function_call(stat):
|
|
145
|
+
self._visit_free_function_call(stat)
|
|
146
|
+
return None
|
|
147
|
+
func_decl = stat.func_decl
|
|
148
|
+
if not self._is_function_with_definition(func_decl):
|
|
149
|
+
return None
|
|
150
|
+
input_slices = self.get_call_input_slices(stat)
|
|
151
|
+
end_call_scope = self.scopes[stat.func_decl.name]
|
|
152
|
+
for port, handle in zip(func_decl.port_declarations, stat.ports):
|
|
153
|
+
if port.direction == PortDeclarationDirection.Input:
|
|
154
|
+
new_slices = Slices()
|
|
155
|
+
else:
|
|
156
|
+
relative_output = end_call_scope.allocated_variables[port.name]
|
|
157
|
+
new_slices = input_slices.mapping_virtual_slices(relative_output)
|
|
158
|
+
self._update_by_handle(handle, new_slices, port.direction)
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
def visit_QuantumFunctionCall(self, stat: QuantumFunctionCall) -> RetType | None:
|
|
162
|
+
if self._is_free_function_call(stat):
|
|
163
|
+
self._visit_free_function_call(stat)
|
|
164
|
+
return None
|
|
165
|
+
self._visit_quantum_function_call(stat)
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
def visit_BindOperation(self, stat: BindOperation) -> RetType | None:
|
|
169
|
+
input_slices = self._handles_to_slices(stat.in_handles)
|
|
170
|
+
for in_handle in stat.in_handles:
|
|
171
|
+
self._current_scope.allocated_variables.pop(in_handle.name)
|
|
172
|
+
start_index, end_index = 0, 0
|
|
173
|
+
for out in stat.out_handles:
|
|
174
|
+
out_var = self._current_scope.variables[out.name]
|
|
175
|
+
end_index += out_var.size_in_bits
|
|
176
|
+
new_slices = input_slices.get_virtual_slice(start_index, end_index)
|
|
177
|
+
self._update_by_handle(out, new_slices, PortDeclarationDirection.Output)
|
|
178
|
+
start_index = end_index
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def get_call_input_slices(self, stat: QuantumFunctionCall) -> Slices:
|
|
182
|
+
input_handles = (
|
|
183
|
+
handle
|
|
184
|
+
for inp, handle in zip(stat.func_decl.port_declarations, stat.ports)
|
|
185
|
+
if inp.direction.is_input
|
|
186
|
+
)
|
|
187
|
+
return self._handles_to_slices(input_handles)
|
|
188
|
+
|
|
189
|
+
def _handles_to_slices(self, handles: Iterable[ConcreteHandleBinding]) -> Slices:
|
|
190
|
+
return Slices(
|
|
191
|
+
itertools.chain.from_iterable(
|
|
192
|
+
self._handle_to_slices(handle) for handle in handles
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def _handle_to_slices(self, handle: HandleBinding) -> Slices:
|
|
197
|
+
quantum_type = self._current_scope.variables[handle.name]
|
|
198
|
+
var_mapping = self._current_scope.allocated_variables[handle.name]
|
|
199
|
+
start, stop = get_path_expr_range(handle, quantum_type)
|
|
200
|
+
return var_mapping.get_virtual_slice(start, stop)
|
|
201
|
+
|
|
202
|
+
def _update_by_handle(
|
|
203
|
+
self,
|
|
204
|
+
handle: HandleBinding,
|
|
205
|
+
new_slices: Slices,
|
|
206
|
+
direction: PortDeclarationDirection,
|
|
207
|
+
) -> None:
|
|
208
|
+
if direction == PortDeclarationDirection.Input:
|
|
209
|
+
self._current_scope.allocated_variables.pop(handle.name)
|
|
210
|
+
elif direction == PortDeclarationDirection.Output:
|
|
211
|
+
pass
|
|
212
|
+
else:
|
|
213
|
+
quantum_type = self._current_scope.variables[handle.name]
|
|
214
|
+
start, stop = get_path_expr_range(handle, quantum_type)
|
|
215
|
+
var_mapping = self._current_scope.allocated_variables[handle.name].copy()
|
|
216
|
+
var_mapping.update_virtual_slice(start, stop, new_slices)
|
|
217
|
+
new_slices = var_mapping
|
|
218
|
+
self._current_scope.bind_slices_to_var(handle.name, new_slices)
|
|
219
|
+
|
|
220
|
+
def _initialize_function_scope(
|
|
221
|
+
self, func_def: NamedParamsQuantumFunctionDeclaration
|
|
222
|
+
) -> T:
|
|
223
|
+
function_variables = self._scope_type()
|
|
224
|
+
ports = func_def.port_declarations
|
|
225
|
+
for port in ports:
|
|
226
|
+
function_variables.add_new_var(port.name, port.quantum_type)
|
|
227
|
+
if port.direction != PortDeclarationDirection.Output:
|
|
228
|
+
function_variables.allocate_new_var(
|
|
229
|
+
port.name, port.quantum_type.size_in_bits
|
|
230
|
+
)
|
|
231
|
+
return function_variables
|