classiq 0.45.0__py3-none-any.whl → 0.46.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 (146) hide show
  1. classiq/__init__.py +0 -1
  2. classiq/_internals/__init__.py +20 -0
  3. classiq/_internals/authentication/authentication.py +11 -0
  4. classiq/analyzer/analyzer.py +12 -10
  5. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +1 -1
  6. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +1 -1
  7. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +1 -1
  8. classiq/applications/libraries/qmci_library.py +4 -9
  9. classiq/execution/execution_session.py +68 -7
  10. classiq/executor.py +14 -2
  11. classiq/interface/_version.py +1 -1
  12. classiq/interface/backend/backend_preferences.py +189 -0
  13. classiq/interface/backend/quantum_backend_providers.py +38 -0
  14. classiq/interface/debug_info/debug_info.py +22 -2
  15. classiq/interface/exceptions.py +16 -1
  16. classiq/interface/executor/execution_preferences.py +18 -0
  17. classiq/interface/generator/application_apis/chemistry_declarations.py +1 -177
  18. classiq/interface/generator/application_apis/combinatorial_optimization_declarations.py +0 -12
  19. classiq/interface/generator/application_apis/finance_declarations.py +8 -43
  20. classiq/interface/generator/application_apis/qsvm_declarations.py +0 -78
  21. classiq/interface/generator/builtin_api_builder.py +0 -3
  22. classiq/interface/generator/functions/__init__.py +0 -2
  23. classiq/interface/generator/functions/builtins/__init__.py +0 -15
  24. classiq/interface/generator/generated_circuit_data.py +2 -0
  25. classiq/interface/generator/hardware/hardware_data.py +37 -0
  26. classiq/interface/generator/model/constraints.py +18 -1
  27. classiq/interface/generator/model/preferences/preferences.py +53 -1
  28. classiq/interface/generator/model/quantum_register.py +1 -1
  29. classiq/interface/generator/quantum_program.py +10 -2
  30. classiq/interface/generator/transpiler_basis_gates.py +4 -0
  31. classiq/interface/generator/types/builtin_enum_declarations.py +136 -21
  32. classiq/interface/generator/types/enum_declaration.py +1 -3
  33. classiq/interface/generator/types/struct_declaration.py +1 -3
  34. classiq/interface/hardware.py +5 -0
  35. classiq/interface/ide/visual_model.py +1 -1
  36. classiq/interface/model/classical_parameter_declaration.py +6 -0
  37. classiq/interface/model/inplace_binary_operation.py +0 -14
  38. classiq/interface/model/model.py +1 -18
  39. classiq/interface/model/port_declaration.py +4 -2
  40. classiq/interface/model/quantum_function_declaration.py +19 -6
  41. classiq/interface/model/quantum_lambda_function.py +11 -1
  42. classiq/interface/model/quantum_variable_declaration.py +1 -1
  43. classiq/model_expansions/__init__.py +0 -0
  44. classiq/model_expansions/atomic_expression_functions_defs.py +250 -0
  45. classiq/model_expansions/capturing/__init__.py +0 -0
  46. classiq/model_expansions/capturing/captured_var_manager.py +50 -0
  47. classiq/model_expansions/capturing/mangling_utils.py +17 -0
  48. classiq/model_expansions/capturing/propagated_var_stack.py +180 -0
  49. classiq/model_expansions/closure.py +160 -0
  50. classiq/model_expansions/debug_flag.py +3 -0
  51. classiq/model_expansions/evaluators/__init__.py +0 -0
  52. classiq/model_expansions/evaluators/arg_type_match.py +160 -0
  53. classiq/model_expansions/evaluators/argument_types.py +42 -0
  54. classiq/model_expansions/evaluators/classical_expression.py +36 -0
  55. classiq/model_expansions/evaluators/control.py +144 -0
  56. classiq/model_expansions/evaluators/parameter_types.py +227 -0
  57. classiq/model_expansions/evaluators/quantum_type_utils.py +235 -0
  58. classiq/model_expansions/evaluators/type_type_match.py +90 -0
  59. classiq/model_expansions/expression_evaluator.py +125 -0
  60. classiq/model_expansions/expression_renamer.py +76 -0
  61. classiq/model_expansions/function_builder.py +192 -0
  62. classiq/model_expansions/generative_functions.py +101 -0
  63. classiq/model_expansions/interpreter.py +365 -0
  64. classiq/model_expansions/model_tables.py +105 -0
  65. classiq/model_expansions/quantum_operations/__init__.py +19 -0
  66. classiq/model_expansions/quantum_operations/bind.py +64 -0
  67. classiq/model_expansions/quantum_operations/classicalif.py +39 -0
  68. classiq/model_expansions/quantum_operations/control.py +235 -0
  69. classiq/model_expansions/quantum_operations/emitter.py +215 -0
  70. classiq/model_expansions/quantum_operations/expression_operation.py +218 -0
  71. classiq/model_expansions/quantum_operations/inplace_binary_operation.py +250 -0
  72. classiq/model_expansions/quantum_operations/invert.py +38 -0
  73. classiq/model_expansions/quantum_operations/power.py +74 -0
  74. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +174 -0
  75. classiq/model_expansions/quantum_operations/quantum_function_call.py +15 -0
  76. classiq/model_expansions/quantum_operations/repeat.py +33 -0
  77. classiq/model_expansions/quantum_operations/variable_decleration.py +28 -0
  78. classiq/model_expansions/quantum_operations/within_apply.py +46 -0
  79. classiq/model_expansions/scope.py +226 -0
  80. classiq/model_expansions/scope_initialization.py +136 -0
  81. classiq/model_expansions/sympy_conversion/__init__.py +0 -0
  82. classiq/model_expansions/sympy_conversion/arithmetics.py +49 -0
  83. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +150 -0
  84. classiq/model_expansions/sympy_conversion/sympy_to_python.py +113 -0
  85. classiq/model_expansions/utils/__init__.py +0 -0
  86. classiq/model_expansions/utils/counted_name_allocator.py +11 -0
  87. classiq/model_expansions/visitors/__init__.py +0 -0
  88. classiq/model_expansions/visitors/boolean_expression_transformers.py +214 -0
  89. classiq/model_expansions/visitors/variable_references.py +115 -0
  90. classiq/qmod/__init__.py +1 -3
  91. classiq/qmod/builtins/enums.py +33 -2
  92. classiq/qmod/builtins/functions/__init__.py +251 -0
  93. classiq/qmod/builtins/functions/amplitude_estimation.py +26 -0
  94. classiq/qmod/builtins/functions/arithmetic.py +68 -0
  95. classiq/qmod/builtins/functions/benchmarking.py +8 -0
  96. classiq/qmod/builtins/functions/chemistry.py +91 -0
  97. classiq/qmod/builtins/functions/discrete_sine_cosine_transform.py +105 -0
  98. classiq/qmod/builtins/functions/exponentiation.py +110 -0
  99. classiq/qmod/builtins/functions/finance.py +34 -0
  100. classiq/qmod/builtins/functions/grover.py +179 -0
  101. classiq/qmod/builtins/functions/hea.py +59 -0
  102. classiq/qmod/builtins/functions/linear_pauli_rotation.py +65 -0
  103. classiq/qmod/builtins/functions/modular_exponentiation.py +137 -0
  104. classiq/qmod/builtins/functions/operators.py +22 -0
  105. classiq/qmod/builtins/functions/qaoa_penalty.py +116 -0
  106. classiq/qmod/builtins/functions/qft.py +23 -0
  107. classiq/qmod/builtins/functions/qpe.py +39 -0
  108. classiq/qmod/builtins/functions/qsvm.py +24 -0
  109. classiq/qmod/builtins/functions/qsvt.py +136 -0
  110. classiq/qmod/builtins/functions/standard_gates.py +739 -0
  111. classiq/qmod/builtins/functions/state_preparation.py +356 -0
  112. classiq/qmod/builtins/functions/swap_test.py +25 -0
  113. classiq/qmod/builtins/structs.py +50 -28
  114. classiq/qmod/cparam.py +64 -0
  115. classiq/qmod/create_model_function.py +190 -0
  116. classiq/qmod/declaration_inferrer.py +52 -81
  117. classiq/qmod/expression_query.py +16 -0
  118. classiq/qmod/generative.py +48 -0
  119. classiq/qmod/model_state_container.py +1 -2
  120. classiq/qmod/native/pretty_printer.py +7 -11
  121. classiq/qmod/pretty_print/pretty_printer.py +7 -11
  122. classiq/qmod/python_classical_type.py +67 -0
  123. classiq/qmod/qfunc.py +19 -4
  124. classiq/qmod/qmod_parameter.py +15 -64
  125. classiq/qmod/qmod_variable.py +28 -46
  126. classiq/qmod/quantum_callable.py +1 -1
  127. classiq/qmod/quantum_expandable.py +10 -4
  128. classiq/qmod/quantum_function.py +22 -40
  129. classiq/qmod/semantics/error_manager.py +22 -10
  130. classiq/qmod/semantics/static_semantics_visitor.py +10 -12
  131. classiq/qmod/semantics/validation/types_validation.py +6 -7
  132. classiq/qmod/utilities.py +2 -2
  133. classiq/qmod/write_qmod.py +14 -0
  134. classiq/show.py +10 -0
  135. classiq/synthesis.py +46 -2
  136. {classiq-0.45.0.dist-info → classiq-0.46.0.dist-info}/METADATA +1 -1
  137. {classiq-0.45.0.dist-info → classiq-0.46.0.dist-info}/RECORD +138 -74
  138. classiq/interface/generator/functions/builtins/core_library/__init__.py +0 -16
  139. classiq/interface/generator/functions/builtins/core_library/atomic_quantum_functions.py +0 -710
  140. classiq/interface/generator/functions/builtins/core_library/exponentiation_functions.py +0 -105
  141. classiq/interface/generator/functions/builtins/open_lib_functions.py +0 -2489
  142. classiq/interface/generator/functions/builtins/quantum_operators.py +0 -24
  143. classiq/interface/generator/types/builtin_struct_declarations/__init__.py +0 -1
  144. classiq/interface/generator/types/builtin_struct_declarations/pauli_struct_declarations.py +0 -21
  145. classiq/qmod/builtins/functions.py +0 -1029
  146. {classiq-0.45.0.dist-info → classiq-0.46.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,250 @@
1
+ from enum import Enum
2
+ from typing import Any, Callable, List, Mapping, Union
3
+
4
+ from sympy import Eq, Expr, Number, Piecewise, Symbol
5
+
6
+ from classiq.interface.exceptions import (
7
+ ClassiqExpansionError,
8
+ ClassiqInternalExpansionError,
9
+ )
10
+ from classiq.interface.generator.expressions.expression_types import (
11
+ ExpressionValue,
12
+ QmodStructInstance,
13
+ )
14
+ from classiq.interface.generator.expressions.qmod_qscalar_proxy import QmodQNumProxy
15
+ from classiq.interface.generator.expressions.qmod_qstruct_proxy import QmodQStructProxy
16
+ from classiq.interface.generator.expressions.qmod_sized_proxy import QmodSizedProxy
17
+ from classiq.interface.generator.expressions.type_proxy import TypeProxy
18
+ from classiq.interface.generator.functions.classical_function_declaration import (
19
+ ClassicalFunctionDeclaration,
20
+ )
21
+ from classiq.interface.generator.functions.classical_type import (
22
+ Bool,
23
+ ClassicalList,
24
+ ClassicalType,
25
+ OpaqueHandle,
26
+ QmodPyObject,
27
+ Real,
28
+ StructMetaType,
29
+ )
30
+ from classiq.interface.generator.functions.type_name import TypeName
31
+
32
+ from classiq.model_expansions.model_tables import (
33
+ HandleIdentifier,
34
+ HandleTable,
35
+ SymbolTable,
36
+ )
37
+ from classiq.model_expansions.sympy_conversion.arithmetics import (
38
+ BitwiseAnd,
39
+ BitwiseNot,
40
+ BitwiseOr,
41
+ BitwiseXor,
42
+ LogicalXor,
43
+ )
44
+ from classiq.model_expansions.sympy_conversion.expression_to_sympy import (
45
+ MISSING_SLICE_VALUE_PLACEHOLDER,
46
+ )
47
+ from classiq.model_expansions.sympy_conversion.sympy_to_python import (
48
+ sympy_to_python,
49
+ )
50
+
51
+
52
+ def qmod_val_to_python(val: ExpressionValue, qmod_type: ClassicalType) -> Any:
53
+ if isinstance(qmod_type, TypeName):
54
+ if (
55
+ isinstance(val, QmodStructInstance)
56
+ and val.struct_declaration == SymbolTable.type_table[qmod_type.name]
57
+ ):
58
+ return {
59
+ field_name: qmod_val_to_python(val.fields[field_name], field_type)
60
+ for field_name, field_type in val.struct_declaration.variables.items()
61
+ }
62
+
63
+ if isinstance(val, Enum):
64
+ return val
65
+
66
+ elif isinstance(qmod_type, ClassicalList):
67
+ if isinstance(val, list):
68
+ return [qmod_val_to_python(elem, qmod_type.element_type) for elem in val]
69
+
70
+ elif isinstance(qmod_type, OpaqueHandle):
71
+ if isinstance(val, HandleIdentifier):
72
+ return HandleTable.get_handle_object(val)
73
+
74
+ elif isinstance(val, Expr):
75
+ return sympy_to_python(val)
76
+
77
+ elif isinstance(qmod_type, Real):
78
+ if isinstance(val, (float, int)):
79
+ return val
80
+
81
+ elif isinstance(qmod_type, Bool):
82
+ if isinstance(val, bool):
83
+ return val
84
+
85
+ elif isinstance(qmod_type, StructMetaType):
86
+ if isinstance(val, TypeProxy):
87
+ return val.struct_declaration
88
+
89
+ elif isinstance(val, int): # other scalars are represented as int
90
+ return val
91
+
92
+ raise ClassiqInternalExpansionError(
93
+ f"Bad value {val!r} of type {type(val)!r} for {qmod_type!r}"
94
+ )
95
+
96
+
97
+ def python_val_to_qmod(val: Any, qmod_type: ClassicalType) -> ExpressionValue:
98
+ if isinstance(qmod_type, TypeName):
99
+ if qmod_type.name in SymbolTable.enum_table:
100
+ return val
101
+
102
+ struct_decl = SymbolTable.type_table[qmod_type.name]
103
+ if not isinstance(val, Mapping):
104
+ raise ClassiqInternalExpansionError(
105
+ f"Bad value for struct {struct_decl.name}"
106
+ )
107
+ qmod_dict = {
108
+ field_name: python_val_to_qmod(val[field_name], field_type)
109
+ for field_name, field_type in struct_decl.variables.items()
110
+ }
111
+ return QmodStructInstance(struct_decl, qmod_dict)
112
+
113
+ if isinstance(qmod_type, ClassicalList):
114
+ if not isinstance(val, list):
115
+ raise ClassiqInternalExpansionError("Bad value for list")
116
+ return [python_val_to_qmod(elem, qmod_type.element_type) for elem in val]
117
+
118
+ if isinstance(qmod_type, OpaqueHandle):
119
+ if not isinstance(val, QmodPyObject):
120
+ raise ClassiqInternalExpansionError("Bad value opaque handle")
121
+ return HandleTable.set_handle_object(val)
122
+
123
+ return val
124
+
125
+
126
+ def python_call_wrapper(func: Callable, *args: ExpressionValue) -> Any:
127
+ func_decl = ClassicalFunctionDeclaration.FOREIGN_FUNCTION_DECLARATIONS[
128
+ func.__name__
129
+ ]
130
+ python_args = [
131
+ qmod_val_to_python(args[idx], param_type.classical_type)
132
+ for idx, param_type in enumerate(func_decl.param_decls)
133
+ ]
134
+ assert func_decl.return_type is not None
135
+ return python_val_to_qmod(func(*python_args), func_decl.return_type)
136
+
137
+
138
+ def struct_literal(struct_type_symbol: Symbol, **kwargs: Any) -> QmodStructInstance:
139
+ return QmodStructInstance(
140
+ SymbolTable.type_table[struct_type_symbol.name],
141
+ {field: sympy_to_python(field_value) for field, field_value in kwargs.items()},
142
+ )
143
+
144
+
145
+ def get_field(
146
+ proxy: Union[QmodSizedProxy, QmodStructInstance, QmodQStructProxy, list],
147
+ field: str,
148
+ ) -> ExpressionValue:
149
+ if isinstance(proxy, Symbol) and not isinstance(proxy, QmodSizedProxy):
150
+ raise ClassiqExpansionError(
151
+ f"Cannot evaluate '{proxy}.{field}': Variable {str(proxy)!r} is not "
152
+ f"initialized"
153
+ )
154
+ if isinstance(proxy, list):
155
+ if field != "len":
156
+ raise ClassiqExpansionError(
157
+ f"List {str(proxy)!r} has no attribute {field!r}. "
158
+ f"Available attributes: len"
159
+ )
160
+ return len(proxy)
161
+ if field not in proxy.fields:
162
+ if isinstance(proxy, (QmodStructInstance, QmodQStructProxy)):
163
+ property_name = "field"
164
+ else:
165
+ property_name = "attribute"
166
+ suffix = (
167
+ f". Available {property_name}s: {', '.join(proxy.fields.keys())}"
168
+ if len(proxy.fields) > 0
169
+ else ""
170
+ )
171
+ proxy_str = proxy.__name__ if isinstance(proxy, type) else f"{str(proxy)!r}"
172
+ raise ClassiqExpansionError(
173
+ f"{proxy.type_name} {proxy_str} has no {property_name} {field!r}{suffix}"
174
+ )
175
+ return proxy.fields[field]
176
+
177
+
178
+ def get_type(struct_type: Symbol) -> TypeProxy:
179
+ return TypeProxy(SymbolTable.type_table[struct_type.name])
180
+
181
+
182
+ def _unwrap_sympy_numeric(n: Any) -> Any:
183
+ if not isinstance(n, Number) or not n.is_constant():
184
+ return n
185
+ if n.is_Integer:
186
+ return int(n)
187
+ return float(n)
188
+
189
+
190
+ def do_div(lhs: Any, rhs: Any) -> Any:
191
+ lhs = _unwrap_sympy_numeric(lhs)
192
+ rhs = _unwrap_sympy_numeric(rhs)
193
+ return lhs / rhs
194
+
195
+
196
+ def do_subscript(value: Any, index: Any) -> Any:
197
+ if not isinstance(value, list) or not isinstance(index, QmodQNumProxy):
198
+ if isinstance(index, (QmodSizedProxy, QmodStructInstance)):
199
+ raise ClassiqExpansionError(
200
+ f"Subscript {value}[{index}] is not supported. Supported subscripts "
201
+ f"include:\n"
202
+ f"\t1. `qbv[idx]`, where `qbv` is a quantum array and `idx` is a "
203
+ f"classical integer.\n"
204
+ f"\t2. `l[n]`, where `l` is a list of classical real numbers and `n` "
205
+ f"is a classical or quantum integer."
206
+ )
207
+ return value[index]
208
+ if index.is_signed or index.fraction_digits > 0:
209
+ raise ClassiqExpansionError(
210
+ "Quantum numeric subscript must be an unsigned integer (is_signed=False, "
211
+ "fraction_digits=0)"
212
+ )
213
+ if len(value) != 2**index.size:
214
+ raise ClassiqExpansionError(
215
+ f"Quantum numeric subscript size mismatch: The quantum numeric has "
216
+ f"{index.size} qubits but the list size is {len(value)} != 2**{index.size}"
217
+ )
218
+ return Piecewise(
219
+ *[(item, Eq(index, idx)) for idx, item in enumerate(value[:-1])],
220
+ (value[-1], True),
221
+ )
222
+
223
+
224
+ def do_slice(value: Any, lower: Any, upper: Any) -> Any:
225
+ if isinstance(lower, Symbol) and str(lower) == MISSING_SLICE_VALUE_PLACEHOLDER:
226
+ lower = None
227
+ if isinstance(upper, Symbol) and str(upper) == MISSING_SLICE_VALUE_PLACEHOLDER:
228
+ upper = None
229
+ return do_subscript(value, slice(lower, upper))
230
+
231
+
232
+ CORE_LIB_FUNCTIONS_LIST: List[Callable] = [
233
+ print,
234
+ sum,
235
+ struct_literal,
236
+ get_field,
237
+ get_type,
238
+ do_div,
239
+ do_slice,
240
+ do_subscript,
241
+ BitwiseAnd,
242
+ BitwiseXor,
243
+ BitwiseNot,
244
+ BitwiseOr,
245
+ LogicalXor,
246
+ ]
247
+
248
+ ATOMIC_EXPRESSION_FUNCTIONS = {
249
+ **{core_func.__name__: core_func for core_func in CORE_LIB_FUNCTIONS_LIST},
250
+ }
File without changes
@@ -0,0 +1,50 @@
1
+ from typing import Dict, List
2
+
3
+ from classiq.interface.generator.functions.port_declaration import (
4
+ PortDeclarationDirection,
5
+ )
6
+ from classiq.interface.model.port_declaration import PortDeclaration
7
+
8
+
9
+ def update_captured_vars(captured_vars: List[PortDeclaration]) -> List[PortDeclaration]:
10
+ if not captured_vars:
11
+ return []
12
+ return _update_declarations(captured_vars)
13
+
14
+
15
+ def _update_declarations(
16
+ captured_vars: List[PortDeclaration],
17
+ ) -> List[PortDeclaration]:
18
+ updated_vars: Dict[str, PortDeclaration] = {
19
+ var.name: PortDeclaration(
20
+ name=var.name,
21
+ quantum_type=var.quantum_type,
22
+ direction=PortDeclarationDirection.Inout,
23
+ )
24
+ for var in captured_vars
25
+ }
26
+ for var in captured_vars:
27
+ updated_vars[var.name].direction = _update_var_declaration(
28
+ var.direction, updated_vars[var.name].direction
29
+ )
30
+ return list(updated_vars.values())
31
+
32
+
33
+ def _update_var_declaration(
34
+ stmt_direction: PortDeclarationDirection,
35
+ existing_direction: PortDeclarationDirection,
36
+ ) -> PortDeclarationDirection:
37
+ if stmt_direction is PortDeclarationDirection.Input:
38
+ if existing_direction is PortDeclarationDirection.Output:
39
+ # This will fail semantically because the inout variable is not initialized.
40
+ # We will get rid of this scenario by unifying variable declaration and allocation.
41
+ return PortDeclarationDirection.Inout
42
+ else:
43
+ return PortDeclarationDirection.Input
44
+ elif stmt_direction is PortDeclarationDirection.Output:
45
+ if existing_direction is PortDeclarationDirection.Input:
46
+ return PortDeclarationDirection.Inout
47
+ else:
48
+ return PortDeclarationDirection.Output
49
+ else:
50
+ return PortDeclarationDirection.Inout
@@ -0,0 +1,17 @@
1
+ import re
2
+
3
+ from classiq.interface.generator.compiler_keywords import CAPTURE_SUFFIX
4
+
5
+ IDENTIFIER_PATTERN = r"[a-zA-Z_][a-zA-Z0-9_]*"
6
+ CAPTURE_PATTERN = re.compile(
7
+ rf"({IDENTIFIER_PATTERN}){CAPTURE_SUFFIX}{IDENTIFIER_PATTERN}__"
8
+ )
9
+
10
+
11
+ def mangle_captured_var_name(var_name: str, defining_function: str) -> str:
12
+ return f"{var_name}{CAPTURE_SUFFIX}{defining_function}__"
13
+
14
+
15
+ def demangle_name(name: str) -> str:
16
+ match = re.match(CAPTURE_PATTERN, name)
17
+ return match.group(1) if match else name
@@ -0,0 +1,180 @@
1
+ from contextlib import contextmanager
2
+ from dataclasses import dataclass
3
+ from typing import Dict, Iterable, Iterator, List, Sequence
4
+
5
+ from classiq.interface.exceptions import (
6
+ ClassiqExpansionError,
7
+ ClassiqInternalExpansionError,
8
+ )
9
+ from classiq.interface.generator.functions.port_declaration import (
10
+ PortDeclarationDirection,
11
+ )
12
+ from classiq.interface.model.handle_binding import HANDLE_ID_SEPARATOR, HandleBinding
13
+ from classiq.interface.model.port_declaration import PortDeclaration
14
+ from classiq.interface.model.quantum_function_call import ArgValue
15
+ from classiq.interface.model.quantum_statement import QuantumOperation
16
+
17
+ from classiq.model_expansions.capturing.mangling_utils import mangle_captured_var_name
18
+ from classiq.model_expansions.closure import FunctionClosure
19
+ from classiq.model_expansions.function_builder import OperationBuilder
20
+ from classiq.model_expansions.scope import QuantumSymbol, Scope
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class PropagatedVariable:
25
+ symbol: QuantumSymbol
26
+ direction: PortDeclarationDirection
27
+ defining_function: str
28
+
29
+ @property
30
+ def name(self) -> str:
31
+ return self.symbol.handle.name
32
+
33
+
34
+ class PropagatedVarStack:
35
+ def __init__(self, scope: Scope, builder: OperationBuilder) -> None:
36
+ # We use dictionary instead of set to maintain the order of insertion
37
+ self._stack: List[Dict[PropagatedVariable, None]] = [dict()]
38
+ self._current_scope = scope
39
+ self._builder = builder
40
+ self._to_mangle: Dict[PropagatedVariable, str] = dict()
41
+
42
+ def set_scope(self, scope: Scope) -> None:
43
+ self._current_scope = scope
44
+
45
+ @contextmanager
46
+ def capture_variables(self, op: QuantumOperation) -> Iterator[None]:
47
+ self._stack.append(dict())
48
+ yield
49
+ self._post_handle_propagated_vars(op)
50
+
51
+ def _post_handle_propagated_vars(self, qop: QuantumOperation) -> None:
52
+ self._halt_propagation_for_vars_in_scope()
53
+ currently_captured_vars = self._get_captured_vars(qop)
54
+ self._stack[-1].update(currently_captured_vars)
55
+ self._update_port_declarations_for_captured_vars()
56
+
57
+ def _halt_propagation_for_vars_in_scope(self) -> None:
58
+ currently_propagated = self._stack.pop()
59
+ self._stack[-1].update(
60
+ {var: None for var in currently_propagated if self._should_propagate(var)}
61
+ )
62
+
63
+ def _should_propagate(self, var: PropagatedVariable) -> bool:
64
+ # The second case is in case the captured variable was defined in another function,
65
+ # but the current scope has a variable with the same name
66
+ return (
67
+ var.name not in self._current_scope.data
68
+ or isinstance(self._builder.current_operation, FunctionClosure)
69
+ and var.defining_function != self._builder.current_function.name
70
+ )
71
+
72
+ def _get_captured_vars(
73
+ self, qop: QuantumOperation
74
+ ) -> Dict[PropagatedVariable, None]:
75
+ input_captured = self._get_captured_vars_with_direction(
76
+ qop.inputs,
77
+ (
78
+ PortDeclarationDirection.Input
79
+ if not self._builder.is_compute_context()
80
+ else PortDeclarationDirection.Inout
81
+ ),
82
+ )
83
+ output_captured = self._get_captured_vars_with_direction(
84
+ qop.outputs,
85
+ (
86
+ PortDeclarationDirection.Output
87
+ if not self._builder.is_compute_context()
88
+ else PortDeclarationDirection.Inout
89
+ ),
90
+ )
91
+ inout_captured = self._get_captured_vars_with_direction(
92
+ qop.inouts, PortDeclarationDirection.Inout
93
+ )
94
+ return inout_captured | input_captured | output_captured
95
+
96
+ def _get_captured_vars_with_direction(
97
+ self,
98
+ variables: Iterable[HandleBinding],
99
+ direction: PortDeclarationDirection,
100
+ ) -> Dict[PropagatedVariable, None]:
101
+ return {
102
+ self._get_captured_var_with_direction(var.name, direction): None
103
+ for var in variables
104
+ if self._is_captured(var.name)
105
+ }
106
+
107
+ def _get_captured_var_with_direction(
108
+ self, var_name: str, direction: PortDeclarationDirection
109
+ ) -> PropagatedVariable:
110
+ defining_function = self._current_scope[var_name].defining_function
111
+ if defining_function is None:
112
+ raise ClassiqInternalExpansionError
113
+ return PropagatedVariable(
114
+ symbol=self._current_scope[var_name].as_type(QuantumSymbol),
115
+ direction=direction,
116
+ defining_function=defining_function.name,
117
+ )
118
+
119
+ def _is_captured(self, var_name: str) -> bool:
120
+ return (
121
+ self._current_scope.parent is not None
122
+ and var_name in self._current_scope.parent
123
+ and var_name not in self._current_scope.data
124
+ )
125
+
126
+ def _update_port_declarations_for_captured_vars(self) -> None:
127
+ self._builder.add_captured_vars(
128
+ PortDeclaration(
129
+ name=self._to_mangle.get(var, var.name),
130
+ direction=var.direction,
131
+ quantum_type=var.symbol.quantum_type,
132
+ )
133
+ for var in self._stack[-1]
134
+ )
135
+
136
+ def get_propagated_variables(self) -> List[HandleBinding]:
137
+ propagated_var_names: List[str] = [
138
+ self._get_propagated_var_name(var) for var in self._stack[-1]
139
+ ]
140
+ return [
141
+ HandleBinding(name=name) for name in dict.fromkeys(propagated_var_names)
142
+ ]
143
+
144
+ def _get_propagated_var_name(self, var: PropagatedVariable) -> str:
145
+ if (
146
+ var.defining_function == self._builder.current_function.name
147
+ or self._no_name_conflict(var)
148
+ ):
149
+ handle_name = var.name
150
+ if var in self._to_mangle:
151
+ self._to_mangle.pop(var)
152
+ else:
153
+ handle_name = mangle_captured_var_name(var.name, var.defining_function)
154
+ self._to_mangle[var] = handle_name
155
+ return handle_name
156
+
157
+ def _no_name_conflict(self, var: PropagatedVariable) -> bool:
158
+ return var.name not in self._builder.current_function.colliding_variables
159
+
160
+
161
+ def validate_args_are_not_propagated(
162
+ args: Sequence[ArgValue], captured_vars: Sequence[HandleBinding]
163
+ ) -> None:
164
+ if not captured_vars:
165
+ return
166
+ captured_var_names = {var.name for var in captured_vars}
167
+ arg_names = {
168
+ demangle_suffixes(arg.name) for arg in args if isinstance(arg, HandleBinding)
169
+ }
170
+ if not captured_var_names.isdisjoint(arg_names):
171
+ vars_msg = f"Explicitly passed variables: {arg_names}, captured variables: {captured_var_names}"
172
+ raise ClassiqExpansionError(
173
+ f"Cannot capture variables that are explicitly passed as arguments. "
174
+ f"{vars_msg}"
175
+ )
176
+
177
+
178
+ # TODO this is not a good long-term solution
179
+ def demangle_suffixes(name: str) -> str:
180
+ return name.split(HANDLE_ID_SEPARATOR)[0]
@@ -0,0 +1,160 @@
1
+ from collections import defaultdict
2
+ from dataclasses import dataclass, field
3
+ from functools import cached_property
4
+ from typing import Any, Dict, List, Optional, Sequence, Set, Union
5
+
6
+ from typing_extensions import Self
7
+
8
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
9
+ from classiq.interface.generator.visitor import Visitor
10
+ from classiq.interface.model.port_declaration import PortDeclaration
11
+ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
12
+ from classiq.interface.model.quantum_function_declaration import (
13
+ NamedParamsQuantumFunctionDeclaration,
14
+ PositionalArg,
15
+ )
16
+ from classiq.interface.model.quantum_statement import QuantumStatement
17
+ from classiq.interface.model.variable_declaration_statement import (
18
+ VariableDeclarationStatement,
19
+ )
20
+
21
+ from classiq.model_expansions.expression_renamer import ExpressionRenamer
22
+ from classiq.model_expansions.scope import Scope
23
+ from classiq.qmod.builtins.functions import permute
24
+ from classiq.qmod.quantum_function import GenerativeQFunc
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class Closure:
29
+ name: str
30
+ blocks: Dict[str, Sequence[QuantumStatement]]
31
+ scope: Scope
32
+ positional_arg_declarations: Sequence[PositionalArg] = tuple()
33
+
34
+ @cached_property
35
+ def port_declarations(self) -> Dict[str, PortDeclaration]:
36
+ return {
37
+ param.name: param
38
+ for param in self.positional_arg_declarations
39
+ if isinstance(param, PortDeclaration)
40
+ }
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class FunctionClosure(Closure):
45
+ is_lambda: bool = False
46
+ is_atomic: bool = False
47
+ signature_scope: Scope = field(default_factory=Scope)
48
+
49
+ @property
50
+ def body(self) -> Sequence[QuantumStatement]:
51
+ if self.name == permute.func_decl.name:
52
+ # permute is an old Qmod "generative" function that doesn't have a body
53
+ return []
54
+ return self.blocks["body"]
55
+
56
+ @cached_property
57
+ def colliding_variables(self) -> Set[str]:
58
+ # Note that this has to be accessed after adding the parameters from the signature and not during
59
+ # initialization
60
+ return VariableCollector(self.scope).get_colliding_variables(self.body)
61
+
62
+ @classmethod
63
+ def create(
64
+ cls,
65
+ name: str,
66
+ scope: Scope,
67
+ body: Optional[Sequence[QuantumStatement]] = None,
68
+ positional_arg_declarations: Sequence[PositionalArg] = tuple(),
69
+ expr_renamer: Optional[ExpressionRenamer] = None,
70
+ is_lambda: bool = False,
71
+ is_atomic: bool = False,
72
+ **kwargs: Any,
73
+ ) -> Self:
74
+ if expr_renamer:
75
+ positional_arg_declarations = (
76
+ expr_renamer.rename_positional_arg_declarations(
77
+ positional_arg_declarations
78
+ )
79
+ )
80
+ if body is not None:
81
+ body = expr_renamer.visit(body)
82
+
83
+ blocks = {"body": body} if body is not None else {}
84
+ return cls(
85
+ name,
86
+ blocks,
87
+ scope,
88
+ positional_arg_declarations,
89
+ is_lambda,
90
+ is_atomic,
91
+ **kwargs,
92
+ )
93
+
94
+ def with_new_declaration(
95
+ self, declaration: NamedParamsQuantumFunctionDeclaration
96
+ ) -> Self:
97
+ fields: dict = self.__dict__ | {
98
+ "positional_arg_declarations": declaration.positional_arg_declarations
99
+ }
100
+ return type(self)(**fields)
101
+
102
+
103
+ @dataclass(frozen=True)
104
+ class GenerativeFunctionClosure(FunctionClosure):
105
+ generative_function: GenerativeQFunc = None # type:ignore[assignment]
106
+
107
+
108
+ NestedFunctionClosureT = Union[FunctionClosure, List["NestedFunctionClosureT"]]
109
+
110
+
111
+ class VariableCollector(Visitor):
112
+ def __init__(self, function_scope: Scope) -> None:
113
+ self._function_scope = function_scope
114
+ self._variables: defaultdict[str, Set[Optional[str]]] = defaultdict(set)
115
+ for var in self._function_scope.data:
116
+ defining_function = self._function_scope[var].defining_function
117
+ if defining_function is not None:
118
+ self._variables[var].add(defining_function.name)
119
+
120
+ def get_colliding_variables(self, body: Sequence[QuantumStatement]) -> Set[str]:
121
+ self.visit(body)
122
+ return {
123
+ var
124
+ for var, defining_functions in self._variables.items()
125
+ if len(defining_functions) > 1
126
+ }
127
+
128
+ def visit_VariableDeclarationStatement(
129
+ self, node: VariableDeclarationStatement
130
+ ) -> None:
131
+ self._variables[node.name].add(None)
132
+
133
+ def visit_QuantumFunctionCall(self, node: QuantumFunctionCall) -> None:
134
+ # The else case corresponds to operand identifiers. In case of operand identifiers, we scan
135
+ # the whole list of operands because we can't evaluate the index yet.
136
+ identifier = (
137
+ node.function if isinstance(node.function, str) else node.function.name
138
+ )
139
+ self._add_variables(self._function_scope[identifier].value)
140
+
141
+ def _add_variables(self, evaluated: NestedFunctionClosureT) -> None:
142
+ if isinstance(evaluated, list):
143
+ for elem in evaluated:
144
+ self._add_variables(elem)
145
+ return
146
+ if not isinstance(evaluated, FunctionClosure):
147
+ raise ClassiqInternalExpansionError
148
+ self._add_variables_from_closure(evaluated)
149
+
150
+ def _add_variables_from_closure(self, closure: FunctionClosure) -> None:
151
+ if not closure.is_lambda:
152
+ return
153
+ lambda_environment = closure.scope.parent
154
+ if lambda_environment is None:
155
+ raise ClassiqInternalExpansionError
156
+
157
+ for var in lambda_environment.iter_without_top_level():
158
+ defining_function = lambda_environment[var].defining_function
159
+ if defining_function is not None:
160
+ self._variables[var].add(defining_function.name)
@@ -0,0 +1,3 @@
1
+ from contextvars import ContextVar
2
+
3
+ debug_mode: ContextVar[bool] = ContextVar("debug_mode", default=False)
File without changes