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
@@ -20,6 +20,7 @@ from classiq.interface.generator.functions.type_name import (
20
20
  from classiq.interface.model.classical_parameter_declaration import (
21
21
  ClassicalParameterDeclaration,
22
22
  )
23
+ from classiq.interface.model.handle_binding import HandleBinding
23
24
  from classiq.interface.model.port_declaration import PortDeclaration
24
25
  from classiq.interface.model.quantum_function_declaration import (
25
26
  PositionalArg,
@@ -46,7 +47,12 @@ from classiq.model_expansions.evaluators.quantum_type_utils import (
46
47
  set_length,
47
48
  set_size,
48
49
  )
49
- from classiq.model_expansions.scope import Evaluated, QuantumSymbol, Scope
50
+ from classiq.model_expansions.scope import (
51
+ Evaluated,
52
+ QuantumSymbol,
53
+ QuantumVariable,
54
+ Scope,
55
+ )
50
56
 
51
57
 
52
58
  def evaluate_parameter_types_from_args(
@@ -83,10 +89,10 @@ def _update_scope(
83
89
  return
84
90
  if parameter.direction is PortDeclarationDirection.Output:
85
91
  return
86
- quantum_symbol = argument.as_type(QuantumSymbol)
92
+ quantum_var = argument.as_type(QuantumVariable)
87
93
  casted_argument = _cast(
88
94
  parameter.quantum_type,
89
- quantum_symbol,
95
+ quantum_var.quantum_type,
90
96
  parameter.name,
91
97
  )
92
98
  closure.scope[parameter.name] = Evaluated(
@@ -120,12 +126,12 @@ def _update_operand_signature_environment(
120
126
 
121
127
 
122
128
  def _cast(
123
- quantum_type: QuantumType, quantum_symbol: QuantumSymbol, param_name: str
129
+ parameter_type: QuantumType, argument_type: QuantumType, param_name: str
124
130
  ) -> QuantumSymbol:
125
- updated_quantum_type = quantum_type.model_copy()
126
- _inject_quantum_arg_info_to_type(updated_quantum_type, quantum_symbol, param_name)
131
+ updated_quantum_type = parameter_type.model_copy()
132
+ _inject_quantum_arg_info_to_type(updated_quantum_type, argument_type, param_name)
127
133
  return QuantumSymbol(
128
- handle=quantum_symbol.handle, quantum_type=updated_quantum_type
134
+ handle=HandleBinding(name=param_name), quantum_type=updated_quantum_type
129
135
  )
130
136
 
131
137
 
@@ -148,7 +154,9 @@ def _evaluate_type_from_arg(
148
154
  )
149
155
  if parameter.direction != PortDeclarationDirection.Output:
150
156
  updated_quantum_type = _inject_quantum_arg_info_to_type(
151
- updated_quantum_type, argument.as_type(QuantumSymbol), parameter.name
157
+ updated_quantum_type,
158
+ argument.as_type(QuantumVariable).quantum_type,
159
+ parameter.name,
152
160
  )
153
161
  return parameter.model_copy(update={"quantum_type": updated_quantum_type})
154
162
 
@@ -280,11 +288,11 @@ def evaluate_types_in_quantum_symbols(
280
288
 
281
289
 
282
290
  def _inject_quantum_arg_info_to_type(
283
- parameter_type: QuantumType, quantum_symbol: QuantumSymbol, param_name: str
291
+ parameter_type: QuantumType, argument_type: QuantumType, param_name: str
284
292
  ) -> QuantumType:
285
- if quantum_symbol.quantum_type.has_size_in_bits:
293
+ if argument_type.has_size_in_bits:
286
294
  copy_type_information(
287
- quantum_symbol.quantum_type,
295
+ argument_type,
288
296
  parameter_type,
289
297
  param_name,
290
298
  )
@@ -1,7 +1,7 @@
1
1
  import ast
2
2
  from collections.abc import Mapping
3
3
  from enum import EnumMeta
4
- from typing import Any, Optional
4
+ from typing import Any
5
5
 
6
6
  from sympy import SympifyError, sympify
7
7
 
@@ -50,11 +50,25 @@ def evaluate_constants_as_python(constants: list[Constant]) -> dict[str, Any]:
50
50
  }
51
51
 
52
52
 
53
+ def _quick_eval(expr: str) -> Any:
54
+ try:
55
+ return int(expr)
56
+ except ValueError:
57
+ pass
58
+ try:
59
+ return float(expr)
60
+ except ValueError:
61
+ pass
62
+ return None
63
+
64
+
53
65
  def evaluate(
54
- expr: Expression,
55
- locals_dict: Mapping[str, EvaluatedExpression],
56
- uninitialized_locals: Optional[set[str]] = None,
66
+ expr: Expression, locals_dict: Mapping[str, EvaluatedExpression]
57
67
  ) -> EvaluatedExpression:
68
+ val = _quick_eval(expr.expr)
69
+ if val is not None:
70
+ return EvaluatedExpression(value=val)
71
+
58
72
  model_locals: dict[str, ExpressionValue] = {}
59
73
  model_locals.update(ATOMIC_EXPRESSION_FUNCTIONS)
60
74
  model_locals.update(
@@ -65,9 +79,8 @@ def evaluate(
65
79
  )
66
80
  # locals override builtin-functions
67
81
  model_locals.update({name: expr.value for name, expr in locals_dict.items()})
68
- uninitialized_locals = uninitialized_locals or set()
69
82
 
70
- _validate_undefined_vars(expr.expr, model_locals, uninitialized_locals)
83
+ _validate_undefined_vars(expr.expr, model_locals)
71
84
 
72
85
  sympy_expr = translate_to_sympy(expr.expr)
73
86
  try:
@@ -95,11 +108,8 @@ def evaluate(
95
108
 
96
109
 
97
110
  def _validate_undefined_vars(
98
- expr: str,
99
- model_locals: dict[str, ExpressionValue],
100
- uninitialized_locals: Optional[set[str]],
111
+ expr: str, model_locals: dict[str, ExpressionValue]
101
112
  ) -> None:
102
- uninitialized_locals = uninitialized_locals or set()
103
113
  id_visitor = _VarsCollector()
104
114
  id_visitor.visit(ast.parse(expr))
105
115
  identifiers = id_visitor.vars
@@ -108,7 +118,6 @@ def _validate_undefined_vars(
108
118
  - model_locals.keys()
109
119
  - set(SYMPY_SUPPORTED_EXPRESSIONS)
110
120
  - set(symbolic.__all__)
111
- - uninitialized_locals
112
121
  )
113
122
 
114
123
  if len(undefined_vars) == 1:
@@ -134,7 +134,7 @@ class _InterpreterExpandable(QFunc):
134
134
  dummy_function = NativeFunctionDefinition(
135
135
  name=current_operation.name,
136
136
  positional_arg_declarations=current_operation.positional_arg_declarations,
137
- body=self._interpreter._builder._current_statements + [stmt],
137
+ body=[stmt],
138
138
  )
139
139
  declarative_functions = {
140
140
  name: func
@@ -24,6 +24,7 @@ from classiq.interface.generator.types.compilation_metadata import CompilationMe
24
24
  from classiq.interface.model.handle_binding import (
25
25
  FieldHandleBinding,
26
26
  HandleBinding,
27
+ HandlesList,
27
28
  SlicedHandleBinding,
28
29
  SubscriptHandleBinding,
29
30
  )
@@ -51,7 +52,12 @@ from classiq.model_expansions.function_builder import (
51
52
  OperationBuilder,
52
53
  OperationContext,
53
54
  )
54
- from classiq.model_expansions.scope import Evaluated, QuantumSymbol, Scope
55
+ from classiq.model_expansions.scope import (
56
+ Evaluated,
57
+ QuantumSymbol,
58
+ QuantumSymbolList,
59
+ Scope,
60
+ )
55
61
  from classiq.model_expansions.scope_initialization import (
56
62
  add_entry_point_params_to_scope,
57
63
  init_builtin_types,
@@ -98,10 +104,8 @@ class BaseInterpreter:
98
104
  main_closure.positional_arg_declarations, main_closure
99
105
  )
100
106
  context = self._expand_operation(main_closure)
101
- self._expanded_functions[main_closure.closure_id] = (
102
- self._builder.create_definition(
103
- cast(FunctionContext, context), main_closure.positional_arg_declarations
104
- )
107
+ self._expanded_functions[main_closure.name] = self._builder.create_definition(
108
+ cast(FunctionContext, context), main_closure.positional_arg_declarations
105
109
  )
106
110
 
107
111
  def _get_main_closure(self, main_func: FunctionClosure) -> FunctionClosure:
@@ -233,6 +237,14 @@ class BaseInterpreter:
233
237
  )
234
238
  return Evaluated(value=fields[field_name])
235
239
 
240
+ @evaluate.register
241
+ def evaluate_handles_list(self, handles_list: HandlesList) -> Evaluated:
242
+ return Evaluated(
243
+ value=QuantumSymbolList.from_symbols(
244
+ [self.evaluate(handle).value for handle in handles_list.handles]
245
+ )
246
+ )
247
+
236
248
  @abstractmethod
237
249
  def emit(self, statement: QuantumStatement) -> None:
238
250
  pass
@@ -261,19 +273,19 @@ class BaseInterpreter:
261
273
 
262
274
  def _expand_operation(self, operation: Closure) -> OperationContext:
263
275
  with self._builder.operation_context(operation) as context:
264
- if isinstance(operation, FunctionClosure) and (
265
- (func_def := self._expanded_functions.get(operation.closure_id))
266
- is not None
267
- ):
268
- cached_closure = self._top_level_scope[func_def.name].value
269
- operation.captured_vars.set(
270
- cached_closure.captured_vars, cached_closure, operation
271
- )
272
- else:
273
- self._expand_body(operation)
276
+ self._expand_body(operation)
274
277
 
275
278
  return context
276
279
 
280
+ def _expand_cached_function(
281
+ self, operation: FunctionClosure, func_def: NativeFunctionDefinition
282
+ ) -> None:
283
+ with self._builder.operation_context(operation):
284
+ cached_closure = self._top_level_scope[func_def.name].value
285
+ operation.captured_vars.set(
286
+ cached_closure.captured_vars, cached_closure, operation
287
+ )
288
+
277
289
  def _expand_body(self, operation: Closure) -> None:
278
290
  for block, block_body in operation.blocks.items():
279
291
  self._expand_block(block_body, block)
@@ -6,11 +6,7 @@ from pydantic import ValidationError
6
6
  from classiq.interface.exceptions import ClassiqError
7
7
  from classiq.interface.model.allocate import Allocate
8
8
  from classiq.interface.model.bind_operation import BindOperation
9
- from classiq.interface.model.native_function_definition import NativeFunctionDefinition
10
9
  from classiq.interface.model.quantum_function_call import QuantumFunctionCall
11
- from classiq.interface.model.quantum_function_declaration import (
12
- NamedParamsQuantumFunctionDeclaration,
13
- )
14
10
  from classiq.interface.source_reference import SourceReference
15
11
 
16
12
  from classiq.model_expansions.closure import FunctionClosure, GenerativeFunctionClosure
@@ -23,22 +19,10 @@ from classiq.model_expansions.quantum_operations.quantum_function_call import (
23
19
  DeclarativeQuantumFunctionCallEmitter,
24
20
  )
25
21
  from classiq.model_expansions.scope import Scope
26
- from classiq.model_expansions.visitors.symbolic_param_inference import (
27
- SymbolicParamInference,
28
- )
29
22
  from classiq.qmod.model_state_container import QMODULE
30
23
 
31
24
 
32
25
  class FrontendGenerativeInterpreter(GenerativeInterpreter):
33
- def infer_symbolic_parameters(
34
- self,
35
- functions: list[NativeFunctionDefinition],
36
- additional_signatures: (
37
- list[NamedParamsQuantumFunctionDeclaration] | None
38
- ) = None,
39
- ) -> None:
40
- SymbolicParamInference(functions, additional_signatures).infer()
41
-
42
26
  def emit_allocate(self, allocate: Allocate) -> None:
43
27
  AllocateEmitter(self, allow_symbolic_size=True).emit(allocate)
44
28
 
@@ -1,5 +1,5 @@
1
1
  from functools import singledispatchmethod
2
- from typing import Any
2
+ from typing import Any, Optional
3
3
 
4
4
  import numpy as np
5
5
  from numpy.random import permutation
@@ -104,9 +104,9 @@ class GenerativeInterpreter(BaseInterpreter):
104
104
  def infer_symbolic_parameters(
105
105
  self,
106
106
  functions: list[NativeFunctionDefinition],
107
- additional_signatures: (
108
- list[NamedParamsQuantumFunctionDeclaration] | None
109
- ) = None,
107
+ additional_signatures: Optional[
108
+ list[NamedParamsQuantumFunctionDeclaration]
109
+ ] = None,
110
110
  ) -> None:
111
111
  pass
112
112
 
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING
1
+ from typing import TYPE_CHECKING, Optional
2
2
 
3
3
  import sympy
4
4
 
@@ -49,7 +49,7 @@ class AllocateEmitter(Emitter[Allocate]):
49
49
  self.emit_statement(allocate)
50
50
  return True
51
51
 
52
- def _get_var_size(self, target: QuantumSymbol, size: Expression | None) -> str:
52
+ def _get_var_size(self, target: QuantumSymbol, size: Optional[Expression]) -> str:
53
53
  if size is None:
54
54
  if not target.quantum_type.is_evaluated:
55
55
  raise ClassiqValueError(
@@ -1,3 +1,5 @@
1
+ from typing import Optional
2
+
1
3
  from classiq.interface.exceptions import ClassiqExpansionError
2
4
  from classiq.interface.generator.arith.arithmetic import compute_arithmetic_result_type
3
5
  from classiq.interface.generator.expressions.expression import Expression
@@ -69,7 +71,7 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
69
71
  self._assign_to_inferred_var_and_bind(op, result_type, inferred_result_type)
70
72
  return True
71
73
 
72
- def _infer_result_type(self, op: ArithmeticOperation) -> QuantumNumeric | None:
74
+ def _infer_result_type(self, op: ArithmeticOperation) -> Optional[QuantumNumeric]:
73
75
  expr = self._evaluate_expression(op.expression)
74
76
  if len(self._get_classical_vars_in_expression(expr)):
75
77
  return None
@@ -4,6 +4,7 @@ from typing import (
4
4
  TYPE_CHECKING,
5
5
  Any,
6
6
  Generic,
7
+ Optional,
7
8
  cast,
8
9
  )
9
10
  from uuid import UUID
@@ -27,16 +28,19 @@ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_insta
27
28
  from classiq.interface.generator.functions.port_declaration import (
28
29
  PortDeclarationDirection,
29
30
  )
31
+ from classiq.interface.generator.functions.type_qualifier import TypeQualifier
30
32
  from classiq.interface.model.classical_parameter_declaration import (
31
33
  ClassicalParameterDeclaration,
32
34
  )
33
35
  from classiq.interface.model.handle_binding import HandleBinding
36
+ from classiq.interface.model.model import MAIN_FUNCTION_NAME
34
37
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
35
38
  from classiq.interface.model.port_declaration import PortDeclaration
36
39
  from classiq.interface.model.quantum_function_call import ArgValue, QuantumFunctionCall
37
40
  from classiq.interface.model.quantum_function_declaration import (
38
41
  NamedParamsQuantumFunctionDeclaration,
39
42
  PositionalArg,
43
+ QuantumOperandDeclaration,
40
44
  )
41
45
  from classiq.interface.model.quantum_statement import QuantumStatement
42
46
  from classiq.interface.model.variable_declaration_statement import (
@@ -48,7 +52,7 @@ from classiq.model_expansions.capturing.captured_vars import (
48
52
  UNINITIALIZED_VAR_MESSAGE,
49
53
  validate_args_are_not_propagated,
50
54
  )
51
- from classiq.model_expansions.closure import FunctionClosure
55
+ from classiq.model_expansions.closure import Closure, FunctionClosure
52
56
  from classiq.model_expansions.evaluators.argument_types import (
53
57
  add_information_from_output_arguments,
54
58
  )
@@ -62,7 +66,19 @@ from classiq.model_expansions.quantum_operations.emitter import (
62
66
  Emitter,
63
67
  QuantumStatementT,
64
68
  )
65
- from classiq.model_expansions.scope import Evaluated, QuantumSymbol, Scope
69
+ from classiq.model_expansions.quantum_operations.function_calls_cache import (
70
+ get_func_call_cache_key,
71
+ )
72
+ from classiq.model_expansions.scope import (
73
+ Evaluated,
74
+ QuantumSymbol,
75
+ QuantumSymbolList,
76
+ QuantumVariable,
77
+ Scope,
78
+ )
79
+ from classiq.model_expansions.transformers.type_qualifier_inference import (
80
+ TypeQualifierInference,
81
+ )
66
82
  from classiq.model_expansions.transformers.var_splitter import VarSplitter
67
83
  from classiq.model_expansions.utils.text_utils import are, readable_list, s
68
84
  from classiq.qmod.semantics.validation.signature_validation import (
@@ -74,11 +90,14 @@ if TYPE_CHECKING:
74
90
 
75
91
 
76
92
  def _validate_cloning(evaluated_args: list[Evaluated]) -> None:
77
- handles = [
78
- arg.value.handle
93
+ handles = chain.from_iterable(
94
+ (
95
+ [arg.value.handle]
96
+ if isinstance(arg.value, QuantumSymbol)
97
+ else arg.value.handles if isinstance(arg.value, QuantumSymbolList) else []
98
+ )
79
99
  for arg in evaluated_args
80
- if isinstance(arg.value, QuantumSymbol)
81
- ]
100
+ )
82
101
  for handle, other_handle in combinations(handles, 2):
83
102
  if handle.overlaps(other_handle):
84
103
  if handle == other_handle:
@@ -130,7 +149,7 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
130
149
  self,
131
150
  function: FunctionClosure,
132
151
  args: list[ArgValue],
133
- propagated_debug_info: FunctionDebugInfo | None,
152
+ propagated_debug_info: Optional[FunctionDebugInfo],
134
153
  ) -> QuantumFunctionCall:
135
154
  call = self._create_quantum_function_call(
136
155
  function, args, propagated_debug_info=propagated_debug_info
@@ -140,8 +159,8 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
140
159
 
141
160
  @staticmethod
142
161
  def _get_back_ref(
143
- propagated_debug_info: FunctionDebugInfo | None,
144
- ) -> UUID | None:
162
+ propagated_debug_info: Optional[FunctionDebugInfo],
163
+ ) -> Optional[UUID]:
145
164
  if propagated_debug_info is None:
146
165
  return None
147
166
  if propagated_debug_info.node is None:
@@ -152,7 +171,7 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
152
171
  self,
153
172
  function: FunctionClosure,
154
173
  args: list[ArgValue],
155
- propagated_debug_info: FunctionDebugInfo | None,
174
+ propagated_debug_info: Optional[FunctionDebugInfo],
156
175
  ) -> QuantumFunctionCall:
157
176
  function = function.clone()
158
177
  function = function.set_depth(self._builder.current_function.depth + 1)
@@ -164,19 +183,22 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
164
183
  )
165
184
  new_positional_arg_decls = new_declaration.positional_arg_declarations
166
185
  if not self.should_expand_function(function, evaluated_args):
167
- is_atomic = True
168
186
  new_declaration = self._expanded_functions_by_name.get(
169
187
  function.name, new_declaration
170
188
  )
171
189
  else:
172
- is_atomic = False
173
190
  new_declaration = self._expand_function(
174
191
  evaluated_args, new_declaration, function
175
192
  )
193
+ new_positional_arg_decls = new_declaration.positional_arg_declarations
194
+ evaluated_args = [
195
+ arg
196
+ for arg in evaluated_args
197
+ if isinstance(arg.value, QuantumVariable) or _is_symbolic(arg.value)
198
+ ]
176
199
 
177
- new_positional_args = self._get_new_positional_args(
178
- evaluated_args, is_atomic, new_positional_arg_decls
179
- )
200
+ add_information_from_output_arguments(new_positional_arg_decls, evaluated_args)
201
+ new_positional_args = [arg.emit() for arg in evaluated_args]
180
202
  captured_args = function.captured_vars.filter_vars(function).get_captured_args(
181
203
  self._builder.current_function
182
204
  )
@@ -219,18 +241,20 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
219
241
  function: FunctionClosure,
220
242
  ) -> NamedParamsQuantumFunctionDeclaration:
221
243
  self._add_params_to_scope(decl.positional_arg_declarations, args, function)
222
- context = self._expand_operation(function.with_new_declaration(decl))
223
- function_context = cast(FunctionContext, context)
224
- closure_id = function_context.closure.closure_id
225
- if closure_id in self._expanded_functions:
226
- function_def = self._expanded_functions[closure_id]
244
+ function = function.with_new_declaration(decl)
245
+ cache_key = get_func_call_cache_key(decl, args)
246
+ if cache_key in self._expanded_functions:
247
+ function_def = self._expanded_functions[cache_key]
248
+ self._expand_cached_function(function, function_def)
227
249
  self._expanded_functions_compilation_metadata[
228
250
  function_def.name
229
251
  ].occurrences_number += 1
230
252
  return function_def
231
253
 
254
+ context = self._expand_operation(function)
255
+ function_context = cast(FunctionContext, context)
232
256
  function_def = self._create_function_definition(function_context, args)
233
- self._expanded_functions[closure_id] = function_def
257
+ self._expanded_functions[cache_key] = function_def
234
258
  self._top_level_scope[function_def.name] = Evaluated(
235
259
  value=function_context.closure.with_new_declaration(function_def)
236
260
  )
@@ -260,6 +284,7 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
260
284
  )
261
285
  captured_ports = captured_vars.get_captured_parameters()
262
286
  if len(captured_ports) == 0:
287
+ self._override_type_qualifier(function_context, func_def)
263
288
  return func_def
264
289
  func_def.positional_arg_declarations = list(
265
290
  chain.from_iterable((func_def.positional_arg_declarations, captured_ports))
@@ -271,6 +296,8 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
271
296
  rewrite_mapping |= captured_vars.get_classical_captured_mapping()
272
297
  func_def.body = self.rewrite(func_def.body, rewrite_mapping)
273
298
 
299
+ self._override_type_qualifier(function_context, func_def)
300
+
274
301
  return func_def
275
302
 
276
303
  @staticmethod
@@ -281,7 +308,7 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
281
308
  ) -> None:
282
309
  for parameter, argument in zip(parameters, arguments):
283
310
  param_handle = HandleBinding(name=parameter.name)
284
- if isinstance(argument.value, QuantumSymbol):
311
+ if isinstance(argument.value, QuantumVariable):
285
312
  assert isinstance(parameter, PortDeclaration)
286
313
  closure.scope[parameter.name] = Evaluated(
287
314
  QuantumSymbol(
@@ -299,26 +326,6 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
299
326
  else:
300
327
  closure.scope[parameter.name] = argument
301
328
 
302
- def _get_new_positional_args(
303
- self,
304
- evaluated_args: list[Evaluated],
305
- is_atomic: bool,
306
- new_positional_arg_decls: Sequence[PositionalArg],
307
- ) -> list[ArgValue]:
308
- evaluated_args = add_information_from_output_arguments(
309
- new_positional_arg_decls, evaluated_args
310
- )
311
- if is_atomic:
312
- return [arg.emit() for arg in evaluated_args]
313
-
314
- positional_args = [
315
- arg.emit()
316
- for arg in evaluated_args
317
- if isinstance(arg.value, QuantumSymbol) or _is_symbolic(arg.value)
318
- ]
319
-
320
- return positional_args
321
-
322
329
  def _prepare_fully_typed_declaration(
323
330
  self, function: FunctionClosure, evaluated_args: list[Evaluated]
324
331
  ) -> NamedParamsQuantumFunctionDeclaration:
@@ -371,3 +378,45 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
371
378
  raise ClassiqExpansionError(UNINITIALIZED_VAR_MESSAGE.format(var_name))
372
379
  if var_state and param.direction == PortDeclarationDirection.Output:
373
380
  raise ClassiqExpansionError(INITIALIZED_VAR_MESSAGE.format(var_name))
381
+
382
+ def _override_type_qualifier(
383
+ self, func_context: FunctionContext, func_def: NativeFunctionDefinition
384
+ ) -> None:
385
+ """
386
+ The type qualifier can be changed according to the operand passed to the
387
+ function. For example,
388
+ apply_to_all(X, q) --> q will be QFree after expansion
389
+ apply_to_all(H, q) --> q will be Quantum after expansion
390
+ This also holds for the intermediate lambda created during the expansion.
391
+
392
+ We don't override the type qualifier if it's explicitly specified (QFree or
393
+ Const), neither in the function declaration nor in the operand declaration.
394
+ """
395
+
396
+ if func_context.is_lambda:
397
+ self._update_type_qualifiers(func_def)
398
+ return
399
+
400
+ orig_name = func_context.name
401
+ if (
402
+ orig_name == MAIN_FUNCTION_NAME
403
+ or orig_name not in func_context.closure.scope
404
+ ):
405
+ return
406
+
407
+ orig_func = func_context.closure.scope[orig_name].value
408
+ if isinstance(orig_func, Closure) and not any(
409
+ isinstance(param_decl, QuantumOperandDeclaration)
410
+ for param_decl in orig_func.positional_arg_declarations
411
+ ):
412
+ return
413
+
414
+ self._update_type_qualifiers(func_def)
415
+
416
+ @staticmethod
417
+ def _update_type_qualifiers(func_def: NativeFunctionDefinition) -> None:
418
+ # only override the qualifier if it's unspecified (not QFree or Const)
419
+ for port in func_def.port_declarations:
420
+ if port.type_qualifier is TypeQualifier.Quantum:
421
+ port.type_qualifier = TypeQualifier.Inferred
422
+ TypeQualifierInference().run(func_def.port_declarations, func_def.body)
@@ -47,7 +47,7 @@ from classiq.interface.model.quantum_function_declaration import (
47
47
  )
48
48
  from classiq.interface.model.quantum_statement import QuantumOperation, QuantumStatement
49
49
 
50
- from classiq.model_expansions.closure import Closure, GenerativeClosure
50
+ from classiq.model_expansions.closure import Closure, FunctionClosure, GenerativeClosure
51
51
  from classiq.model_expansions.function_builder import (
52
52
  OperationBuilder,
53
53
  OperationContext,
@@ -87,6 +87,11 @@ class Emitter(Generic[QuantumStatementT], ABC):
87
87
  def _expand_operation(self, closure: Closure) -> OperationContext:
88
88
  return self._interpreter._expand_operation(closure)
89
89
 
90
+ def _expand_cached_function(
91
+ self, closure: FunctionClosure, func_def: NativeFunctionDefinition
92
+ ) -> None:
93
+ return self._interpreter._expand_cached_function(closure, func_def)
94
+
90
95
  @property
91
96
  def _builder(self) -> OperationBuilder:
92
97
  return self._interpreter._builder
@@ -181,12 +186,7 @@ class Emitter(Generic[QuantumStatementT], ABC):
181
186
  self._capture_classical_var(var_name, var_type)
182
187
 
183
188
  def _update_captured_vars(self, op: QuantumOperation) -> None:
184
- handles = (
185
- [(handle, PortDeclarationDirection.Input) for handle in op.inputs]
186
- + [(handle, PortDeclarationDirection.Output) for handle in op.outputs]
187
- + [(handle, PortDeclarationDirection.Inout) for handle in op.inouts]
188
- )
189
- for handle, direction in handles:
189
+ for handle, direction in op.handles_with_directions:
190
190
  self._capture_handle(handle, direction)
191
191
 
192
192
  def _capture_handle(