classiq 0.80.1__py3-none-any.whl → 0.82.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/analyzer/show_interactive_hack.py +10 -4
- classiq/analyzer/url_utils.py +4 -3
- classiq/applications/qnn/qlayer.py +1 -1
- classiq/interface/_version.py +1 -1
- classiq/interface/backend/backend_preferences.py +0 -3
- classiq/interface/debug_info/debug_info.py +0 -1
- classiq/interface/executor/execution_preferences.py +2 -1
- classiq/interface/generator/compiler_keywords.py +2 -2
- classiq/interface/generator/expressions/atomic_expression_functions.py +11 -7
- classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +6 -2
- classiq/interface/generator/function_params.py +1 -1
- classiq/interface/generator/functions/classical_type.py +8 -0
- classiq/interface/generator/generated_circuit_data.py +1 -2
- classiq/interface/generator/quantum_program.py +13 -0
- classiq/interface/generator/types/compilation_metadata.py +14 -2
- classiq/interface/model/handle_binding.py +12 -2
- classiq/interface/model/quantum_type.py +12 -1
- classiq/interface/server/routes.py +0 -1
- classiq/model_expansions/atomic_expression_functions_defs.py +1 -1
- classiq/model_expansions/capturing/captured_vars.py +123 -9
- classiq/model_expansions/closure.py +2 -0
- classiq/model_expansions/evaluators/quantum_type_utils.py +3 -18
- classiq/model_expansions/function_builder.py +1 -17
- classiq/model_expansions/interpreters/base_interpreter.py +2 -0
- classiq/model_expansions/quantum_operations/allocate.py +18 -7
- classiq/model_expansions/quantum_operations/assignment_result_processor.py +4 -0
- classiq/model_expansions/quantum_operations/bind.py +2 -1
- classiq/model_expansions/quantum_operations/call_emitter.py +27 -21
- classiq/model_expansions/quantum_operations/emitter.py +28 -0
- classiq/model_expansions/quantum_operations/function_calls_cache.py +1 -16
- classiq/model_expansions/transformers/type_qualifier_inference.py +220 -50
- classiq/model_expansions/visitors/symbolic_param_inference.py +0 -6
- classiq/open_library/functions/amplitude_amplification.py +3 -5
- classiq/open_library/functions/state_preparation.py +9 -0
- classiq/qmod/builtins/functions/__init__.py +3 -1
- classiq/qmod/builtins/functions/exponentiation.py +41 -3
- classiq/qmod/builtins/operations.py +65 -37
- classiq/qmod/declaration_inferrer.py +2 -1
- classiq/qmod/native/pretty_printer.py +12 -10
- classiq/qmod/pretty_print/pretty_printer.py +9 -9
- classiq/qmod/qfunc.py +11 -11
- classiq/qmod/quantum_expandable.py +4 -0
- classiq/qmod/semantics/error_manager.py +8 -2
- classiq/synthesis.py +6 -3
- {classiq-0.80.1.dist-info → classiq-0.82.0.dist-info}/METADATA +1 -1
- {classiq-0.80.1.dist-info → classiq-0.82.0.dist-info}/RECORD +47 -49
- classiq/interface/execution/resource_estimator.py +0 -7
- classiq/interface/execution/result.py +0 -5
- {classiq-0.80.1.dist-info → classiq-0.82.0.dist-info}/WHEEL +0 -0
@@ -1,10 +1,17 @@
|
|
1
1
|
import ast
|
2
2
|
import functools
|
3
3
|
import itertools
|
4
|
+
import warnings
|
4
5
|
from collections.abc import Collection, Iterator, Sequence
|
5
6
|
from contextlib import contextmanager
|
6
7
|
|
7
|
-
from classiq.interface.exceptions import
|
8
|
+
from classiq.interface.exceptions import (
|
9
|
+
ClassiqDeprecationWarning,
|
10
|
+
ClassiqInternalExpansionError,
|
11
|
+
)
|
12
|
+
from classiq.interface.generator.functions.port_declaration import (
|
13
|
+
PortDeclarationDirection,
|
14
|
+
)
|
8
15
|
from classiq.interface.generator.functions.type_qualifier import TypeQualifier
|
9
16
|
from classiq.interface.model.allocate import Allocate
|
10
17
|
from classiq.interface.model.bind_operation import BindOperation
|
@@ -30,7 +37,32 @@ from classiq.interface.model.within_apply_operation import WithinApply
|
|
30
37
|
from classiq.model_expansions.visitors.variable_references import VarRefCollector
|
31
38
|
|
32
39
|
|
33
|
-
|
40
|
+
def _inconsistent_type_qualifier_error(
|
41
|
+
port_name: str, expected: TypeQualifier, actual: TypeQualifier
|
42
|
+
) -> str:
|
43
|
+
return (
|
44
|
+
f"The type modifier of variable '{port_name}' does not conform to the function signature: "
|
45
|
+
f"expected '{expected.name}', but found '{actual.name}'.\n"
|
46
|
+
f"Tip: If the final role of the variable in the function matches '{expected.name}', "
|
47
|
+
f"you may use the `unchecked` flag to instruct the compiler to disregard individual operations.\n"
|
48
|
+
# TODO add earliest date of enforcement. See https://classiq.atlassian.net/browse/CLS-2709.
|
49
|
+
)
|
50
|
+
|
51
|
+
|
52
|
+
def _inconsistent_type_qualifier_in_binding_error(
|
53
|
+
expected: TypeQualifier, known_qualifiers: dict[str, TypeQualifier]
|
54
|
+
) -> str:
|
55
|
+
actual = ", ".join(
|
56
|
+
f"{name}: {qualifier.name}" for name, qualifier in known_qualifiers.items()
|
57
|
+
)
|
58
|
+
return (
|
59
|
+
f"The variable binding has inconsistent type modifiers: "
|
60
|
+
f"Expected modifier: {expected.name}, Actual modifiers: {actual}"
|
61
|
+
# TODO add earliest date of enforcement. See https://classiq.atlassian.net/browse/CLS-2709.
|
62
|
+
)
|
63
|
+
|
64
|
+
|
65
|
+
class TypeQualifierValidation(ModelVisitor):
|
34
66
|
"""
|
35
67
|
This class assumes that function calls are topologically sorted, so it traverses
|
36
68
|
the list of function calls and infers the type qualifiers for each function call
|
@@ -38,26 +70,54 @@ class TypeQualifierInference(ModelVisitor):
|
|
38
70
|
The function definition ports are modified inplace.
|
39
71
|
"""
|
40
72
|
|
41
|
-
def __init__(
|
73
|
+
def __init__(
|
74
|
+
self, *, skip_validation: bool = False, support_unused_ports: bool = True
|
75
|
+
) -> None:
|
42
76
|
self._signature_ports: dict[str, PortDeclaration] = dict()
|
43
77
|
self._inferred_ports: dict[str, PortDeclaration] = dict()
|
78
|
+
self._unchecked: set[str] = set()
|
79
|
+
|
80
|
+
self._initialized_vars: dict[str, TypeQualifier] = dict()
|
81
|
+
self._bound_vars: list[set[str]] = []
|
82
|
+
|
83
|
+
self._conjugation_context: bool = False
|
44
84
|
self._support_unused_ports = (
|
45
85
|
support_unused_ports # could be turned off for debugging
|
46
86
|
)
|
87
|
+
self._skip_validation = skip_validation
|
47
88
|
|
48
89
|
@contextmanager
|
49
|
-
def
|
90
|
+
def validate_ports(
|
91
|
+
self, ports: Collection[PortDeclaration], unchecked: Collection[str]
|
92
|
+
) -> Iterator[bool]:
|
50
93
|
for port in ports:
|
51
94
|
if port.type_qualifier is TypeQualifier.Inferred:
|
52
95
|
self._inferred_ports[port.name] = port
|
53
96
|
else:
|
54
97
|
self._signature_ports[port.name] = port
|
98
|
+
self._unchecked.update(unchecked)
|
55
99
|
|
56
|
-
yield len(self._inferred_ports) > 0
|
100
|
+
yield len(self._inferred_ports) > 0 or (
|
101
|
+
any(
|
102
|
+
port.type_qualifier is not TypeQualifier.Quantum
|
103
|
+
for port in self._signature_ports.values()
|
104
|
+
)
|
105
|
+
and not self._skip_validation
|
106
|
+
)
|
57
107
|
|
58
108
|
self._set_unused_as_const()
|
59
109
|
self._signature_ports.clear()
|
60
110
|
self._inferred_ports.clear()
|
111
|
+
self._unchecked.clear()
|
112
|
+
|
113
|
+
@contextmanager
|
114
|
+
def conjugation_context(self) -> Iterator[None]:
|
115
|
+
previous_context = self._conjugation_context
|
116
|
+
self._conjugation_context = True
|
117
|
+
try:
|
118
|
+
yield
|
119
|
+
finally:
|
120
|
+
self._conjugation_context = previous_context
|
61
121
|
|
62
122
|
def _set_unused_as_const(self) -> None:
|
63
123
|
unresolved_ports = [
|
@@ -73,71 +133,144 @@ class TypeQualifierInference(ModelVisitor):
|
|
73
133
|
for port in unresolved_ports:
|
74
134
|
port.type_qualifier = TypeQualifier.Const
|
75
135
|
|
76
|
-
def
|
136
|
+
def _validate_qualifier(self, candidate: str, qualifier: TypeQualifier) -> None:
|
137
|
+
if self._conjugation_context and qualifier is TypeQualifier.QFree:
|
138
|
+
qualifier = TypeQualifier.Const
|
139
|
+
|
77
140
|
if candidate in self._inferred_ports:
|
78
141
|
self._inferred_ports[candidate].type_qualifier = TypeQualifier.and_(
|
79
142
|
self._inferred_ports[candidate].type_qualifier, qualifier
|
80
143
|
)
|
144
|
+
return
|
145
|
+
|
146
|
+
if self._skip_validation or candidate in self._unchecked:
|
147
|
+
return
|
148
|
+
|
149
|
+
if candidate in self._signature_ports:
|
150
|
+
self._validate_signature_qualifier(candidate, qualifier)
|
151
|
+
|
152
|
+
elif candidate in self._initialized_vars:
|
153
|
+
self._initialized_vars[candidate] = TypeQualifier.and_(
|
154
|
+
self._initialized_vars[candidate], qualifier
|
155
|
+
)
|
156
|
+
|
157
|
+
def _validate_signature_qualifier(
|
158
|
+
self, candidate: str, qualifier: TypeQualifier
|
159
|
+
) -> None:
|
160
|
+
signature_qualifier = self._signature_ports[candidate].type_qualifier
|
161
|
+
if signature_qualifier is not TypeQualifier.and_(
|
162
|
+
signature_qualifier, qualifier
|
163
|
+
):
|
164
|
+
warnings.warn(
|
165
|
+
_inconsistent_type_qualifier_error(
|
166
|
+
candidate, signature_qualifier, qualifier
|
167
|
+
),
|
168
|
+
ClassiqDeprecationWarning,
|
169
|
+
stacklevel=1,
|
170
|
+
)
|
171
|
+
|
172
|
+
def _add_initialized_qualifier(self, var: str, qualifier: TypeQualifier) -> None:
|
173
|
+
if var in self._inferred_ports or var in self._signature_ports:
|
174
|
+
return
|
175
|
+
if self._conjugation_context and qualifier is TypeQualifier.QFree:
|
176
|
+
qualifier = TypeQualifier.Const
|
177
|
+
self._initialized_vars[var] = qualifier
|
81
178
|
|
82
179
|
def run(
|
83
|
-
self,
|
180
|
+
self,
|
181
|
+
ports: Collection[PortDeclaration],
|
182
|
+
body: Sequence[QuantumStatement],
|
183
|
+
unchecked: Collection[str],
|
84
184
|
) -> None:
|
85
|
-
with self.
|
86
|
-
if
|
185
|
+
with self.validate_ports(ports, unchecked) as should_validate:
|
186
|
+
if should_validate:
|
87
187
|
self.visit(body)
|
188
|
+
self._update_bound_vars()
|
189
|
+
|
190
|
+
def _update_bound_vars(self) -> None:
|
191
|
+
merged_bound_vars = _merge_overlapping(self._bound_vars)
|
192
|
+
for bound_vars in merged_bound_vars:
|
193
|
+
reduced_qualifier = self._get_reduced_qualifier(bound_vars)
|
194
|
+
for var in bound_vars:
|
195
|
+
self._validate_qualifier(var, reduced_qualifier)
|
88
196
|
|
89
197
|
def visit_QuantumFunctionCall(self, call: QuantumFunctionCall) -> None:
|
90
198
|
for handle, port in call.handles_with_params:
|
91
|
-
self.
|
199
|
+
self._validate_qualifier(handle.name, port.type_qualifier)
|
200
|
+
if port.direction is PortDeclarationDirection.Output:
|
201
|
+
self._add_initialized_qualifier(handle.name, port.type_qualifier)
|
202
|
+
|
203
|
+
if self._has_inputs(call):
|
204
|
+
bound_vars = {
|
205
|
+
handle.name
|
206
|
+
for handle, port in call.handles_with_params
|
207
|
+
if port.direction is not PortDeclarationDirection.Inout
|
208
|
+
}
|
209
|
+
self._bound_vars.append(bound_vars)
|
210
|
+
|
211
|
+
@staticmethod
|
212
|
+
def _has_inputs(call: QuantumFunctionCall) -> bool:
|
213
|
+
return any(
|
214
|
+
port.direction is PortDeclarationDirection.Input
|
215
|
+
for _, port in call.handles_with_params
|
216
|
+
)
|
92
217
|
|
93
218
|
def visit_Allocate(self, alloc: Allocate) -> None:
|
94
|
-
self.
|
219
|
+
self._validate_qualifier(alloc.target.name, TypeQualifier.QFree)
|
220
|
+
self._add_initialized_qualifier(alloc.target.name, TypeQualifier.QFree)
|
95
221
|
|
96
222
|
def visit_BindOperation(self, bind_op: BindOperation) -> None:
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
223
|
+
var_names = {
|
224
|
+
handle.name
|
225
|
+
for handle in itertools.chain(bind_op.in_handles, bind_op.out_handles)
|
226
|
+
}
|
227
|
+
self._bound_vars.append(var_names)
|
228
|
+
for handle in bind_op.out_handles:
|
229
|
+
self._add_initialized_qualifier(handle.name, TypeQualifier.Inferred)
|
104
230
|
|
105
|
-
|
106
|
-
|
107
|
-
|
231
|
+
def _get_reduced_qualifier(self, bound_vars: set[str]) -> TypeQualifier:
|
232
|
+
signature_qualifier = {
|
233
|
+
name: self._signature_ports[name].type_qualifier
|
234
|
+
for name in bound_vars.intersection(self._signature_ports)
|
108
235
|
}
|
109
|
-
|
110
|
-
name: self._inferred_ports[name]
|
111
|
-
for name in
|
236
|
+
known_inferred_qualifiers = {
|
237
|
+
name: self._inferred_ports[name].type_qualifier
|
238
|
+
for name in bound_vars.intersection(self._inferred_ports)
|
112
239
|
if self._inferred_ports[name].type_qualifier is not TypeQualifier.Inferred
|
113
240
|
}
|
114
|
-
|
241
|
+
known_initialized_qualifiers = {
|
242
|
+
name: self._initialized_vars[name]
|
243
|
+
for name in bound_vars.intersection(self._initialized_vars)
|
244
|
+
if self._initialized_vars[name] is not TypeQualifier.Inferred
|
245
|
+
}
|
246
|
+
known_qualifiers = (
|
247
|
+
signature_qualifier
|
248
|
+
| known_inferred_qualifiers
|
249
|
+
| known_initialized_qualifiers
|
250
|
+
)
|
251
|
+
min_qualifier = self._get_min_qualifier(list(known_qualifiers.values()))
|
115
252
|
if not all(
|
116
|
-
|
253
|
+
type_qualifier is min_qualifier
|
254
|
+
for type_qualifier in signature_qualifier.values()
|
117
255
|
):
|
118
|
-
|
119
|
-
|
120
|
-
|
256
|
+
warnings.warn(
|
257
|
+
_inconsistent_type_qualifier_in_binding_error(
|
258
|
+
min_qualifier, known_qualifiers
|
259
|
+
),
|
260
|
+
ClassiqDeprecationWarning,
|
261
|
+
stacklevel=1,
|
121
262
|
)
|
122
263
|
|
123
264
|
return min_qualifier
|
124
265
|
|
125
266
|
@staticmethod
|
126
|
-
def _get_min_qualifier(
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
itertools.chain(signature_ports.values(), known_inferred_ports.values())
|
132
|
-
)
|
133
|
-
if len(known_ports) == 0:
|
134
|
-
return TypeQualifier.Quantum
|
135
|
-
elif len(known_ports) == 1:
|
136
|
-
return known_ports[0].type_qualifier
|
267
|
+
def _get_min_qualifier(qualifiers: list[TypeQualifier]) -> TypeQualifier:
|
268
|
+
if len(qualifiers) == 0:
|
269
|
+
return TypeQualifier.Const
|
270
|
+
elif len(qualifiers) == 1:
|
271
|
+
return qualifiers[0]
|
137
272
|
else:
|
138
|
-
return functools.reduce(
|
139
|
-
TypeQualifier.and_, (port.type_qualifier for port in known_ports)
|
140
|
-
)
|
273
|
+
return functools.reduce(TypeQualifier.and_, qualifiers)
|
141
274
|
|
142
275
|
@staticmethod
|
143
276
|
def _extract_expr_vars(expr_op: QuantumExpressionOperation) -> list[str]:
|
@@ -149,25 +282,27 @@ class TypeQualifierInference(ModelVisitor):
|
|
149
282
|
|
150
283
|
def visit_ArithmeticOperation(self, arith: ArithmeticOperation) -> None:
|
151
284
|
result_var = arith.result_var.name
|
152
|
-
self.
|
285
|
+
self._validate_qualifier(result_var, TypeQualifier.QFree)
|
153
286
|
for expr_var in self._extract_expr_vars(arith):
|
154
|
-
self.
|
287
|
+
self._validate_qualifier(expr_var, TypeQualifier.Const)
|
288
|
+
if not arith.is_inplace:
|
289
|
+
self._add_initialized_qualifier(result_var, TypeQualifier.QFree)
|
155
290
|
|
156
291
|
def visit_AmplitudeLoadingOperation(
|
157
292
|
self, amp_load: AmplitudeLoadingOperation
|
158
293
|
) -> None:
|
159
294
|
result_var = amp_load.result_var.name
|
160
|
-
self.
|
295
|
+
self._validate_qualifier(result_var, TypeQualifier.Quantum)
|
161
296
|
for expr_var in self._extract_expr_vars(amp_load):
|
162
|
-
self.
|
297
|
+
self._validate_qualifier(expr_var, TypeQualifier.Const)
|
163
298
|
|
164
299
|
def visit_PhaseOperation(self, phase_op: PhaseOperation) -> None:
|
165
300
|
for expr_var in self._extract_expr_vars(phase_op):
|
166
|
-
self.
|
301
|
+
self._validate_qualifier(expr_var, TypeQualifier.Const)
|
167
302
|
|
168
303
|
def visit_Control(self, control: Control) -> None:
|
169
304
|
for control_var in self._extract_expr_vars(control):
|
170
|
-
self.
|
305
|
+
self._validate_qualifier(control_var, TypeQualifier.Const)
|
171
306
|
self.visit(control.body)
|
172
307
|
if control.else_block is not None:
|
173
308
|
self.visit(control.else_block)
|
@@ -179,5 +314,40 @@ class TypeQualifierInference(ModelVisitor):
|
|
179
314
|
self.visit(power.body)
|
180
315
|
|
181
316
|
def visit_WithinApply(self, within_apply: WithinApply) -> None:
|
182
|
-
self.
|
317
|
+
with self.conjugation_context():
|
318
|
+
self.visit(within_apply.compute)
|
183
319
|
self.visit(within_apply.action)
|
320
|
+
|
321
|
+
|
322
|
+
def _merge_overlapping(bound_vars: Sequence[Collection[str]]) -> list[set[str]]:
|
323
|
+
"""
|
324
|
+
Merges overlapping sets of bound variables.
|
325
|
+
Two sets overlap if they share at least one variable.
|
326
|
+
"""
|
327
|
+
all_bound_vars = bound_vars
|
328
|
+
merged_bound_vars: list[set[str]] = []
|
329
|
+
loop_guard: int = 10
|
330
|
+
idx: int = 0
|
331
|
+
|
332
|
+
for _ in range(loop_guard):
|
333
|
+
idx += 1
|
334
|
+
|
335
|
+
merged_bound_vars = []
|
336
|
+
modified: bool = False
|
337
|
+
for current_bound_vars in all_bound_vars:
|
338
|
+
for existing in merged_bound_vars:
|
339
|
+
if existing.intersection(current_bound_vars):
|
340
|
+
existing.update(current_bound_vars)
|
341
|
+
modified = True
|
342
|
+
break
|
343
|
+
else:
|
344
|
+
merged_bound_vars.append(set(current_bound_vars))
|
345
|
+
|
346
|
+
if not modified:
|
347
|
+
break
|
348
|
+
all_bound_vars = merged_bound_vars
|
349
|
+
|
350
|
+
if idx == loop_guard - 1:
|
351
|
+
raise ClassiqInternalExpansionError
|
352
|
+
|
353
|
+
return merged_bound_vars
|
@@ -152,16 +152,10 @@ class SymbolicParamInference(ModelVisitor):
|
|
152
152
|
):
|
153
153
|
self._process_compile_time_expressions(arg)
|
154
154
|
else:
|
155
|
-
if isinstance(arg, Expression):
|
156
|
-
for expr_part in self.get_generative_expression_parts(arg.expr):
|
157
|
-
self._process_compile_time_expression(expr_part)
|
158
155
|
for expr in _get_expressions(arg):
|
159
156
|
self._process_nested_compile_time_expression(expr.expr)
|
160
157
|
self.generic_visit(call)
|
161
158
|
|
162
|
-
def get_generative_expression_parts(self, expr: str) -> list[str]:
|
163
|
-
return []
|
164
|
-
|
165
159
|
def _get_params(self, call: QuantumFunctionCall) -> Sequence[AnonPositionalArg]:
|
166
160
|
name = call.func_name
|
167
161
|
if name in self._scope_operands:
|
@@ -1,5 +1,3 @@
|
|
1
|
-
import numpy as np
|
2
|
-
|
3
1
|
from classiq.open_library.functions.grover import grover_operator
|
4
2
|
from classiq.qmod.builtins.functions.standard_gates import RY
|
5
3
|
from classiq.qmod.builtins.operations import (
|
@@ -13,7 +11,7 @@ from classiq.qmod.cparam import CInt, CReal
|
|
13
11
|
from classiq.qmod.qfunc import qfunc
|
14
12
|
from classiq.qmod.qmod_variable import QArray, QBit
|
15
13
|
from classiq.qmod.quantum_callable import QCallable
|
16
|
-
from classiq.qmod.symbolic import acos, asin, ceiling, sin
|
14
|
+
from classiq.qmod.symbolic import acos, asin, ceiling, pi, sin
|
17
15
|
|
18
16
|
|
19
17
|
@qfunc
|
@@ -69,8 +67,8 @@ def exact_amplitude_amplification(
|
|
69
67
|
packed_vars: The variable that holds the state to be amplified. Assumed to be in the zero state at the beginning of the algorithm.
|
70
68
|
"""
|
71
69
|
aux = QBit()
|
72
|
-
k = ceiling((
|
73
|
-
theta =
|
70
|
+
k = ceiling((pi / (4 * asin(amplitude))) - 0.5)
|
71
|
+
theta = pi / (4 * k + 2)
|
74
72
|
rot_phase = 2 * acos(sin(theta) / amplitude)
|
75
73
|
|
76
74
|
extended_qvars: QArray = QArray()
|
@@ -57,6 +57,10 @@ def allocate_num(
|
|
57
57
|
"""
|
58
58
|
[Qmod Classiq-library function]
|
59
59
|
|
60
|
+
This function is **deprecated** and will no longer be supported starting on
|
61
|
+
16/06/2025 at the earliest. Instead, use `allocate` which supports the same
|
62
|
+
parameters.
|
63
|
+
|
60
64
|
Initializes a quantum number with the given number of qubits, sign, and fractional digits.
|
61
65
|
|
62
66
|
Args:
|
@@ -64,6 +68,11 @@ def allocate_num(
|
|
64
68
|
is_signed: Whether the number is signed or unsigned.
|
65
69
|
fraction_digits: The number of fractional digits.
|
66
70
|
"""
|
71
|
+
warnings.warn(
|
72
|
+
"Function `allocate_num` is deprecated and will no longer be supported starting on 16/06/2025 at the earliest. Instead, use `allocate` which supports the same parameters. ",
|
73
|
+
ClassiqDeprecationWarning,
|
74
|
+
stacklevel=1,
|
75
|
+
)
|
67
76
|
allocate(num_qubits, out)
|
68
77
|
|
69
78
|
|
@@ -70,8 +70,9 @@ CORE_LIB_DECLS = [
|
|
70
70
|
inplace_prepare_amplitudes_approx,
|
71
71
|
single_pauli_exponent,
|
72
72
|
commuting_paulis_exponent,
|
73
|
-
sparse_suzuki_trotter,
|
74
73
|
suzuki_trotter,
|
74
|
+
parametric_suzuki_trotter,
|
75
|
+
sparse_suzuki_trotter,
|
75
76
|
qdrift,
|
76
77
|
exponentiation_with_depth_constraint,
|
77
78
|
RESET,
|
@@ -131,6 +132,7 @@ __all__ = [ # noqa: RUF022
|
|
131
132
|
"molecule_hartree_fock",
|
132
133
|
"molecule_hva",
|
133
134
|
"molecule_ucc",
|
135
|
+
"parametric_suzuki_trotter",
|
134
136
|
"pauli_feature_map",
|
135
137
|
"permute",
|
136
138
|
"prepare_amplitudes",
|
@@ -66,7 +66,9 @@ def suzuki_trotter(
|
|
66
66
|
"""
|
67
67
|
[Qmod core-library function]
|
68
68
|
|
69
|
-
Applies the Suzuki-Trotter decomposition to a Pauli operator.
|
69
|
+
Applies the Suzuki-Trotter decomposition to a Pauli operator.
|
70
|
+
|
71
|
+
The Suzuki-Trotter decomposition is a method for approximating the exponential of a sum of operators by a product of exponentials of each operator.
|
70
72
|
The Suzuki-Trotter decomposition of a given order nullifies the error of the Taylor series expansion of the product of exponentials up to that order.
|
71
73
|
The error of a Suzuki-Trotter decomposition decreases as the order and number of repetitions increase.
|
72
74
|
|
@@ -80,6 +82,37 @@ def suzuki_trotter(
|
|
80
82
|
pass
|
81
83
|
|
82
84
|
|
85
|
+
@qfunc(external=True)
|
86
|
+
def parametric_suzuki_trotter(
|
87
|
+
paulis: CArray[CArray[Pauli]],
|
88
|
+
coefficients: CArray[CReal, Literal["get_field(paulis, 'len')"]],
|
89
|
+
evolution_coefficient: CReal,
|
90
|
+
order: CInt,
|
91
|
+
repetitions: CInt,
|
92
|
+
qbv: QArray[QBit, Literal["get_field(paulis[0], 'len')"]],
|
93
|
+
) -> None:
|
94
|
+
"""
|
95
|
+
[Qmod core-library function]
|
96
|
+
|
97
|
+
Applies the Suzuki-Trotter decomposition to a Pauli operator represented by two
|
98
|
+
separate lists of paulis and coefficients.
|
99
|
+
Supports symbolic coefficients, including execution parameters.
|
100
|
+
|
101
|
+
The Suzuki-Trotter decomposition is a method for approximating the exponential of a sum of operators by a product of exponentials of each operator.
|
102
|
+
The Suzuki-Trotter decomposition of a given order nullifies the error of the Taylor series expansion of the product of exponentials up to that order.
|
103
|
+
The error of a Suzuki-Trotter decomposition decreases as the order and number of repetitions increase.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
paulis: The Paulis of the Pauli operator.
|
107
|
+
coefficients: The coefficients of the Pauli operator.
|
108
|
+
evolution_coefficient: A global evolution coefficient multiplying the Pauli operator.
|
109
|
+
order: The order of the Suzuki-Trotter decomposition.
|
110
|
+
repetitions: The number of repetitions of the Suzuki-Trotter decomposition.
|
111
|
+
qbv: The target quantum variable of the exponentiation.
|
112
|
+
"""
|
113
|
+
pass
|
114
|
+
|
115
|
+
|
83
116
|
@qfunc(external=True)
|
84
117
|
def sparse_suzuki_trotter(
|
85
118
|
pauli_operator: SparsePauliOp,
|
@@ -89,8 +122,13 @@ def sparse_suzuki_trotter(
|
|
89
122
|
qbv: QArray[QBit, Literal["get_field(pauli_operator, 'num_qubits')"]],
|
90
123
|
) -> None:
|
91
124
|
"""
|
92
|
-
|
93
|
-
|
125
|
+
[Qmod core-library function]
|
126
|
+
|
127
|
+
Applies the Suzuki-Trotter decomposition to a sparse Pauli operator.
|
128
|
+
|
129
|
+
The Suzuki-Trotter decomposition is a method for approximating the exponential of a sum of operators by a product of exponentials of each operator.
|
130
|
+
The Suzuki-Trotter decomposition of a given order nullifies the error of the Taylor series expansion of the product of exponentials up to that order.
|
131
|
+
The error of a Suzuki-Trotter decomposition decreases as the order and number of repetitions increase.
|
94
132
|
|
95
133
|
Args:
|
96
134
|
pauli_operator: The Pauli operator to be exponentiated, in sparse representation (See: SparsePauliOp).
|
@@ -6,6 +6,7 @@ from typing import (
|
|
6
6
|
Any,
|
7
7
|
Callable,
|
8
8
|
Final,
|
9
|
+
NoReturn,
|
9
10
|
Union,
|
10
11
|
overload,
|
11
12
|
)
|
@@ -16,6 +17,7 @@ from classiq.interface.generator.functions.builtins.internal_operators import (
|
|
16
17
|
REPEAT_OPERATOR_NAME,
|
17
18
|
)
|
18
19
|
from classiq.interface.generator.functions.classical_type import Integer
|
20
|
+
from classiq.interface.helpers.text_utils import s
|
19
21
|
from classiq.interface.model.allocate import Allocate
|
20
22
|
from classiq.interface.model.bind_operation import BindOperation
|
21
23
|
from classiq.interface.model.classical_if import ClassicalIf
|
@@ -322,7 +324,7 @@ def within_apply(
|
|
322
324
|
def repeat(
|
323
325
|
count: Union[SymbolicExpr, int], iteration: Callable[[int], Statements]
|
324
326
|
) -> None:
|
325
|
-
_validate_operand(iteration)
|
327
|
+
_validate_operand(iteration, num_params=1)
|
326
328
|
assert QCallable.CURRENT_EXPANDABLE is not None
|
327
329
|
source_ref = get_source_ref(sys._getframe(1))
|
328
330
|
iteration_operand = prepare_arg(
|
@@ -396,54 +398,80 @@ def phase(expr: SymbolicExpr, theta: float = 1.0) -> None:
|
|
396
398
|
)
|
397
399
|
|
398
400
|
|
399
|
-
def _validate_operand(stmt_block: Any) -> None:
|
400
|
-
if stmt_block is
|
401
|
+
def _validate_operand(stmt_block: Any, num_params: int = 0) -> None:
|
402
|
+
if stmt_block is None:
|
403
|
+
_raise_operand_error(
|
404
|
+
lambda operation_name, operand_arg_name: (
|
405
|
+
f"{operation_name!r} is missing required argument for "
|
406
|
+
f"parameter {operand_arg_name!r}"
|
407
|
+
),
|
408
|
+
num_params,
|
409
|
+
)
|
410
|
+
if isinstance(stmt_block, QCallable):
|
401
411
|
return
|
402
|
-
|
412
|
+
op_spec = inspect.getfullargspec(stmt_block)
|
413
|
+
params = op_spec.args[: len(op_spec.args) - len(op_spec.defaults or ())]
|
414
|
+
if len(params) > num_params or (
|
415
|
+
len(params) < num_params and op_spec.varargs is None
|
416
|
+
):
|
417
|
+
_raise_operand_error(
|
418
|
+
lambda operation_name, operand_arg_name: (
|
419
|
+
f"{operation_name!r} argument for {operand_arg_name!r} has "
|
420
|
+
f"{len(params)} parameter{s(params)} but {num_params} expected"
|
421
|
+
),
|
422
|
+
num_params,
|
423
|
+
)
|
424
|
+
|
425
|
+
|
426
|
+
def _raise_operand_error(
|
427
|
+
error_template: Callable[[str, str], str], num_params: int
|
428
|
+
) -> NoReturn:
|
429
|
+
currentframe: FrameType = inspect.currentframe().f_back # type: ignore[assignment,union-attr]
|
403
430
|
operation_frame: FrameType = currentframe.f_back # type: ignore[assignment]
|
404
431
|
operation_frame_info: inspect.Traceback = inspect.getframeinfo(operation_frame)
|
405
432
|
operation_name: str = operation_frame_info.function
|
406
|
-
|
407
433
|
context = operation_frame_info.code_context
|
408
434
|
assert context is not None
|
409
|
-
operand_arg_name =
|
410
|
-
|
411
|
-
error_message = (
|
412
|
-
f"{operation_name!r} is missing required argument for {operand_arg_name!r}."
|
435
|
+
operand_arg_name = (
|
436
|
+
context[0].split("_validate_operand(")[1].split(")")[0].split(",")[0]
|
413
437
|
)
|
414
|
-
|
415
|
-
operation_name
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
)
|
425
|
-
return ", ".join(
|
426
|
-
[
|
427
|
-
(
|
428
|
-
f"{param.name}={operand_value}"
|
429
|
-
if param.name == operand_arg_name
|
430
|
-
else f"{param.name}=..."
|
431
|
-
)
|
432
|
-
for param in params.values()
|
433
|
-
if param.name != "operand" # FIXME: Remove compatibility (CAD-21932)
|
434
|
-
]
|
438
|
+
operation_parameters = inspect.signature(
|
439
|
+
operation_frame.f_globals[operation_name]
|
440
|
+
).parameters
|
441
|
+
raise ClassiqValueError(
|
442
|
+
error_template(operation_name, operand_arg_name)
|
443
|
+
+ _get_operand_hint(
|
444
|
+
operation_name=operation_name,
|
445
|
+
operand_arg_name=operand_arg_name,
|
446
|
+
params=operation_parameters,
|
447
|
+
num_params=num_params,
|
448
|
+
)
|
435
449
|
)
|
436
450
|
|
437
451
|
|
438
452
|
def _get_operand_hint(
|
439
|
-
operation_name: str,
|
453
|
+
operation_name: str,
|
454
|
+
operand_arg_name: str,
|
455
|
+
params: Mapping[str, inspect.Parameter],
|
456
|
+
num_params: int,
|
440
457
|
) -> str:
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
458
|
+
if operation_name == "repeat":
|
459
|
+
operand_params = " i"
|
460
|
+
else:
|
461
|
+
operand_params = (
|
462
|
+
""
|
463
|
+
if num_params == 0
|
464
|
+
else f" {', '.join([f'p{i}' for i in range(num_params)])}"
|
465
|
+
)
|
466
|
+
args = [
|
467
|
+
(
|
468
|
+
f"{param.name}=lambda{operand_params}: ..."
|
469
|
+
if param.name == operand_arg_name
|
470
|
+
else f"{param.name}=..."
|
471
|
+
)
|
472
|
+
for param in params.values()
|
473
|
+
]
|
474
|
+
return f"\nHint: Write '{operation_name}({', '.join(args)})'"
|
447
475
|
|
448
476
|
|
449
477
|
def _operand_to_body(
|
@@ -175,7 +175,8 @@ def _get_param_name(py_type_args: Any) -> Optional[str]:
|
|
175
175
|
def _validate_annotations(py_type_args: Any, py_type: Any) -> None:
|
176
176
|
for arg in py_type_args[1:-1]:
|
177
177
|
if (
|
178
|
-
isinstance(arg, str)
|
178
|
+
isinstance(arg, str)
|
179
|
+
and not isinstance(arg, (PortDeclarationDirection, TypeQualifier))
|
179
180
|
) or arg is Literal:
|
180
181
|
raise ClassiqValueError(
|
181
182
|
f"Operand parameter declaration must be of the form <param-type> or "
|