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.
Files changed (49) hide show
  1. classiq/analyzer/show_interactive_hack.py +10 -4
  2. classiq/analyzer/url_utils.py +4 -3
  3. classiq/applications/qnn/qlayer.py +1 -1
  4. classiq/interface/_version.py +1 -1
  5. classiq/interface/backend/backend_preferences.py +0 -3
  6. classiq/interface/debug_info/debug_info.py +0 -1
  7. classiq/interface/executor/execution_preferences.py +2 -1
  8. classiq/interface/generator/compiler_keywords.py +2 -2
  9. classiq/interface/generator/expressions/atomic_expression_functions.py +11 -7
  10. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +6 -2
  11. classiq/interface/generator/function_params.py +1 -1
  12. classiq/interface/generator/functions/classical_type.py +8 -0
  13. classiq/interface/generator/generated_circuit_data.py +1 -2
  14. classiq/interface/generator/quantum_program.py +13 -0
  15. classiq/interface/generator/types/compilation_metadata.py +14 -2
  16. classiq/interface/model/handle_binding.py +12 -2
  17. classiq/interface/model/quantum_type.py +12 -1
  18. classiq/interface/server/routes.py +0 -1
  19. classiq/model_expansions/atomic_expression_functions_defs.py +1 -1
  20. classiq/model_expansions/capturing/captured_vars.py +123 -9
  21. classiq/model_expansions/closure.py +2 -0
  22. classiq/model_expansions/evaluators/quantum_type_utils.py +3 -18
  23. classiq/model_expansions/function_builder.py +1 -17
  24. classiq/model_expansions/interpreters/base_interpreter.py +2 -0
  25. classiq/model_expansions/quantum_operations/allocate.py +18 -7
  26. classiq/model_expansions/quantum_operations/assignment_result_processor.py +4 -0
  27. classiq/model_expansions/quantum_operations/bind.py +2 -1
  28. classiq/model_expansions/quantum_operations/call_emitter.py +27 -21
  29. classiq/model_expansions/quantum_operations/emitter.py +28 -0
  30. classiq/model_expansions/quantum_operations/function_calls_cache.py +1 -16
  31. classiq/model_expansions/transformers/type_qualifier_inference.py +220 -50
  32. classiq/model_expansions/visitors/symbolic_param_inference.py +0 -6
  33. classiq/open_library/functions/amplitude_amplification.py +3 -5
  34. classiq/open_library/functions/state_preparation.py +9 -0
  35. classiq/qmod/builtins/functions/__init__.py +3 -1
  36. classiq/qmod/builtins/functions/exponentiation.py +41 -3
  37. classiq/qmod/builtins/operations.py +65 -37
  38. classiq/qmod/declaration_inferrer.py +2 -1
  39. classiq/qmod/native/pretty_printer.py +12 -10
  40. classiq/qmod/pretty_print/pretty_printer.py +9 -9
  41. classiq/qmod/qfunc.py +11 -11
  42. classiq/qmod/quantum_expandable.py +4 -0
  43. classiq/qmod/semantics/error_manager.py +8 -2
  44. classiq/synthesis.py +6 -3
  45. {classiq-0.80.1.dist-info → classiq-0.82.0.dist-info}/METADATA +1 -1
  46. {classiq-0.80.1.dist-info → classiq-0.82.0.dist-info}/RECORD +47 -49
  47. classiq/interface/execution/resource_estimator.py +0 -7
  48. classiq/interface/execution/result.py +0 -5
  49. {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 ClassiqInternalExpansionError
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
- class TypeQualifierInference(ModelVisitor):
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__(self, support_unused_ports: bool = True) -> None:
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 infer_ports(self, ports: Collection[PortDeclaration]) -> Iterator[bool]:
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 _reduce_qualifier(self, candidate: str, qualifier: TypeQualifier) -> None:
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, ports: Collection[PortDeclaration], body: Sequence[QuantumStatement]
180
+ self,
181
+ ports: Collection[PortDeclaration],
182
+ body: Sequence[QuantumStatement],
183
+ unchecked: Collection[str],
84
184
  ) -> None:
