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.
Files changed (52) hide show
  1. classiq/__init__.py +2 -0
  2. classiq/_internals/authentication/auth0.py +29 -0
  3. classiq/_internals/authentication/auth_flow_factory.py +43 -0
  4. classiq/_internals/authentication/machine_credentials_flow.py +26 -0
  5. classiq/_internals/authentication/password_manager.py +84 -0
  6. classiq/_internals/authentication/token_manager.py +24 -8
  7. classiq/analyzer/show_interactive_hack.py +0 -8
  8. classiq/applications/combinatorial_optimization/combinatorial_problem.py +1 -1
  9. classiq/execution/all_hardware_devices.py +59 -1
  10. classiq/execution/functions/__init__.py +11 -1
  11. classiq/execution/functions/expectation_value.py +106 -0
  12. classiq/execution/functions/minimize.py +90 -0
  13. classiq/execution/functions/sample.py +8 -189
  14. classiq/execution/functions/state_vector.py +113 -0
  15. classiq/execution/functions/util/__init__.py +0 -0
  16. classiq/execution/functions/util/backend_preferences.py +188 -0
  17. classiq/interface/_version.py +1 -1
  18. classiq/interface/backend/backend_preferences.py +66 -0
  19. classiq/interface/backend/quantum_backend_providers.py +11 -0
  20. classiq/interface/exceptions.py +0 -4
  21. classiq/interface/generator/arith/binary_ops.py +24 -0
  22. classiq/interface/generator/arith/number_utils.py +15 -6
  23. classiq/interface/generator/compiler_keywords.py +1 -0
  24. classiq/interface/generator/function_param_list.py +4 -0
  25. classiq/interface/generator/function_params.py +1 -1
  26. classiq/interface/generator/functions/classical_type.py +15 -0
  27. classiq/interface/generator/functions/type_name.py +17 -4
  28. classiq/interface/generator/transpiler_basis_gates.py +1 -0
  29. classiq/interface/generator/types/compilation_metadata.py +15 -6
  30. classiq/interface/hardware.py +1 -0
  31. classiq/interface/interface_version.py +1 -1
  32. classiq/interface/model/model.py +19 -0
  33. classiq/interface/model/quantum_type.py +15 -0
  34. classiq/interface/qubits_mapping/__init__.py +4 -0
  35. classiq/interface/qubits_mapping/path_expr_range.py +69 -0
  36. classiq/interface/qubits_mapping/qubits_mapping.py +231 -0
  37. classiq/interface/qubits_mapping/slices.py +112 -0
  38. classiq/model_expansions/arithmetic.py +6 -0
  39. classiq/qmod/builtins/functions/__init__.py +12 -9
  40. classiq/qmod/builtins/functions/allocation.py +0 -36
  41. classiq/qmod/builtins/functions/arithmetic.py +52 -0
  42. classiq/qmod/builtins/functions/gray_code.py +23 -0
  43. classiq/qmod/builtins/functions/mcx_func.py +10 -0
  44. classiq/qmod/builtins/structs.py +22 -3
  45. classiq/qprog_to_cudaq.py +347 -0
  46. {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/METADATA +4 -1
  47. {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/RECORD +52 -39
  48. /classiq/execution/functions/{_logging.py → util/_logging.py} +0 -0
  49. /classiq/execution/functions/{constants.py → util/constants.py} +0 -0
  50. /classiq/execution/functions/{parse_provider_backend.py → util/parse_provider_backend.py} +0 -0
  51. {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/WHEEL +0 -0
  52. {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
- if number >= 0:
9
- return number
10
-
11
- not_power2 = abs(number) & (abs(number) - 1) != 0
12
- return number + 2 ** (number.bit_length() + 1 * not_power2)
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:
@@ -2,6 +2,7 @@ EXPANDED_KEYWORD = "expanded__"
2
2
  CAPTURE_SUFFIX = "_captured__"
3
3
  LAMBDA_KEYWORD = "lambda__"
4
4
  INPLACE_ARITH_AUX_VAR_PREFIX = "_tmp"
5
+ INPLACE_CONTROL_AUX_VAR_PREFIX = "_ctmp"
5
6
  THEN_KEYWORD = "then"
6
7
  ELSE_KEYWORD = "else"
7
8
 
@@ -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"_?[a-zA-Z]{ALPHANUM_AND_UNDERSCORE}"
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[field_prefix] = field_type.get_compile_time_attributes(
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[field_prefix] = classical_field_type.get_compile_time_attributes(
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
@@ -59,6 +59,7 @@ EXTRA_TWO_QUBIT_GATES: BasisGates = frozenset(
59
59
  "cu",
60
60
  "ch",
61
61
  "cp",
62
+ "iswap",
62
63
  )
63
64
  )
64
65
 
@@ -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
- _required_clean_qubit: NonNegativeInt = PrivateAttr(default=0)
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 required_clean_qubit(self) -> NonNegativeInt:
22
- return self._required_clean_qubit
22
+ def min_required_clean_qubit(self) -> NonNegativeInt:
23
+ return self._min_required_clean_qubit
23
24
 
24
- @required_clean_qubit.setter
25
- def required_clean_qubit(self, value: NonNegativeInt) -> None:
26
- self._required_clean_qubit = value
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:
@@ -34,6 +34,7 @@ class Provider(StrEnum):
34
34
  AQT = "AQT"
35
35
  CINECA = "CINECA"
36
36
  SOFTBANK = "Softbank"
37
+ C12 = "C12"
37
38
 
38
39
  @property
39
40
  def id(self) -> "ProviderIDEnum":
@@ -1 +1 @@
1
- INTERFACE_VERSION = "16"
1
+ INTERFACE_VERSION = "17"
@@ -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,4 @@
1
+ from .qubits_mapping import FunctionScope, QubitsMapping
2
+ from .slices import Slices
3
+
4
+ __all__ = ["FunctionScope", "QubitsMapping", "Slices"]
@@ -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