classiq 0.80.1__py3-none-any.whl → 0.81.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 (40) hide show
  1. classiq/interface/_version.py +1 -1
  2. classiq/interface/debug_info/debug_info.py +0 -1
  3. classiq/interface/generator/compiler_keywords.py +1 -1
  4. classiq/interface/generator/expressions/atomic_expression_functions.py +11 -7
  5. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +6 -2
  6. classiq/interface/generator/function_params.py +1 -1
  7. classiq/interface/generator/functions/classical_type.py +8 -0
  8. classiq/interface/generator/generated_circuit_data.py +1 -2
  9. classiq/interface/generator/types/compilation_metadata.py +4 -1
  10. classiq/interface/model/handle_binding.py +12 -2
  11. classiq/interface/model/quantum_type.py +12 -1
  12. classiq/interface/server/routes.py +0 -1
  13. classiq/model_expansions/atomic_expression_functions_defs.py +1 -1
  14. classiq/model_expansions/capturing/captured_vars.py +123 -9
  15. classiq/model_expansions/closure.py +2 -0
  16. classiq/model_expansions/evaluators/quantum_type_utils.py +3 -18
  17. classiq/model_expansions/function_builder.py +1 -17
  18. classiq/model_expansions/quantum_operations/allocate.py +18 -7
  19. classiq/model_expansions/quantum_operations/assignment_result_processor.py +4 -0
  20. classiq/model_expansions/quantum_operations/bind.py +2 -1
  21. classiq/model_expansions/quantum_operations/call_emitter.py +27 -21
  22. classiq/model_expansions/quantum_operations/emitter.py +28 -0
  23. classiq/model_expansions/quantum_operations/function_calls_cache.py +1 -16
  24. classiq/model_expansions/transformers/type_qualifier_inference.py +72 -19
  25. classiq/model_expansions/visitors/symbolic_param_inference.py +0 -6
  26. classiq/open_library/functions/amplitude_amplification.py +3 -5
  27. classiq/open_library/functions/state_preparation.py +9 -0
  28. classiq/qmod/builtins/functions/__init__.py +3 -1
  29. classiq/qmod/builtins/functions/exponentiation.py +41 -3
  30. classiq/qmod/builtins/operations.py +65 -37
  31. classiq/qmod/declaration_inferrer.py +2 -1
  32. classiq/qmod/native/pretty_printer.py +8 -9
  33. classiq/qmod/pretty_print/pretty_printer.py +9 -9
  34. classiq/qmod/qfunc.py +11 -11
  35. classiq/qmod/quantum_expandable.py +4 -0
  36. {classiq-0.80.1.dist-info → classiq-0.81.0.dist-info}/METADATA +1 -1
  37. {classiq-0.80.1.dist-info → classiq-0.81.0.dist-info}/RECORD +38 -40
  38. classiq/interface/execution/resource_estimator.py +0 -7
  39. classiq/interface/execution/result.py +0 -5
  40. {classiq-0.80.1.dist-info → classiq-0.81.0.dist-info}/WHEEL +0 -0
@@ -29,6 +29,7 @@ from classiq.interface.generator.functions.port_declaration import (
29
29
  PortDeclarationDirection,
30
30
  )
31
31
  from classiq.interface.generator.functions.type_qualifier import TypeQualifier
32
+ from classiq.interface.generator.types.compilation_metadata import CompilationMetadata
32
33
  from classiq.interface.helpers.backward_compatibility import zip_strict
33
34
  from classiq.interface.helpers.text_utils import are, readable_list, s
