classiq 0.76.0__py3-none-any.whl → 0.78.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 (63) hide show
  1. classiq/applications/chemistry/chemistry_model_constructor.py +7 -6
  2. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +9 -1
  3. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -3
  4. classiq/applications/iqae/__init__.py +0 -0
  5. classiq/applications/iqae/iqae.py +207 -0
  6. classiq/execution/__init__.py +1 -1
  7. classiq/interface/_version.py +1 -1
  8. classiq/interface/applications/iqae/__init__.py +0 -0
  9. classiq/interface/applications/iqae/generic_iqae.py +222 -0
  10. classiq/interface/applications/iqae/iqae_result.py +45 -0
  11. classiq/interface/debug_info/debug_info.py +3 -0
  12. classiq/interface/executor/execution_result.py +1 -1
  13. classiq/interface/executor/user_budget.py +1 -1
  14. classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +9 -3
  15. classiq/interface/generator/expressions/proxies/quantum/qmod_qarray_proxy.py +6 -15
  16. classiq/interface/generator/expressions/proxies/quantum/qmod_qscalar_proxy.py +14 -5
  17. classiq/interface/generator/expressions/proxies/quantum/qmod_sized_proxy.py +5 -3
  18. classiq/interface/generator/generated_circuit_data.py +18 -7
  19. classiq/interface/ide/visual_model.py +2 -0
  20. classiq/interface/model/handle_binding.py +8 -0
  21. classiq/interface/model/model.py +3 -6
  22. classiq/interface/model/quantum_function_call.py +31 -1
  23. classiq/interface/model/quantum_statement.py +14 -1
  24. classiq/interface/source_reference.py +7 -2
  25. classiq/model_expansions/capturing/captured_vars.py +16 -6
  26. classiq/model_expansions/closure.py +1 -58
  27. classiq/model_expansions/evaluators/arg_type_match.py +2 -2
  28. classiq/model_expansions/evaluators/argument_types.py +4 -5
  29. classiq/model_expansions/evaluators/classical_expression.py +9 -9
  30. classiq/model_expansions/evaluators/parameter_types.py +19 -11
  31. classiq/model_expansions/expression_evaluator.py +20 -11
  32. classiq/model_expansions/generative_functions.py +1 -1
  33. classiq/model_expansions/interpreters/base_interpreter.py +27 -15
  34. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +0 -16
  35. classiq/model_expansions/interpreters/generative_interpreter.py +4 -4
  36. classiq/model_expansions/quantum_operations/allocate.py +2 -2
  37. classiq/model_expansions/quantum_operations/assignment_result_processor.py +3 -1
  38. classiq/model_expansions/quantum_operations/call_emitter.py +91 -42
  39. classiq/model_expansions/quantum_operations/emitter.py +7 -7
  40. classiq/model_expansions/quantum_operations/function_calls_cache.py +84 -0
  41. classiq/model_expansions/scope.py +73 -13
  42. classiq/model_expansions/transformers/model_renamer.py +2 -2
  43. classiq/model_expansions/transformers/type_qualifier_inference.py +183 -0
  44. classiq/model_expansions/utils/text_utils.py +4 -2
  45. classiq/model_expansions/visitors/symbolic_param_inference.py +4 -15
  46. classiq/open_library/functions/lookup_table.py +1 -1
  47. classiq/open_library/functions/state_preparation.py +1 -1
  48. classiq/qmod/builtins/classical_execution_primitives.py +1 -1
  49. classiq/qmod/create_model_function.py +21 -3
  50. classiq/qmod/global_declarative_switch.py +19 -0
  51. classiq/qmod/native/pretty_printer.py +4 -0
  52. classiq/qmod/pretty_print/pretty_printer.py +4 -0
  53. classiq/qmod/qfunc.py +31 -23
  54. classiq/qmod/qmod_variable.py +7 -4
  55. classiq/qmod/quantum_expandable.py +29 -1
  56. classiq/qmod/quantum_function.py +26 -19
  57. classiq/qmod/utilities.py +4 -0
  58. classiq/qmod/write_qmod.py +36 -10
  59. classiq/synthesis.py +7 -6
  60. {classiq-0.76.0.dist-info → classiq-0.78.0.dist-info}/METADATA +1 -1
  61. {classiq-0.76.0.dist-info → classiq-0.78.0.dist-info}/RECORD +62 -55
  62. classiq/interface/executor/iqae_result.py +0 -17
  63. {classiq-0.76.0.dist-info → classiq-0.78.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,84 @@
1
+ import json
2
+ from typing import Any
3
+
4
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
5
+ from classiq.interface.generator.expressions.proxies.classical.classical_proxy import (
6
+ ClassicalProxy,
7
+ )
8
+ from classiq.interface.generator.expressions.proxies.classical.classical_struct_proxy import (
9
+ ClassicalStructProxy,
10
+ )
11
+ from classiq.interface.generator.expressions.proxies.classical.utils import (
12
+ get_proxy_type,
13
+ )
14
+ from classiq.interface.generator.functions.port_declaration import (
15
+ PortDeclarationDirection,
16
+ )
17
+ from classiq.interface.model.port_declaration import PortDeclaration
18
+ from classiq.interface.model.quantum_function_declaration import (
19
+ NamedParamsQuantumFunctionDeclaration,
20
+ )
21
+
22
+ from classiq.model_expansions.closure import FunctionClosure
23
+ from classiq.model_expansions.scope import (
24
+ Evaluated,
25
+ QuantumSymbol,
26
+ evaluated_to_str as evaluated_classical_param_to_str,
27
+ )
28
+
29
+
30
+ def get_func_call_cache_key(
31
+ decl: NamedParamsQuantumFunctionDeclaration,
32
+ args: list[Evaluated],
33
+ ) -> str:
34
+ if len(decl.positional_arg_declarations) != len(args):
35
+ raise ClassiqInternalExpansionError(
36
+ "Mismatch between number of args to number of arg declarations"
37
+ )
38
+
39
+ # output arguments cannot affect the morphization of the function, as their
40
+ # attributes are defined locally by the function, according to other arguments
41
+ non_outputs_args = [
42
+ arg
43
+ for arg_decl, arg in zip(decl.positional_arg_declarations, args)
44
+ if not (
45
+ isinstance(arg_decl, PortDeclaration)
46
+ and arg_decl.direction is PortDeclarationDirection.Output
47
+ )
48
+ ]
49
+ return f"{decl.name}__{_evaluated_args_to_str(non_outputs_args)}"
50
+
51
+
52
+ def _evaluated_args_to_str(evaluated_args: list[Evaluated]) -> str:
53
+ args_signature = [
54
+ _evaluated_arg_to_str(eval_arg.value) for eval_arg in evaluated_args
55
+ ]
56
+ return json.dumps(args_signature)
57
+
58
+
59
+ def _evaluated_arg_to_str(arg: Any) -> str:
60
+ if isinstance(arg, str):
61
+ return arg
62
+ if isinstance(arg, QuantumSymbol):
63
+ return _evaluated_quantum_symbol_to_str(arg)
64
+ if isinstance(arg, FunctionClosure):
65
+ return _evaluated_one_operand_to_str(arg)
66
+ if isinstance(arg, list) and arg and isinstance(arg[0], FunctionClosure):
67
+ return _evaluated_operands_list_to_str(arg)
68
+ if isinstance(arg, ClassicalProxy):
69
+ if isinstance(arg, ClassicalStructProxy):
70
+ return repr(arg.struct_declaration)
71
+ return repr(get_proxy_type(arg))
72
+ return evaluated_classical_param_to_str(arg)
73
+
74
+
75
+ def _evaluated_quantum_symbol_to_str(port: QuantumSymbol) -> str:
76
+ return port.quantum_type.model_dump_json(exclude_none=True, exclude={"name"})
77
+
78
+
79
+ def _evaluated_one_operand_to_str(operand: FunctionClosure) -> str:
80
+ return operand.name
81
+
82
+
83
+ def _evaluated_operands_list_to_str(arg: list[FunctionClosure]) -> str:
84
+ return json.dumps([_evaluated_one_operand_to_str(ope) for ope in arg])
@@ -3,7 +3,9 @@ from collections import UserDict
3
3
  from collections.abc import Iterator
4
4
  from dataclasses import dataclass
5
5
  from functools import singledispatch
6
- from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union
6
+ from typing import TYPE_CHECKING, Any, NoReturn, Optional, TypeVar, Union
7
+
8
+ import sympy
7
9
 
8
10
  from classiq.interface.exceptions import (
9
11
  ClassiqExpansionError,
@@ -22,7 +24,9 @@ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_insta
22
24
  from classiq.interface.generator.functions.type_name import TypeName
23
25
  from classiq.interface.model.handle_binding import (
24
26
  FieldHandleBinding,
27
+ GeneralHandle,
25
28
  HandleBinding,
29
+ HandlesList,
26
30
  SlicedHandleBinding,
27
31
  SubscriptHandleBinding,
28
32
  )
@@ -32,6 +36,8 @@ from classiq.interface.model.quantum_type import (
32
36
  QuantumType,
33
37
  )
34
38
 
39
+ from classiq.model_expansions.utils.text_utils import readable_list, s
40
+
35
41
  if TYPE_CHECKING:
36
42
  from classiq.model_expansions.closure import FunctionClosure
37
43
 
@@ -39,10 +45,17 @@ T = TypeVar("T")
39
45
 
40
46
 
41
47
  @dataclass(frozen=True)
42
- class QuantumSymbol:
43
- handle: HandleBinding
48
+ class QuantumVariable:
44
49
  quantum_type: QuantumType
45
50
 
51
+ def emit(self) -> GeneralHandle:
52
+ raise NotImplementedError
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class QuantumSymbol(QuantumVariable):
57
+ handle: HandleBinding
58
+
46
59
  @property
47
60
  def is_subscript(self) -> bool:
48
61
  return isinstance(self.handle, (SubscriptHandleBinding, SlicedHandleBinding))
@@ -140,6 +153,47 @@ class QuantumSymbol:
140
153
  for field_name, field_type in quantum_type.fields.items()
141
154
  }
142
155
 
156
+ def __str__(self) -> str:
157
+ return str(self.handle)
158
+
159
+
160
+ @dataclass(frozen=True)
161
+ class QuantumSymbolList(QuantumVariable):
162
+ handles: list[HandleBinding]
163
+
164
+ @staticmethod
165
+ def from_symbols(
166
+ symbols: list[Union[QuantumSymbol, "QuantumSymbolList"]],
167
+ ) -> "QuantumSymbolList":
168
+ handles = list(
169
+ itertools.chain.from_iterable(
170
+ (
171
+ symbol.handles
172
+ if isinstance(symbol, QuantumSymbolList)
173
+ else [symbol.handle]
174
+ )
175
+ for symbol in symbols
176
+ )
177
+ )
178
+ if len(handles) == 0:
179
+ raise ClassiqExpansionError("Empty concatenation expression")
180
+ length: Optional[Expression]
181
+ if any(not symbol.quantum_type.has_size_in_bits for symbol in symbols):
182
+ length = None
183
+ else:
184
+ length = Expression(
185
+ expr=str(sum(symbol.quantum_type.size_in_bits for symbol in symbols))
186
+ )
187
+ return QuantumSymbolList(
188
+ handles=handles, quantum_type=QuantumBitvector(length=length)
189
+ )
190
+
191
+ def emit(self) -> HandlesList:
192
+ return HandlesList(handles=self.handles)
193
+
194
+ def __str__(self) -> str:
195
+ return str(self.handles)
196
+
143
197
 
144
198
  @singledispatch
145
199
  def evaluated_to_str(value: Any) -> str:
@@ -156,34 +210,40 @@ def _evaluated_to_str_struct_literal(value: QmodStructInstance) -> str:
156
210
  return f"struct_literal({value.struct_declaration.name}, {', '.join(f'{k}={evaluated_to_str(v)}' for k, v in value.fields.items())})"
157
211
 
158
212
 
213
+ def _raise_type_error(val: Any, t: type, location_hint: Optional[str]) -> NoReturn:
214
+ if isinstance(val, sympy.Basic) and len(val.free_symbols) > 0:
215
+ symbolic_vars = list(val.free_symbols)
216
+ suffix = f" {location_hint}" if location_hint is not None else ""
217
+ raise ClassiqExpansionError(
218
+ f"Cannot use execution parameter{s(symbolic_vars)} {readable_list(symbolic_vars, quote=True)} in a compile-time context{suffix}"
219
+ )
220
+ raise ClassiqExpansionError(f"Invalid access to expression {val!r} as {t}")
221
+
222
+
159
223
  @dataclass(frozen=True)
160
224
  class Evaluated: # FIXME: Merge with EvaluatedExpression if possible
161
225
  value: Any
162
226
  defining_function: Optional["FunctionClosure"] = None
163
227
 
164
- def as_type(self, t: type[T]) -> T:
228
+ def as_type(self, t: type[T], location_hint: Optional[str] = None) -> T:
165
229
  if t is int:
166
- return self._as_int() # type: ignore[return-value]
230
+ return self._as_int(location_hint) # type: ignore[return-value]
167
231
 
168
232
  if not isinstance(self.value, t):
169
- raise ClassiqExpansionError(
170
- f"Invalid access to expression {self.value!r} as {t}"
171
- )
233
+ _raise_type_error(self.value, t, location_hint)
172
234
 
173
235
  return self.value
174
236
 
175
- def _as_int(self) -> int:
237
+ def _as_int(self, location_hint: Optional[str]) -> int:
176
238
  if not isinstance(self.value, (int, float)):
177
- raise ClassiqExpansionError(
178
- f"Invalid access to expression {self.value!r} as {int}"
179
- )
239
+ _raise_type_error(self.value, int, location_hint)
180
240
 
181
241
  return int(self.value)
182
242
 
183
243
  def emit(self) -> ArgValue:
184
244
  from classiq.model_expansions.closure import FunctionClosure
185
245
 
186
- if isinstance(self.value, (QuantumSymbol, FunctionClosure)):
246
+ if isinstance(self.value, (QuantumVariable, FunctionClosure)):
187
247
  return self.value.emit()
188
248
  if isinstance(self.value, list) and all(
189
249
  isinstance(item, FunctionClosure) for item in self.value
@@ -33,7 +33,7 @@ def _handle_contains_handle(handle: HandleBinding, other_handle: HandleBinding)
33
33
  return 0
34
34
 
35
35
 
36
- class _ExprNormalizer(ast.NodeTransformer):
36
+ class ExprNormalizer(ast.NodeTransformer):
37
37
  def visit_Call(self, node: ast.Call) -> ast.AST:
38
38
  if not isinstance(node.func, ast.Name):
39
39
  return self.generic_visit(node)
@@ -74,7 +74,7 @@ SymbolRenaming = Mapping[HandleBinding, Sequence[HandleRenaming]]
74
74
  def _rewrite_expression(
75
75
  symbol_mapping: SymbolRenaming, expression: Expression
76
76
  ) -> Expression:
77
- normalized_expr = _ExprNormalizer().visit(ast.parse(expression.expr))
77
+ normalized_expr = ExprNormalizer().visit(ast.parse(expression.expr))
78
78
  vrc = VarRefCollector(
79
79
  ignore_duplicated_handles=True, ignore_sympy_symbols=True, unevaluated=True
80
80
  )
@@ -0,0 +1,183 @@
1
+ import ast
2
+ import functools
3
+ import itertools
4
+ from collections.abc import Collection, Iterator, Sequence
5
+ from contextlib import contextmanager
6
+
7
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
8
+ from classiq.interface.generator.functions.type_qualifier import TypeQualifier
9
+ from classiq.interface.model.allocate import Allocate
10
+ from classiq.interface.model.bind_operation import BindOperation
11
+ from classiq.interface.model.control import Control
12
+ from classiq.interface.model.invert import Invert
13
+ from classiq.interface.model.model_visitor import ModelVisitor
14
+ from classiq.interface.model.phase_operation import PhaseOperation
15
+ from classiq.interface.model.port_declaration import PortDeclaration
16
+ from classiq.interface.model.power import Power
17
+ from classiq.interface.model.quantum_expressions.amplitude_loading_operation import (
18
+ AmplitudeLoadingOperation,
19
+ )
20
+ from classiq.interface.model.quantum_expressions.arithmetic_operation import (
21
+ ArithmeticOperation,
22
+ )
23
+ from classiq.interface.model.quantum_expressions.quantum_expression import (
24
+ QuantumExpressionOperation,
25
+ )
26
+ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
27
+ from classiq.interface.model.quantum_statement import QuantumStatement
28
+ from classiq.interface.model.within_apply_operation import WithinApply
29
+
30
+ from classiq.model_expansions.visitors.variable_references import VarRefCollector
31
+
32
+
33
+ class TypeQualifierInference(ModelVisitor):
34
+ """
35
+ This class assumes that function calls are topologically sorted, so it traverses
36
+ the list of function calls and infers the type qualifiers for each function call
37
+ without going recursively into the function calls.
38
+ The function definition ports are modified inplace.
39
+ """
40
+
41
+ def __init__(self, support_unused_ports: bool = True) -> None:
42
+ self._signature_ports: dict[str, PortDeclaration] = dict()
43
+ self._inferred_ports: dict[str, PortDeclaration] = dict()
44
+ self._support_unused_ports = (
45
+ support_unused_ports # could be turned off for debugging
46
+ )
47
+
48
+ @contextmanager
49
+ def infer_ports(self, ports: Collection[PortDeclaration]) -> Iterator[bool]:
50
+ for port in ports:
51
+ if port.type_qualifier is TypeQualifier.Inferred:
52
+ self._inferred_ports[port.name] = port
53
+ else:
54
+ self._signature_ports[port.name] = port
55
+
56
+ yield len(self._inferred_ports) > 0
57
+
58
+ self._set_unused_as_const()
59
+ self._signature_ports.clear()
60
+ self._inferred_ports.clear()
61
+
62
+ def _set_unused_as_const(self) -> None:
63
+ unresolved_ports = [
64
+ port
65
+ for port in self._inferred_ports.values()
66
+ if port.type_qualifier is TypeQualifier.Inferred
67
+ ]
68
+ if not self._support_unused_ports and len(unresolved_ports) > 0:
69
+ raise ClassiqInternalExpansionError(
70
+ f"Unresolved inferred ports detected: {', '.join(port.name for port in unresolved_ports)}. "
71
+ "All ports must have their type qualifiers resolved."
72
+ )
73
+ for port in unresolved_ports:
74
+ port.type_qualifier = TypeQualifier.Const
75
+
76
+ def _reduce_qualifier(self, candidate: str, qualifier: TypeQualifier) -> None:
77
+ if candidate in self._inferred_ports:
78
+ self._inferred_ports[candidate].type_qualifier = TypeQualifier.and_(
79
+ self._inferred_ports[candidate].type_qualifier, qualifier
80
+ )
81
+
82
+ def run(
83
+ self, ports: Collection[PortDeclaration], body: Sequence[QuantumStatement]
84
+ ) -> None:
85
+ with self.infer_ports(ports) as should_infer:
86
+ if should_infer:
87
+ self.visit(body)
88
+
89
+ def visit_QuantumFunctionCall(self, call: QuantumFunctionCall) -> None:
90
+ for handle, port in call.handles_with_params:
91
+ self._reduce_qualifier(handle.name, port.type_qualifier)
92
+
93
+ def visit_Allocate(self, alloc: Allocate) -> None:
94
+ self._reduce_qualifier(alloc.target.name, TypeQualifier.QFree)
95
+
96
+ 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}
104
+
105
+ signature_ports = {
106
+ name: self._signature_ports[name]
107
+ for name in op_vars.intersection(self._signature_ports)
108
+ }
109
+ known_inferred_ports = {
110
+ name: self._inferred_ports[name]
111
+ for name in op_vars.intersection(self._inferred_ports)
112
+ if self._inferred_ports[name].type_qualifier is not TypeQualifier.Inferred
113
+ }
114
+ min_qualifier = self._get_min_qualifier(known_inferred_ports, signature_ports)
115
+ if not all(
116
+ port.type_qualifier is min_qualifier for port in signature_ports.values()
117
+ ):
118
+ raise ClassiqInternalExpansionError(
119
+ f"Bind operation {bind_op} has inconsistent type qualifiers: "
120
+ f"{signature_ports}, {known_inferred_ports}"
121
+ )
122
+
123
+ return min_qualifier
124
+
125
+ @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
137
+ else:
138
+ return functools.reduce(
139
+ TypeQualifier.and_, (port.type_qualifier for port in known_ports)
140
+ )
141
+
142
+ @staticmethod
143
+ def _extract_expr_vars(expr_op: QuantumExpressionOperation) -> list[str]:
144
+ vrc = VarRefCollector(
145
+ ignore_duplicated_handles=True, ignore_sympy_symbols=True, unevaluated=True
146
+ )
147
+ vrc.visit(ast.parse(expr_op.expression.expr))
148
+ return [handle.name for handle in vrc.var_handles]
149
+
150
+ def visit_ArithmeticOperation(self, arith: ArithmeticOperation) -> None:
151
+ result_var = arith.result_var.name
152
+ self._reduce_qualifier(result_var, TypeQualifier.QFree)
153
+ for expr_var in self._extract_expr_vars(arith):
154
+ self._reduce_qualifier(expr_var, TypeQualifier.Const)
155
+
156
+ def visit_AmplitudeLoadingOperation(
157
+ self, amp_load: AmplitudeLoadingOperation
158
+ ) -> None:
159
+ result_var = amp_load.result_var.name
160
+ self._reduce_qualifier(result_var, TypeQualifier.Quantum)
161
+ for expr_var in self._extract_expr_vars(amp_load):
162
+ self._reduce_qualifier(expr_var, TypeQualifier.Const)
163
+
164
+ def visit_PhaseOperation(self, phase_op: PhaseOperation) -> None:
165
+ for expr_var in self._extract_expr_vars(phase_op):
166
+ self._reduce_qualifier(expr_var, TypeQualifier.Const)
167
+
168
+ def visit_Control(self, control: Control) -> None:
169
+ for control_var in self._extract_expr_vars(control):
170
+ self._reduce_qualifier(control_var, TypeQualifier.Const)
171
+ self.visit(control.body)
172
+ if control.else_block is not None:
173
+ self.visit(control.else_block)
174
+
175
+ def visit_Invert(self, invert: Invert) -> None:
176
+ self.visit(invert.body)
177
+
178
+ def visit_Power(self, power: Power) -> None:
179
+ self.visit(power.body)
180
+
181
+ def visit_WithinApply(self, within_apply: WithinApply) -> None:
182
+ self.visit(within_apply.compute)
183
+ self.visit(within_apply.action)
@@ -10,7 +10,9 @@ def they(items: list) -> str:
10
10
  return "it" if len(items) == 1 else "they"
11
11
 
12
12
 
13
- def readable_list(items: list) -> str:
13
+ def readable_list(items: list, quote: bool = False) -> str:
14
+ if quote:
15
+ items = [repr(str(item)) for item in items]
14
16
  if len(items) == 1:
15
17
  return str(items[0])
16
- return f"{', '.join(items[:-1])} and {items[-1]}"
18
+ return f"{', '.join(items[:-1])}{',' if len(items) > 2 else ''} and {items[-1]}"
@@ -62,9 +62,9 @@ class SymbolicParamInference(ModelVisitor):
62
62
  def __init__(
63
63
  self,
64
64
  functions: list[NativeFunctionDefinition],
65
- additional_signatures: (
66
- list[NamedParamsQuantumFunctionDeclaration] | None
67
- ) = None,
65
+ additional_signatures: Optional[
66
+ list[NamedParamsQuantumFunctionDeclaration]
67
+ ] = None,
68
68
  ) -> None:
69
69
  self._functions = nameables_to_dict(functions)
70
70
  self._additional_signatures = (
@@ -73,7 +73,6 @@ class SymbolicParamInference(ModelVisitor):
73
73
  else nameables_to_dict(additional_signatures)
74
74
  )
75
75
  self._inferred_funcs: set[str] = set()
76
- self._call_stack: list[str] = []
77
76
  self._scope: Mapping[str, ClassicalType] = {}
78
77
  self._scope_operands: dict[str, QuantumOperandDeclaration] = {}
79
78
 
@@ -81,9 +80,6 @@ class SymbolicParamInference(ModelVisitor):
81
80
  for func in self._functions.values():
82
81
  self._infer_func_params(func)
83
82
 
84
- def _is_recursive_call(self, func: str) -> bool:
85
- return func in self._call_stack
86
-
87
83
  @contextmanager
88
84
  def function_context(
89
85
  self,
@@ -91,8 +87,6 @@ class SymbolicParamInference(ModelVisitor):
91
87
  scope: Mapping[str, ClassicalType],
92
88
  scope_operands: dict[str, QuantumOperandDeclaration],
93
89
  ) -> Iterator[None]:
94
- if func_name is not None:
95
- self._call_stack.append(func_name)
96
90
  prev_scope = self._scope
97
91
  self._scope = scope
98
92
  prev_scope_ops = self._scope_operands
@@ -100,12 +94,11 @@ class SymbolicParamInference(ModelVisitor):
100
94
  yield
101
95
  self._scope = prev_scope
102
96
  self._scope_operands = prev_scope_ops
103
- if func_name is not None:
104
- self._call_stack.pop()
105
97
 
106
98
  def _infer_func_params(self, func: NativeFunctionDefinition) -> None:
107
99
  if func.name in self._inferred_funcs:
108
100
  return
101
+ self._inferred_funcs.add(func.name)
109
102
  scope = {param.name: param.classical_type for param in func.param_decls}
110
103
  scope_operands = func.operand_declarations_dict
111
104
  with self.function_context(func.name, scope, scope_operands):
@@ -114,7 +107,6 @@ class SymbolicParamInference(ModelVisitor):
114
107
  self._process_compile_time_expression(expr.expr)
115
108
  self._set_enums_generative(func)
116
109
  self.visit(func.body)
117
- self._inferred_funcs.add(func.name)
118
110
 
119
111
  def _set_enums_generative(self, decl: AnonQuantumFunctionDeclaration) -> None:
120
112
  for param in decl.positional_arg_declarations:
@@ -144,9 +136,6 @@ class SymbolicParamInference(ModelVisitor):
144
136
 
145
137
  def visit_QuantumFunctionCall(self, call: QuantumFunctionCall) -> None:
146
138
  self._process_compile_time_expressions(call.function)
147
- name = call.func_name
148
- if self._is_recursive_call(name):
149
- return # Recursion is not fully supported
150
139
  params = self._get_params(call)
151
140
  for param, arg in zip_longest(params, call.positional_args):
152
141
  if (
@@ -39,7 +39,7 @@ def span_lookup_table(func: RealFunction, *targets: QNum) -> QNum:
39
39
  The quantum result of applying func to targets
40
40
 
41
41
  Notes:
42
- Must be called inside a generative function (`@qfunc(generative=True)`)
42
+ Must be called inside a generative function (`@qfunc`)
43
43
  """
44
44
  if len(targets) == 0:
45
45
  raise ClassiqValueError("No targets specified")
@@ -334,7 +334,7 @@ def _classical_hadamard_transform(arr: list[float]) -> np.ndarray:
334
334
  return 1 / np.sqrt(len(arr)) * np.array(sympy.fwht(np.array(arr)))
335
335
 
336
336
 
337
- @qfunc(generative=True)
337
+ @qfunc
338
338
  def _load_phases(
339
339
  phases: list[float],
340
340
  target: QArray[QBit, Literal["log(get_field(phases, 'len'), 2)"]],
@@ -1,8 +1,8 @@
1
1
  from typing import Final, Optional, Union
2
2
 
3
+ from classiq.interface.applications.iqae.iqae_result import IQAEResult
3
4
  from classiq.interface.exceptions import ClassiqError
4
5
  from classiq.interface.executor.execution_preferences import QaeWithQpeEstimationMethod
5
- from classiq.interface.executor.iqae_result import IQAEResult
6
6
  from classiq.interface.executor.result import (
7
7
  EstimationResult,
8
8
  EstimationResults,
@@ -1,4 +1,4 @@
1
- from typing import Optional, Union
1
+ from typing import Optional, Union, cast
2
2
 
3
3
  from classiq.interface.exceptions import ClassiqError
4
4
  from classiq.interface.executor.execution_preferences import ExecutionPreferences
@@ -7,10 +7,26 @@ from classiq.interface.generator.model.preferences.preferences import Preference
7
7
  from classiq.interface.model.model import MAIN_FUNCTION_NAME, SerializedModel
8
8
 
9
9
  from classiq.qmod.classical_function import CFunc
10
- from classiq.qmod.quantum_function import GenerativeQFunc, QFunc
10
+ from classiq.qmod.quantum_function import BaseQFunc, GenerativeQFunc, QFunc
11
11
  from classiq.qmod.write_qmod import write_qmod
12
12
 
13
13
 
14
+ class _EntryPointWrapper(str):
15
+ entry_point: BaseQFunc
16
+
17
+
18
+ def add_entry_point(
19
+ new_model: SerializedModel, old_model: SerializedModel
20
+ ) -> SerializedModel:
21
+ if not hasattr(old_model, "entry_point"):
22
+ return new_model
23
+ new_model_with_entry_point = _EntryPointWrapper(new_model)
24
+ new_model_with_entry_point.entry_point = cast(
25
+ _EntryPointWrapper, old_model
26
+ ).entry_point
27
+ return cast(SerializedModel, new_model_with_entry_point)
28
+
29
+
14
30
  def create_model(
15
31
  entry_point: Union[QFunc, GenerativeQFunc],
16
32
  constraints: Optional[Constraints] = None,
@@ -48,7 +64,9 @@ def create_model(
48
64
  preferences,
49
65
  classical_execution_function,
50
66
  )
51
- result = model.get_model()
67
+ serialized_model = _EntryPointWrapper(model.get_model())
68
+ serialized_model.entry_point = entry_point
69
+ result = cast(SerializedModel, serialized_model)
52
70
 
53
71
  if out_file is not None:
54
72
  write_qmod(result, out_file)
@@ -0,0 +1,19 @@
1
+ from collections.abc import Iterator
2
+ from contextlib import contextmanager
3
+
4
+ _DECLARATIVE_SWITCH = False
5
+
6
+
7
+ def get_global_declarative_switch() -> bool:
8
+ return _DECLARATIVE_SWITCH
9
+
10
+
11
+ @contextmanager
12
+ def set_global_declarative_switch() -> Iterator[None]:
13
+ global _DECLARATIVE_SWITCH
14
+ previous = _DECLARATIVE_SWITCH
15
+ _DECLARATIVE_SWITCH = True
16
+ try:
17
+ yield
18
+ finally:
19
+ _DECLARATIVE_SWITCH = previous
@@ -35,6 +35,7 @@ from classiq.interface.model.control import Control
35
35
  from classiq.interface.model.handle_binding import (
36
36
  FieldHandleBinding,
37
37
  HandleBinding,
38
+ HandlesList,
38
39
  SlicedHandleBinding,
39
40
  SubscriptHandleBinding,
40
41
  )
@@ -395,6 +396,9 @@ class DSLPrettyPrinter(ModelVisitor):
395
396
  def visit_FieldHandleBinding(self, var_ref: FieldHandleBinding) -> str:
396
397
  return f"{self.visit(var_ref.base_handle)}.{self.visit(var_ref.field)}"
397
398
 
399
+ def visit_HandlesList(self, handles: HandlesList) -> str:
400
+ return f"{{{', '.join(map(self.visit, handles.handles))}}}"
401
+
398
402
  def visit_ArithmeticOperation(self, arith_op: ArithmeticOperation) -> str:
399
403
  if arith_op.operation_kind == ArithmeticOperationKind.Assignment:
400
404
  op = "="
@@ -37,6 +37,7 @@ from classiq.interface.model.control import Control
37
37
  from classiq.interface.model.handle_binding import (
38
38
  FieldHandleBinding,
39
39
  HandleBinding,
40
+ HandlesList,
40
41
  SlicedHandleBinding,
41
42
  SubscriptHandleBinding,
42
43
  )
@@ -518,6 +519,9 @@ class PythonPrettyPrinter(ModelVisitor):
518
519
  def visit_FieldHandleBinding(self, var_ref: FieldHandleBinding) -> str:
519
520
  return f"{self.visit(var_ref.base_handle)}.{self.visit(var_ref.field)}"
520
521
 
522
+ def visit_HandlesList(self, handles: HandlesList) -> str:
523
+ return self.visit(handles.handles)
524
+
521
525
  def visit_ArithmeticOperation(
522
526
  self, arith_op: ArithmeticOperation, in_lambda: bool = False
523
527
  ) -> str: