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.
- classiq/__init__.py +0 -1
- classiq/_internals/__init__.py +20 -0
- classiq/_internals/authentication/authentication.py +11 -0
- classiq/analyzer/analyzer.py +12 -10
- classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +1 -1
- classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +1 -1
- classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +1 -1
- classiq/applications/libraries/qmci_library.py +4 -9
- classiq/execution/execution_session.py +68 -7
- classiq/executor.py +14 -2
- classiq/interface/_version.py +1 -1
- classiq/interface/backend/backend_preferences.py +189 -0
- classiq/interface/backend/quantum_backend_providers.py +38 -0
- classiq/interface/debug_info/debug_info.py +22 -2
- classiq/interface/exceptions.py +16 -1
- classiq/interface/executor/execution_preferences.py +18 -0
- classiq/interface/generator/application_apis/chemistry_declarations.py +1 -177
- classiq/interface/generator/application_apis/combinatorial_optimization_declarations.py +0 -12
- classiq/interface/generator/application_apis/finance_declarations.py +8 -43
- classiq/interface/generator/application_apis/qsvm_declarations.py +0 -78
- classiq/interface/generator/builtin_api_builder.py +0 -3
- classiq/interface/generator/functions/__init__.py +0 -2
- classiq/interface/generator/functions/builtins/__init__.py +0 -15
- classiq/interface/generator/generated_circuit_data.py +2 -0
- classiq/interface/generator/hardware/hardware_data.py +37 -0
- classiq/interface/generator/model/constraints.py +18 -1
- classiq/interface/generator/model/preferences/preferences.py +53 -1
- classiq/interface/generator/model/quantum_register.py +1 -1
- classiq/interface/generator/quantum_program.py +10 -2
- classiq/interface/generator/transpiler_basis_gates.py +4 -0
- classiq/interface/generator/types/builtin_enum_declarations.py +136 -21
- classiq/interface/generator/types/enum_declaration.py +1 -3
- classiq/interface/generator/types/struct_declaration.py +1 -3
- classiq/interface/hardware.py +5 -0
- classiq/interface/ide/visual_model.py +1 -1
- classiq/interface/model/classical_parameter_declaration.py +6 -0
- classiq/interface/model/inplace_binary_operation.py +0 -14
- classiq/interface/model/model.py +1 -18
- classiq/interface/model/port_declaration.py +4 -2
- classiq/interface/model/quantum_function_declaration.py +19 -6
- classiq/interface/model/quantum_lambda_function.py +11 -1
- classiq/interface/model/quantum_variable_declaration.py +1 -1
- classiq/model_expansions/__init__.py +0 -0
- classiq/model_expansions/atomic_expression_functions_defs.py +250 -0
- classiq/model_expansions/capturing/__init__.py +0 -0
- classiq/model_expansions/capturing/captured_var_manager.py +50 -0
- classiq/model_expansions/capturing/mangling_utils.py +17 -0
- classiq/model_expansions/capturing/propagated_var_stack.py +180 -0
- classiq/model_expansions/closure.py +160 -0
- classiq/model_expansions/debug_flag.py +3 -0
- classiq/model_expansions/evaluators/__init__.py +0 -0
- classiq/model_expansions/evaluators/arg_type_match.py +160 -0
- classiq/model_expansions/evaluators/argument_types.py +42 -0
- classiq/model_expansions/evaluators/classical_expression.py +36 -0
- classiq/model_expansions/evaluators/control.py +144 -0
- classiq/model_expansions/evaluators/parameter_types.py +227 -0
- classiq/model_expansions/evaluators/quantum_type_utils.py +235 -0
- classiq/model_expansions/evaluators/type_type_match.py +90 -0
- classiq/model_expansions/expression_evaluator.py +125 -0
- classiq/model_expansions/expression_renamer.py +76 -0
- classiq/model_expansions/function_builder.py +192 -0
- classiq/model_expansions/generative_functions.py +101 -0
- classiq/model_expansions/interpreter.py +365 -0
- classiq/model_expansions/model_tables.py +105 -0
- classiq/model_expansions/quantum_operations/__init__.py +19 -0
- classiq/model_expansions/quantum_operations/bind.py +64 -0
- classiq/model_expansions/quantum_operations/classicalif.py +39 -0
- classiq/model_expansions/quantum_operations/control.py +235 -0
- classiq/model_expansions/quantum_operations/emitter.py +215 -0
- classiq/model_expansions/quantum_operations/expression_operation.py +218 -0
- classiq/model_expansions/quantum_operations/inplace_binary_operation.py +250 -0
- classiq/model_expansions/quantum_operations/invert.py +38 -0
- classiq/model_expansions/quantum_operations/power.py +74 -0
- classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +174 -0
- classiq/model_expansions/quantum_operations/quantum_function_call.py +15 -0
- classiq/model_expansions/quantum_operations/repeat.py +33 -0
- classiq/model_expansions/quantum_operations/variable_decleration.py +28 -0
- classiq/model_expansions/quantum_operations/within_apply.py +46 -0
- classiq/model_expansions/scope.py +226 -0
- classiq/model_expansions/scope_initialization.py +136 -0
- classiq/model_expansions/sympy_conversion/__init__.py +0 -0
- classiq/model_expansions/sympy_conversion/arithmetics.py +49 -0
- classiq/model_expansions/sympy_conversion/expression_to_sympy.py +150 -0
- classiq/model_expansions/sympy_conversion/sympy_to_python.py +113 -0
- classiq/model_expansions/utils/__init__.py +0 -0
- classiq/model_expansions/utils/counted_name_allocator.py +11 -0
- classiq/model_expansions/visitors/__init__.py +0 -0
- classiq/model_expansions/visitors/boolean_expression_transformers.py +214 -0
- classiq/model_expansions/visitors/variable_references.py +115 -0
- classiq/qmod/__init__.py +1 -3
- classiq/qmod/builtins/enums.py +33 -2
- classiq/qmod/builtins/functions/__init__.py +251 -0
- classiq/qmod/builtins/functions/amplitude_estimation.py +26 -0
- classiq/qmod/builtins/functions/arithmetic.py +68 -0
- classiq/qmod/builtins/functions/benchmarking.py +8 -0
- classiq/qmod/builtins/functions/chemistry.py +91 -0
- classiq/qmod/builtins/functions/discrete_sine_cosine_transform.py +105 -0
- classiq/qmod/builtins/functions/exponentiation.py +110 -0
- classiq/qmod/builtins/functions/finance.py +34 -0
- classiq/qmod/builtins/functions/grover.py +179 -0
- classiq/qmod/builtins/functions/hea.py +59 -0
- classiq/qmod/builtins/functions/linear_pauli_rotation.py +65 -0
- classiq/qmod/builtins/functions/modular_exponentiation.py +137 -0
- classiq/qmod/builtins/functions/operators.py +22 -0
- classiq/qmod/builtins/functions/qaoa_penalty.py +116 -0
- classiq/qmod/builtins/functions/qft.py +23 -0
- classiq/qmod/builtins/functions/qpe.py +39 -0
- classiq/qmod/builtins/functions/qsvm.py +24 -0
- classiq/qmod/builtins/functions/qsvt.py +136 -0
- classiq/qmod/builtins/functions/standard_gates.py +739 -0
- classiq/qmod/builtins/functions/state_preparation.py +356 -0
- classiq/qmod/builtins/functions/swap_test.py +25 -0
- classiq/qmod/builtins/structs.py +50 -28
- classiq/qmod/cparam.py +64 -0
- classiq/qmod/create_model_function.py +190 -0
- classiq/qmod/declaration_inferrer.py +52 -81
- classiq/qmod/expression_query.py +16 -0
- classiq/qmod/generative.py +48 -0
- classiq/qmod/model_state_container.py +1 -2
- classiq/qmod/native/pretty_printer.py +7 -11
- classiq/qmod/pretty_print/pretty_printer.py +7 -11
- classiq/qmod/python_classical_type.py +67 -0
- classiq/qmod/qfunc.py +19 -4
- classiq/qmod/qmod_parameter.py +15 -64
- classiq/qmod/qmod_variable.py +28 -46
- classiq/qmod/quantum_callable.py +1 -1
- classiq/qmod/quantum_expandable.py +10 -4
- classiq/qmod/quantum_function.py +22 -40
- classiq/qmod/semantics/error_manager.py +22 -10
- classiq/qmod/semantics/static_semantics_visitor.py +10 -12
- classiq/qmod/semantics/validation/types_validation.py +6 -7
- classiq/qmod/utilities.py +2 -2
- classiq/qmod/write_qmod.py +14 -0
- classiq/show.py +10 -0
- classiq/synthesis.py +46 -2
- {classiq-0.45.0.dist-info → classiq-0.46.0.dist-info}/METADATA +1 -1
- {classiq-0.45.0.dist-info → classiq-0.46.0.dist-info}/RECORD +138 -74
- classiq/interface/generator/functions/builtins/core_library/__init__.py +0 -16
- classiq/interface/generator/functions/builtins/core_library/atomic_quantum_functions.py +0 -710
- classiq/interface/generator/functions/builtins/core_library/exponentiation_functions.py +0 -105
- classiq/interface/generator/functions/builtins/open_lib_functions.py +0 -2489
- classiq/interface/generator/functions/builtins/quantum_operators.py +0 -24
- classiq/interface/generator/types/builtin_struct_declarations/__init__.py +0 -1
- classiq/interface/generator/types/builtin_struct_declarations/pauli_struct_declarations.py +0 -21
- classiq/qmod/builtins/functions.py +0 -1029
- {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)
|
File without changes
|