34
35
  from classiq.interface.model.classical_parameter_declaration import (
@@ -80,7 +81,7 @@ from classiq.model_expansions.scope import (
80
81
  Scope,
81
82
  )
82
83
  from classiq.model_expansions.transformers.type_qualifier_inference import (
83
- TypeQualifierInference,
84
+ TypeQualifierValidation,
84
85
  )
85
86
  from classiq.model_expansions.transformers.var_splitter import VarSplitter
86
87
  from classiq.qmod.pretty_print.expression_to_python import transform_expression
@@ -290,6 +291,7 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
290
291
  context = self._expand_operation(function)
291
292
  function_context = cast(FunctionContext, context)
292
293
  function_def = self._create_function_definition(function_context, args)
294
+ self._validate_type_qualifiers(function_context, function_def)
293
295
  self._expanded_functions[cache_key] = function_def
294
296
  self._top_level_scope[function_def.name] = Evaluated(
295
297
  value=function_context.closure.with_new_declaration(function_def)
@@ -320,20 +322,14 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
320
322
  )
321
323
  captured_ports = captured_vars.get_captured_parameters()
322
324
  if len(captured_ports) == 0:
323
- self._override_type_qualifier(function_context, func_def)
324
325
  return func_def
325
326
  func_def.positional_arg_declarations = list(
326
327
  chain.from_iterable((func_def.positional_arg_declarations, captured_ports))
327
328
  )
328
329
 
329
- rewrite_mapping = dict(captured_vars.get_propagated_captured_mapping())
330
- if function_context.is_lambda:
331
- rewrite_mapping |= captured_vars.get_immediate_captured_mapping()
332
- rewrite_mapping |= captured_vars.get_classical_captured_mapping()
330
+ rewrite_mapping = captured_vars.get_captured_mapping(function_context.is_lambda)
333
331
  func_def.body = self.rewrite(func_def.body, rewrite_mapping)
334
332
 
335
- self._override_type_qualifier(function_context, func_def)
336
-
337
333
  return func_def
338
334
 
339
335
  @staticmethod
@@ -419,9 +415,21 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
419
415
  if var_state and param.direction == PortDeclarationDirection.Output:
420
416
  raise ClassiqExpansionError(INITIALIZED_VAR_MESSAGE.format(var_name))
421
417
 
422
- def _override_type_qualifier(
418
+ def _validate_type_qualifiers(
423
419
  self, func_context: FunctionContext, func_def: NativeFunctionDefinition
424
420
  ) -> None:
421
+ if self._should_override_type_qualifiers(func_context):
422
+ self._override_type_qualifiers(func_def)
423
+
424
+ unchecked = self._functions_compilation_metadata.get(
425
+ func_context.name, CompilationMetadata()
426
+ ).unchecked
427
+ TypeQualifierValidation().run(
428
+ func_def.port_declarations, func_def.body, unchecked
429
+ )
430
+
431
+ @staticmethod
432
+ def _should_override_type_qualifiers(func_context: FunctionContext) -> bool:
425
433
  """
426
434
  The type qualifier can be changed according to the operand passed to the
427
435
  function. For example,
@@ -434,29 +442,27 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
434
442
  """
435
443
 
436
444
  if func_context.is_lambda:
437
- self._update_type_qualifiers(func_def)
438
- return
445
+ return True
439
446
 
440
447
  orig_name = func_context.name
441
448
  if (
442
449
  orig_name == MAIN_FUNCTION_NAME
443
450
  or orig_name not in func_context.closure.scope
444
451
  ):
445
- return
452
+ return False
446
453
 
447
454
  orig_func = func_context.closure.scope[orig_name].value
448
- if isinstance(orig_func, Closure) and not any(
449
- isinstance(param_decl, QuantumOperandDeclaration)
450
- for param_decl in orig_func.positional_arg_declarations
451
- ):
452
- return
453
-
454
- self._update_type_qualifiers(func_def)
455
+ return not (
456
+ isinstance(orig_func, Closure)
457
+ and not any(
458
+ isinstance(param_decl, QuantumOperandDeclaration)
459
+ for param_decl in orig_func.positional_arg_declarations
460
+ )
461
+ )
455
462
 
456
463
  @staticmethod
457
- def _update_type_qualifiers(func_def: NativeFunctionDefinition) -> None:
464
+ def _override_type_qualifiers(func_def: NativeFunctionDefinition) -> None:
458
465
  # only override the qualifier if it's unspecified (not QFree or Const)
459
466
  for port in func_def.port_declarations:
460
467
  if port.type_qualifier is TypeQualifier.Quantum:
461
468
  port.type_qualifier = TypeQualifier.Inferred
462
- TypeQualifierInference().run(func_def.port_declarations, func_def.body)
@@ -184,6 +184,8 @@ class Emitter(Generic[QuantumStatementT], ABC):
184
184
  def _update_captured_classical_vars_in_expression(self, expr: Expression) -> None:
185
185
  for var_name, var_type in self._get_classical_vars_in_expression(expr):
186
186
  self._capture_classical_var(var_name, var_type)
187
+ for handle in self._get_quantum_type_attributes_in_expression(expr):
188
+ self._capture_quantum_type_attribute(handle)
187
189
 
188
190
  def _update_captured_vars(self, op: QuantumOperation) -> None:
189
191
  for handle, direction in op.handles_with_directions:
@@ -223,6 +225,17 @@ class Emitter(Generic[QuantumStatementT], ABC):
223
225
  defining_function=defining_function,
224
226
  )
225
227
 
228
+ def _capture_quantum_type_attribute(self, handle: FieldHandleBinding) -> None:
229
+ if handle.name not in self._current_scope:
230
+ return
231
+ defining_function = self._current_scope[handle.name].defining_function
232
+ if defining_function is None:
233
+ raise ClassiqInternalExpansionError
234
+ self._builder.current_block.captured_vars.capture_quantum_type_attribute(
235
+ handle=handle,
236
+ defining_function=defining_function,
237
+ )
238
+
226
239
  def _get_symbols_in_expression(self, expr: Expression) -> list[QuantumSymbol]:
227
240
  vrc = VarRefCollector(ignore_duplicated_handles=True, unevaluated=True)
228
241
  vrc.visit(ast.parse(expr.expr))
@@ -258,3 +271,18 @@ class Emitter(Generic[QuantumStatementT], ABC):
258
271
  )