85
- with self.infer_ports(ports) as should_infer:
86
- if should_infer:
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._reduce_qualifier(handle.name, port.type_qualifier)
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._reduce_qualifier(alloc.target.name, TypeQualifier.QFree)
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
- reduced_qualifier = self._get_reduced_qualifier(bind_op)
98
- for handle in itertools.chain(bind_op.in_handles, bind_op.out_handles):
99
- self._reduce_qualifier(handle.name, reduced_qualifier)
100
-
101
- def _get_reduced_qualifier(self, bind_op: BindOperation) -> TypeQualifier:
102
- handles = itertools.chain(bind_op.in_handles, bind_op.out_handles)
103
- op_vars = {handle.name for handle in handles}
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
- signature_ports = {
106
- name: self._signature_ports[name]
107
- for name in op_vars.intersection(self._signature_ports)
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
- known_inferred_ports = {
110
- name: self._inferred_ports[name]
111
- for name in op_vars.intersection(self._inferred_ports)
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
- min_qualifier = self._get_min_qualifier(known_inferred_ports, signature_ports)
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
- port.type_qualifier is min_qualifier for port in signature_ports.values()
253
+ type_qualifier is min_qualifier
254
+ for type_qualifier in signature_qualifier.values()
117
255
  ):
118
- raise ClassiqInternalExpansionError(
119
- f"Bind operation {bind_op} has inconsistent type qualifiers: "
120
- f"{signature_ports}, {known_inferred_ports}"
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
- known_inferred_ports: dict[str, PortDeclaration],
128
- signature_ports: dict[str, PortDeclaration],
129
- ) -> TypeQualifier:
130
- known_ports = tuple(
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._reduce_qualifier(result_var, TypeQualifier.QFree)
285
+ self._validate_qualifier(result_var, TypeQualifier.QFree)
153
286
  for expr_var in self._extract_expr_vars(arith):
154
- self._reduce_qualifier(expr_var, TypeQualifier.Const)
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._reduce_qualifier(result_var, TypeQualifier.Quantum)
295
+ self._validate_qualifier(result_var, TypeQualifier.Quantum)
161
296
  for expr_var in self._extract_expr_vars(amp_load):
162
- self._reduce_qualifier(expr_var, TypeQualifier.Const)
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._reduce_qualifier(expr_var, TypeQualifier.Const)
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._reduce_qualifier(control_var, TypeQualifier.Const)
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.visit(within_apply.compute)
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((np.pi / (4 * asin(amplitude))) - 0.5)
73
- theta = np.pi / (4 * k + 2)
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. The Suzuki-Trotter decomposition is a method for approximating the exponential of a sum of operators by a product of exponentials of each 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
- Applies the Suzuki-Trotter decomposition to a sparse Pauli operator. For more details,
93
- See about Suzuki-Trotter decomposition in `suzuki_trotter` `qfunc`.
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 not None:
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
- currentframe: FrameType = inspect.currentframe() # type: ignore[assignment]
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 = context[0].split("_validate_operand(")[1].split(")")[0]
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
- error_message += _get_operand_hint(
415
- operation_name=operation_name,
416
- operand_arg_name=operand_arg_name,
417
- params=inspect.signature(operation_frame.f_globals[operation_name]).parameters,
418
- )
419
- raise ClassiqValueError(error_message)
420
-
421
-
422
- def _get_operand_hint_args(
423
- params: Mapping[str, inspect.Parameter], operand_arg_name: str, operand_value: str
424
- ) -> str:
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, operand_arg_name: str, params: Mapping[str, inspect.Parameter]
453
+ operation_name: str,
454
+ operand_arg_name: str,
455
+ params: Mapping[str, inspect.Parameter],
456
+ num_params: int,
440
457
  ) -> str:
441
- return (
442
- f"\nHint: To call a function under {operation_name!r} use a lambda function as in "
443
- f"'{operation_name}({_get_operand_hint_args(params, operand_arg_name, 'lambda: f(q)')})' "
444
- f"or pass the quantum function directly as in "
445
- f"'{operation_name}({_get_operand_hint_args(params, operand_arg_name, 'f')})'."
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) and not isinstance(arg, PortDeclarationDirection)
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 "