259
272
  }.items()
260
273
  )
274
+
275
+ def _get_quantum_type_attributes_in_expression(
276
+ self, expr: Expression
277
+ ) -> list[FieldHandleBinding]:
278
+ vrc = VarRefCollector(ignore_duplicated_handles=True, unevaluated=True)
279
+ vrc.visit(ast.parse(expr.expr))
280
+ return list(
281
+ dict.fromkeys(
282
+ handle
283
+ for handle in vrc.var_handles
284
+ if isinstance(handle, FieldHandleBinding)
285
+ and handle.field in CLASSICAL_ATTRIBUTES
286
+ and isinstance(self._current_scope[handle.name].value, QuantumSymbol)
287
+ )
288
+ )
@@ -11,10 +11,6 @@ from classiq.interface.generator.expressions.proxies.classical.classical_struct_
11
11
  from classiq.interface.generator.expressions.proxies.classical.utils import (
12
12
  get_proxy_type,
13
13
  )
14
- from classiq.interface.generator.functions.port_declaration import (
15
- PortDeclarationDirection,
16
- )
17
- from classiq.interface.model.port_declaration import PortDeclaration
18
14
  from classiq.interface.model.quantum_function_declaration import (
19
15
  NamedParamsQuantumFunctionDeclaration,
20
16
  )
@@ -36,18 +32,7 @@ def get_func_call_cache_key(
36
32
  raise ClassiqInternalExpansionError(
37
33
  "Mismatch between number of args to number of arg declarations"
38
34
  )
39
-
40
- # output arguments cannot affect the morphization of the function, as their
41
- # attributes are defined locally by the function, according to other arguments
42
- non_outputs_args = [
43
- arg
44
- for arg_decl, arg in zip(decl.positional_arg_declarations, args)
45
- if not (
46
- isinstance(arg_decl, PortDeclaration)
47
- and arg_decl.direction is PortDeclarationDirection.Output
48
- )
49
- ]
50
- return f"{decl.name}__{_evaluated_args_to_str(non_outputs_args)}"
35
+ return f"{decl.name}__{_evaluated_args_to_str(args)}"
51
36
 
52
37
 
53
38
  def _evaluated_args_to_str(evaluated_args: list[Evaluated]) -> str:
@@ -4,7 +4,10 @@ import itertools
4
4
  from collections.abc import Collection, Iterator, Sequence
5
5
  from contextlib import contextmanager
6
6
 
7
- from classiq.interface.exceptions import ClassiqInternalExpansionError
7
+ from classiq.interface.exceptions import (
8
+ ClassiqExpansionError,
9
+ ClassiqInternalExpansionError,
10
+ )
8
11
  from classiq.interface.generator.functions.type_qualifier import TypeQualifier
9
12
  from classiq.interface.model.allocate import Allocate
10
13
  from classiq.interface.model.bind_operation import BindOperation
@@ -30,7 +33,18 @@ from classiq.interface.model.within_apply_operation import WithinApply
30
33
  from classiq.model_expansions.visitors.variable_references import VarRefCollector
31
34
 
32
35
 
33
- class TypeQualifierInference(ModelVisitor):
36
+ def _inconsistent_type_qualifier_error(
37
+ port_name: str, expected: TypeQualifier, actual: TypeQualifier
38
+ ) -> str:
39
+ return (
40
+ f"The type modifier of variable '{port_name}' does not conform to the function signature: "
41
+ f"expected '{expected.name}', but found '{actual.name}'.\n"
42
+ f"Tip: If the final role of the variable in the function matches '{expected.name}', "
43
+ f"you may use the `unchecked` flag to instruct the compiler to disregard individual operations."
44
+ )
45
+
46
+
47
+ class TypeQualifierValidation(ModelVisitor):
34
48
  """
35
49
  This class assumes that function calls are topologically sorted, so it traverses
36
50
  the list of function calls and infers the type qualifiers for each function call
@@ -41,23 +55,41 @@ class TypeQualifierInference(ModelVisitor):
41
55
  def __init__(self, support_unused_ports: bool = True) -> None:
42
56
  self._signature_ports: dict[str, PortDeclaration] = dict()
43
57
  self._inferred_ports: dict[str, PortDeclaration] = dict()
58
+ self._unchecked: set[str] = set()
59
+ self._conjugation_context: bool = False
44
60
  self._support_unused_ports = (
45
61
  support_unused_ports # could be turned off for debugging
46
62
  )
47
63
 
48
64
  @contextmanager
49
- def infer_ports(self, ports: Collection[PortDeclaration]) -> Iterator[bool]:
65
+ def validate_ports(
66
+ self, ports: Collection[PortDeclaration], unchecked: Collection[str]
67
+ ) -> Iterator[bool]:
50
68
  for port in ports:
51
69
  if port.type_qualifier is TypeQualifier.Inferred:
52
70
  self._inferred_ports[port.name] = port
53
71
  else:
54
72
  self._signature_ports[port.name] = port
73
+ self._unchecked.update(unchecked)
55
74
 
56
- yield len(self._inferred_ports) > 0
75
+ yield len(self._inferred_ports) > 0 or any(
76
+ port.type_qualifier is not TypeQualifier.Quantum
77
+ for port in self._signature_ports.values()
78
+ )
57
79
 
58
80
  self._set_unused_as_const()
59
81
  self._signature_ports.clear()
60
82
  self._inferred_ports.clear()
83
+ self._unchecked.clear()
84
+
85
+ @contextmanager
86
+ def conjugation_context(self) -> Iterator[None]:
87
+ previous_context = self._conjugation_context
88
+ self._conjugation_context = True
89
+ try:
90
+ yield
91
+ finally:
92
+ self._conjugation_context = previous_context
61
93
 
62
94
  def _set_unused_as_const(self) -> None:
63
95
  unresolved_ports = [
@@ -73,30 +105,50 @@ class TypeQualifierInference(ModelVisitor):
73
105
  for port in unresolved_ports:
74
106
  port.type_qualifier = TypeQualifier.Const
75
107
 
76
- def _reduce_qualifier(self, candidate: str, qualifier: TypeQualifier) -> None:
108
+ def _validate_qualifier(self, candidate: str, qualifier: TypeQualifier) -> None:
109
+ if self._conjugation_context and qualifier is TypeQualifier.QFree:
110
+ qualifier = TypeQualifier.Const
111
+
77
112
  if candidate in self._inferred_ports:
78
113
  self._inferred_ports[candidate].type_qualifier = TypeQualifier.and_(
79
114
  self._inferred_ports[candidate].type_qualifier, qualifier
80
115
  )
116
+ return
117
+
118
+ if candidate not in self._signature_ports or candidate in self._unchecked:
119
+ return
120
+
121
+ signature_qualifier = self._signature_ports[candidate].type_qualifier
122
+ if signature_qualifier is not TypeQualifier.and_(
123
+ signature_qualifier, qualifier
124
+ ):
125
+ raise ClassiqExpansionError(
126
+ _inconsistent_type_qualifier_error(
127
+ candidate, signature_qualifier, qualifier
128
+ )
129
+ )
81
130
 
82
131
  def run(
83
- self, ports: Collection[PortDeclaration], body: Sequence[QuantumStatement]
132
+ self,
133
+ ports: Collection[PortDeclaration],
134
+ body: Sequence[QuantumStatement],
135
+ unchecked: Collection[str],
84
136
  ) -> None:
85
- with self.infer_ports(ports) as should_infer:
86
- if should_infer:
137
+ with self.validate_ports(ports, unchecked) as should_validate:
138
+ if should_validate:
87
139
  self.visit(body)
88
140
 
89
141
  def visit_QuantumFunctionCall(self, call: QuantumFunctionCall) -> None:
90
142
  for handle, port in call.handles_with_params:
91
- self._reduce_qualifier(handle.name, port.type_qualifier)
143
+ self._validate_qualifier(handle.name, port.type_qualifier)
92
144
 
93
145
  def visit_Allocate(self, alloc: Allocate) -> None:
94
- self._reduce_qualifier(alloc.target.name, TypeQualifier.QFree)
146
+ self._validate_qualifier(alloc.target.name, TypeQualifier.QFree)
95
147
 
96
148
  def visit_BindOperation(self, bind_op: BindOperation) -> None:
97
149
  reduced_qualifier = self._get_reduced_qualifier(bind_op)
98
150
  for handle in itertools.chain(bind_op.in_handles, bind_op.out_handles):
99
- self._reduce_qualifier(handle.name, reduced_qualifier)
151
+ self._validate_qualifier(handle.name, reduced_qualifier)
100
152
 
101
153
  def _get_reduced_qualifier(self, bind_op: BindOperation) -> TypeQualifier:
102
154
  handles = itertools.chain(bind_op.in_handles, bind_op.out_handles)
@@ -131,7 +183,7 @@ class TypeQualifierInference(ModelVisitor):
131
183
  itertools.chain(signature_ports.values(), known_inferred_ports.values())
132
184
  )
133
185
  if len(known_ports) == 0:
134
- return TypeQualifier.Quantum
186
+ return TypeQualifier.Const
135
187
  elif len(known_ports) == 1:
136
188
  return known_ports[0].type_qualifier
137
189
  else:
@@ -149,25 +201,25 @@ class TypeQualifierInference(ModelVisitor):
149
201
 
150
202
  def visit_ArithmeticOperation(self, arith: ArithmeticOperation) -> None:
151
203
  result_var = arith.result_var.name
152
- self._reduce_qualifier(result_var, TypeQualifier.QFree)
204
+ self._validate_qualifier(result_var, TypeQualifier.QFree)
153
205
  for expr_var in self._extract_expr_vars(arith):
154
- self._reduce_qualifier(expr_var, TypeQualifier.Const)
206
+ self._validate_qualifier(expr_var, TypeQualifier.Const)
155
207
 
156
208
  def visit_AmplitudeLoadingOperation(
157
209
  self, amp_load: AmplitudeLoadingOperation
158
210
  ) -> None:
159
211
  result_var = amp_load.result_var.name
160
- self._reduce_qualifier(result_var, TypeQualifier.Quantum)
212
+ self._validate_qualifier(result_var, TypeQualifier.Quantum)
161
213
  for expr_var in self._extract_expr_vars(amp_load):
162
- self._reduce_qualifier(expr_var, TypeQualifier.Const)
214
+ self._validate_qualifier(expr_var, TypeQualifier.Const)
163
215
 
164
216
  def visit_PhaseOperation(self, phase_op: PhaseOperation) -> None:
165
217
  for expr_var in self._extract_expr_vars(phase_op):
166
- self._reduce_qualifier(expr_var, TypeQualifier.Const)
218
+ self._validate_qualifier(expr_var, TypeQualifier.Const)
167
219
 
168
220
  def visit_Control(self, control: Control) -> None:
169
221
  for control_var in self._extract_expr_vars(control):
170
- self._reduce_qualifier(control_var, TypeQualifier.Const)
222
+ self._validate_qualifier(control_var, TypeQualifier.Const)
171
223
  self.visit(control.body)
172
224
  if control.else_block is not None:
173
225
  self.visit(control.else_block)
@@ -179,5 +231,6 @@ class TypeQualifierInference(ModelVisitor):
179
231
  self.visit(power.body)
180
232
 
181
233
  def visit_WithinApply(self, within_apply: WithinApply) -> None:
182
- self.visit(within_apply.compute)
234
+ with self.conjugation_context():
235
+ self.visit(within_apply.compute)
183
236
  self.visit(within_apply.action)
@@ -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 "