classiq 0.70.0__py3-none-any.whl → 0.71.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 (56) hide show
  1. classiq/interface/_version.py +1 -1
  2. classiq/interface/debug_info/debug_info.py +4 -0
  3. classiq/interface/generator/expressions/expression_constants.py +0 -3
  4. classiq/interface/generator/expressions/expression_types.py +4 -2
  5. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +4 -0
  6. classiq/interface/generator/expressions/proxies/classical/classical_struct_proxy.py +5 -1
  7. classiq/interface/generator/expressions/proxies/classical/utils.py +34 -0
  8. classiq/interface/generator/functions/classical_type.py +1 -1
  9. classiq/interface/generator/functions/type_name.py +16 -0
  10. classiq/interface/generator/functions/type_qualifier.py +7 -0
  11. classiq/interface/generator/generated_circuit_data.py +1 -1
  12. classiq/interface/generator/quantum_function_call.py +8 -1
  13. classiq/interface/generator/synthesis_execution_parameter.py +1 -0
  14. classiq/interface/generator/types/compilation_metadata.py +1 -0
  15. classiq/interface/ide/visual_model.py +1 -0
  16. classiq/interface/interface_version.py +1 -1
  17. classiq/interface/model/allocate.py +7 -0
  18. classiq/interface/model/block.py +12 -0
  19. classiq/interface/model/classical_if.py +4 -0
  20. classiq/interface/model/inplace_binary_operation.py +4 -0
  21. classiq/interface/model/model.py +3 -1
  22. classiq/interface/model/phase_operation.py +4 -0
  23. classiq/interface/model/port_declaration.py +3 -0
  24. classiq/interface/model/power.py +4 -0
  25. classiq/interface/model/quantum_expressions/quantum_expression.py +4 -0
  26. classiq/interface/model/quantum_function_call.py +4 -0
  27. classiq/interface/model/quantum_function_declaration.py +1 -1
  28. classiq/interface/model/quantum_statement.py +5 -0
  29. classiq/interface/model/quantum_type.py +22 -0
  30. classiq/interface/model/repeat.py +4 -0
  31. classiq/interface/model/statement_block.py +3 -0
  32. classiq/interface/model/variable_declaration_statement.py +5 -0
  33. classiq/model_expansions/capturing/captured_vars.py +156 -34
  34. classiq/model_expansions/evaluators/classical_type_inference.py +70 -0
  35. classiq/model_expansions/evaluators/parameter_types.py +14 -0
  36. classiq/model_expansions/expression_evaluator.py +0 -11
  37. classiq/model_expansions/function_builder.py +2 -8
  38. classiq/model_expansions/generative_functions.py +7 -30
  39. classiq/model_expansions/interpreters/base_interpreter.py +3 -4
  40. classiq/model_expansions/quantum_operations/call_emitter.py +46 -6
  41. classiq/model_expansions/quantum_operations/emitter.py +41 -0
  42. classiq/model_expansions/quantum_operations/expression_evaluator.py +4 -0
  43. classiq/model_expansions/scope.py +0 -8
  44. classiq/model_expansions/scope_initialization.py +20 -28
  45. classiq/qmod/__init__.py +3 -1
  46. classiq/qmod/declaration_inferrer.py +52 -24
  47. classiq/qmod/native/pretty_printer.py +25 -3
  48. classiq/qmod/pretty_print/pretty_printer.py +31 -14
  49. classiq/qmod/python_classical_type.py +12 -1
  50. classiq/qmod/qfunc.py +33 -8
  51. classiq/qmod/qmod_variable.py +188 -147
  52. classiq/qmod/quantum_function.py +3 -4
  53. classiq/qmod/semantics/validation/type_hints.py +19 -10
  54. {classiq-0.70.0.dist-info → classiq-0.71.0.dist-info}/METADATA +1 -1
  55. {classiq-0.70.0.dist-info → classiq-0.71.0.dist-info}/RECORD +56 -52
  56. {classiq-0.70.0.dist-info → classiq-0.71.0.dist-info}/WHEEL +0 -0
@@ -2,17 +2,29 @@ from collections.abc import Sequence
2
2
  from itertools import chain, combinations
3
3
  from typing import (
4
4
  TYPE_CHECKING,
5
+ Any,
5
6
  Generic,
6
7
  cast,
7
8
  )
8
9
  from uuid import UUID
9
10
 
11
+ import sympy
12
+
10
13
  from classiq.interface.debug_info.debug_info import FunctionDebugInfo
11
14
  from classiq.interface.exceptions import ClassiqExpansionError
15
+ from classiq.interface.generator.expressions.proxies.classical.classical_proxy import (
16
+ ClassicalProxy,
17
+ )
18
+ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_instance import (
19
+ QmodStructInstance,
20
+ )
12
21
  from classiq.interface.generator.functions.port_declaration import (
13
22
  PortDeclarationDirection,
14
23
  )
15
24
  from classiq.interface.generator.generated_circuit_data import OperationLevel
25
+ from classiq.interface.model.classical_parameter_declaration import (
26
+ ClassicalParameterDeclaration,
27
+ )
16
28
  from classiq.interface.model.handle_binding import HandleBinding
17
29
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
18
30
  from classiq.interface.model.port_declaration import PortDeclaration
@@ -75,6 +87,16 @@ def _validate_cloning(evaluated_args: list[Evaluated]) -> None:
75
87
  )
76
88
 
77
89
 
90
+ def _is_symbolic(arg: Any) -> bool:
91
+ if isinstance(arg, list):
92
+ return any(_is_symbolic(item) for item in arg)
93
+ if isinstance(arg, QmodStructInstance):
94
+ return any(_is_symbolic(item) for item in arg.fields.values())
95
+ if isinstance(arg, sympy.Basic):
96
+ return len(arg.free_symbols) > 0
97
+ return isinstance(arg, ClassicalProxy)
98
+
99
+
78
100
  class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSplitter):
79
101
  def __init__(self, interpreter: "BaseInterpreter") -> None:
80
102
  Emitter.__init__(self, interpreter)
@@ -197,7 +219,7 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
197
219
  ].occurrences_number += 1
198
220
  return function_def
199
221
 
200
- function_def = self._create_function_definition(function_context)
222
+ function_def = self._create_function_definition(function_context, args)
201
223
  self._expanded_functions[closure_id] = function_def
202
224
  self._top_level_scope[function_def.name] = Evaluated(
203
225
  value=function_context.closure.with_new_declaration(function_def)
@@ -210,14 +232,23 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
210
232
  return function_def
211
233
 
212
234
  def _create_function_definition(
213
- self, function_context: FunctionContext
235
+ self, function_context: FunctionContext, args: list[Evaluated]
214
236
  ) -> NativeFunctionDefinition:
215
- func_def = self._builder.create_definition(function_context)
237
+ params = [
238
+ param
239
+ for arg, param in zip(args, function_context.positional_arg_declarations)
240
+ if isinstance(param, PortDeclaration)
241
+ or (
242
+ isinstance(param, ClassicalParameterDeclaration)
243
+ and _is_symbolic(arg.value)
244
+ )
245
+ ]
246
+ func_def = self._builder.create_definition(function_context, params)
216
247
 
217
248
  captured_vars = function_context.closure.captured_vars.filter_vars(
218
249
  function_context.closure
219
250
  )
220
- captured_ports = captured_vars.get_captured_ports()
251
+ captured_ports = captured_vars.get_captured_parameters()
221
252
  if len(captured_ports) == 0:
222
253
  return func_def
223
254
  func_def.positional_arg_declarations = list(
@@ -239,15 +270,22 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
239
270
  closure: FunctionClosure,
240
271
  ) -> None:
241
272
  for parameter, argument in zip(parameters, arguments):
273
+ param_handle = HandleBinding(name=parameter.name)
242
274
  if isinstance(argument.value, QuantumSymbol):
243
275
  assert isinstance(parameter, PortDeclaration)
244
276
  closure.scope[parameter.name] = Evaluated(
245
277
  QuantumSymbol(
246
- handle=HandleBinding(name=parameter.name),
278
+ handle=param_handle,
247
279
  quantum_type=parameter.quantum_type,
248
280
  ),
249
281
  defining_function=closure,
250
282
  )
283
+ elif _is_symbolic(argument.value):
284
+ assert isinstance(parameter, ClassicalParameterDeclaration)
285
+ closure.scope[parameter.name] = Evaluated(
286
+ value=parameter.classical_type.get_classical_proxy(param_handle),
287
+ defining_function=closure,
288
+ )
251
289
  else:
252
290
  closure.scope[parameter.name] = argument
253
291
 
@@ -264,7 +302,9 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
264
302
  return [arg.emit() for arg in evaluated_args]
265
303
 
266
304
  positional_args = [
267
- arg.emit() for arg in evaluated_args if isinstance(arg.value, QuantumSymbol)
305
+ arg.emit()
306
+ for arg in evaluated_args
307
+ if isinstance(arg.value, QuantumSymbol) or _is_symbolic(arg.value)
268
308
  ]
269
309
 
270
310
  return positional_args
@@ -19,6 +19,13 @@ from classiq.interface.generator.expressions.evaluated_expression import (
19
19
  EvaluatedExpression,
20
20
  )
21
21
  from classiq.interface.generator.expressions.expression import Expression
22
+ from classiq.interface.generator.expressions.proxies.classical.classical_proxy import (
23
+ ClassicalProxy,
24
+ )
25
+ from classiq.interface.generator.expressions.proxies.classical.utils import (
26
+ get_proxy_type,
27
+ )
28
+ from classiq.interface.generator.functions.classical_type import ClassicalType
22
29
  from classiq.interface.generator.functions.port_declaration import (
23
30
  PortDeclarationDirection,
24
31
  )
@@ -145,6 +152,7 @@ class Emitter(Generic[QuantumStatementT], ABC):
145
152
  return new_expression
146
153
 
147
154
  def emit_statement(self, statement: QuantumStatement) -> None:
155
+ self._update_captured_classical_vars(statement)
148
156
  if isinstance(statement, QuantumOperation):
149
157
  self._update_captured_vars(statement)
150
158
  if statement.uuid not in self._interpreter._model.debug_info:
@@ -153,6 +161,11 @@ class Emitter(Generic[QuantumStatementT], ABC):
153
161
  )
154
162
  self._builder.emit_statement(statement)
155
163
 
164
+ def _update_captured_classical_vars(self, stmt: QuantumStatement) -> None:
165
+ for expr in stmt.expressions:
166
+ for var_name, var_type in self._get_classical_vars_in_expression(expr):
167
+ self._capture_classical_var(var_name, var_type)
168
+
156
169
  def _update_captured_vars(self, op: QuantumOperation) -> None:
157
170
  handles = (
158
171
  [(handle, PortDeclarationDirection.Input) for handle in op.inputs]
@@ -178,6 +191,18 @@ class Emitter(Generic[QuantumStatementT], ABC):
178
191
  direction=direction,
179
192
  )
180
193
 
194
+ def _capture_classical_var(self, var_name: str, var_type: ClassicalType) -> None:
195
+ if var_name not in self._current_scope:
196
+ return
197
+ defining_function = self._current_scope[var_name].defining_function
198
+ if defining_function is None:
199
+ raise ClassiqInternalExpansionError
200
+ self._builder.current_block.captured_vars.capture_classical_var(
201
+ var_name=var_name,
202
+ var_type=var_type,
203
+ defining_function=defining_function,
204
+ )
205
+
181
206
  def _get_symbols_in_expression(self, expr: Expression) -> list[QuantumSymbol]:
182
207
  vrc = VarRefCollector(ignore_duplicated_handles=True)
183
208
  vrc.visit(ast.parse(expr.expr))
@@ -187,3 +212,19 @@ class Emitter(Generic[QuantumStatementT], ABC):
187
212
  if isinstance(self._current_scope[handle.name].value, QuantumSymbol)
188
213
  )
189
214
  return [self._interpreter.evaluate(handle).value for handle in handles]
215
+
216
+ def _get_classical_vars_in_expression(
217
+ self, expr: Expression
218
+ ) -> list[tuple[str, ClassicalType]]:
219
+ vrc = VarRefCollector(ignore_duplicated_handles=True, ignore_sympy_symbols=True)
220
+ vrc.visit(ast.parse(expr.expr))
221
+ return list(
222
+ {
223
+ handle.name: get_proxy_type(proxy)
224
+ for handle in vrc.var_handles
225
+ if handle.name in self._current_scope
226
+ and isinstance(
227
+ proxy := self._current_scope[handle.name].value, ClassicalProxy
228
+ )
229
+ }.items()
230
+ )
@@ -26,6 +26,10 @@ class ExpressionEvaluator(Emitter[QuantumOperation]):
26
26
  )
27
27
  for symbol in self._get_symbols_in_expression(evaluated_expression):
28
28
  self._capture_handle(symbol.handle, PortDeclarationDirection.Inout)
29
+ for var_name, var_type in self._get_classical_vars_in_expression(
30
+ evaluated_expression
31
+ ):
32
+ self._capture_classical_var(var_name, var_type)
29
33
  op = op.model_copy(
30
34
  update={self._expression_name: evaluated_expression, "back_ref": op.uuid}
31
35
  )
@@ -1,13 +1,10 @@
1
1
  import itertools
2
- import re
3
2
  from collections import UserDict
4
3
  from collections.abc import Iterator
5
4
  from dataclasses import dataclass
6
5
  from functools import singledispatch
7
6
  from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union
8
7
 
9
- from sympy import Symbol
10
-
11
8
  from classiq.interface.exceptions import (
12
9
  ClassiqExpansionError,
13
10
  ClassiqInternalExpansionError,
@@ -16,9 +13,6 @@ from classiq.interface.generator.expressions.evaluated_expression import (
16
13
  EvaluatedExpression,
17
14
  )
18
15
  from classiq.interface.generator.expressions.expression import Expression
19
- from classiq.interface.generator.expressions.expression_constants import (
20
- CPARAM_EXECUTION_SUFFIX_PATTERN,
21
- )
22
16
  from classiq.interface.generator.expressions.proxies.classical.qmod_struct_instance import (
23
17
  QmodStructInstance,
24
18
  )
@@ -204,8 +198,6 @@ class Scope(EvaluatedUserDict):
204
198
  return self.data[name]
205
199
  if self._parent is not None:
206
200
  return self._parent[name]
207
- if re.search(CPARAM_EXECUTION_SUFFIX_PATTERN, name):
208
- return Evaluated(value=Symbol(name))
209
201
  raise ClassiqExpansionError(f"Variable {name!r} is undefined")
210
202
 
211
203
  def __contains__(self, item: Any) -> bool:
@@ -7,6 +7,9 @@ from classiq.interface.generator.functions.classical_type import (
7
7
  ClassicalList,
8
8
  )
9
9
  from classiq.interface.generator.functions.concrete_types import ConcreteClassicalType
10
+ from classiq.interface.model.classical_parameter_declaration import (
11
+ ClassicalParameterDeclaration,
12
+ )
10
13
  from classiq.interface.model.handle_binding import HandleBinding
11
14
  from classiq.interface.model.model import MAIN_FUNCTION_NAME, Model
12
15
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
@@ -102,17 +105,24 @@ def add_entry_point_params_to_scope(
102
105
  parameters: Sequence[PositionalArg], main_closure: FunctionClosure
103
106
  ) -> None:
104
107
  for parameter in parameters:
105
- if not isinstance(parameter, PortDeclaration):
106
- continue
107
- main_closure.scope[parameter.name] = Evaluated(
108
- value=QuantumSymbol(
109
- handle=HandleBinding(name=parameter.name),
110
- quantum_type=evaluate_type_in_quantum_symbol(
111
- parameter.quantum_type, main_closure.scope, parameter.name
108
+ if isinstance(parameter, PortDeclaration):
109
+ main_closure.scope[parameter.name] = Evaluated(
110
+ value=QuantumSymbol(
111
+ handle=HandleBinding(name=parameter.name),
112
+ quantum_type=evaluate_type_in_quantum_symbol(
113
+ parameter.quantum_type, main_closure.scope, parameter.name
114
+ ),
112
115
  ),
113
- ),
114
- defining_function=main_closure,
115
- )
116
+ defining_function=main_closure,
117
+ )
118
+ elif isinstance(parameter, ClassicalParameterDeclaration):
119
+ param_val = parameter.classical_type.get_classical_proxy(
120
+ handle=HandleBinding(name=parameter.name)
121
+ )
122
+ main_closure.scope[parameter.name] = Evaluated(
123
+ value=param_val,
124
+ defining_function=main_closure,
125
+ )
116
126
 
117
127
 
118
128
  def init_top_level_scope(model: Model, scope: Scope) -> None:
@@ -126,24 +136,6 @@ def init_builtin_types() -> None:
126
136
  QMODULE.type_decls |= BUILTIN_STRUCT_DECLARATIONS
127
137
 
128
138
 
129
- def init_exec_params(model: Model, scope: Scope) -> dict[str, ConcreteClassicalType]:
130
- if model.execution_parameters is not None:
131
- exec_params = model.execution_parameters
132
- else:
133
- exec_params = {
134
- param.name: param.classical_type
135
- for param in model.function_dict.get(
136
- "_dec_main", model.main_func
137
- ).param_decls
138
- }
139
- for param_name, param_type in exec_params.items():
140
- param_val = param_type.get_classical_proxy(
141
- handle=HandleBinding(name=param_name)
142
- )
143
- scope[param_name] = Evaluated(value=param_val)
144
- return exec_params
145
-
146
-
147
139
  def _get_shape(classical_type: ConcreteClassicalType) -> tuple[int, ...]:
148
140
  if isinstance(classical_type, ClassicalList):
149
141
  raise ClassiqInternalExpansionError("Unexpected classical list")
classiq/qmod/__init__.py CHANGED
@@ -6,7 +6,7 @@ from .expression_query import get_expression_numeric_attributes
6
6
  from .qfunc import qfunc
7
7
  from .qmod_constant import QConstant
8
8
  from .qmod_parameter import Array, CArray, CBool, CInt, CReal
9
- from .qmod_variable import Input, Output, QArray, QBit, QNum, QStruct
9
+ from .qmod_variable import Const, Input, Output, QArray, QBit, QFree, QNum, QStruct
10
10
  from .quantum_callable import QCallable, QCallableList
11
11
  from .write_qmod import write_qmod
12
12
 
@@ -18,6 +18,8 @@ __all__ = [
18
18
  "CReal",
19
19
  "Input",
20
20
  "Output",
21
+ "Const",
22
+ "QFree",
21
23
  "QArray",
22
24
  "QBit",
23
25
  "QNum",
@@ -19,6 +19,7 @@ from classiq.interface.generator.functions.concrete_types import ConcreteClassic
19
19
  from classiq.interface.generator.functions.port_declaration import (
20
20
  PortDeclarationDirection,
21
21
  )
22
+ from classiq.interface.generator.functions.type_qualifier import TypeQualifier
22
23
  from classiq.interface.generator.types.enum_declaration import declaration_from_enum
23
24
  from classiq.interface.generator.types.struct_declaration import StructDeclaration
24
25
  from classiq.interface.model.classical_parameter_declaration import (
@@ -36,7 +37,7 @@ from classiq.qmod.builtins.enums import BUILTIN_ENUM_DECLARATIONS
36
37
  from classiq.qmod.builtins.structs import BUILTIN_STRUCT_DECLARATIONS
37
38
  from classiq.qmod.model_state_container import ModelStateContainer
38
39
  from classiq.qmod.python_classical_type import PythonClassicalType
39
- from classiq.qmod.qmod_variable import QVar
40
+ from classiq.qmod.qmod_variable import QVar, get_port_from_type_hint
40
41
  from classiq.qmod.quantum_callable import QCallableList
41
42
  from classiq.qmod.semantics.validation.type_hints import validate_annotation
42
43
  from classiq.qmod.utilities import unmangle_keyword, version_portable_get_args
@@ -85,15 +86,12 @@ def python_type_to_qmod(
85
86
 
86
87
 
87
88
  def _extract_port_decl(name: Optional[str], py_type: Any) -> AnonPortDeclaration:
88
- # FIXME: CAD-13409
89
- qtype: type[QVar] = QVar.from_type_hint(py_type) # type:ignore[assignment]
90
- direction = qtype.port_direction(py_type)
91
- if isinstance(py_type, _AnnotatedAlias):
92
- py_type = py_type.__args__[0]
89
+ quantum_type, direction, qualifier = get_port_from_type_hint(py_type)
93
90
  param = AnonPortDeclaration(
94
91
  name=None,
95
92
  direction=direction,
96
- quantum_type=qtype.to_qmod_quantum_type(py_type),
93
+ quantum_type=quantum_type,
94
+ type_qualifier=qualifier,
97
95
  )
98
96
  if name is not None:
99
97
  param = param.rename(name)
@@ -126,23 +124,45 @@ def _extract_operand_decl(
126
124
  def _extract_operand_param(py_type: Any) -> tuple[Optional[str], Any]:
127
125
  if get_origin(py_type) is not Annotated:
128
126
  return None, py_type
127
+
129
128
  args = get_args(py_type)
130
- if len(args) == 2:
131
- if isinstance(args[1], PortDeclarationDirection):
132
- return None, py_type
133
- elif isinstance(args[1], str):
134
- return args[1], args[0]
135
- elif get_origin(args[1]) is Literal:
136
- return version_portable_get_args(args[1])[0], args[0]
137
- elif len(args) == 3 and isinstance(args[1], PortDeclarationDirection):
138
- if isinstance(args[2], str):
139
- return args[2], Annotated[args[0], args[1]]
140
- elif get_origin(args[2]) is Literal:
141
- return version_portable_get_args(args[2])[0], Annotated[args[0], args[1]]
142
- raise ClassiqValueError(
143
- f"Operand parameter declaration must be of the form <param-type> or "
144
- f"Annotated[<param-type>, <param-name>]. Got {py_type}"
145
- )
129
+ _validate_annotations(args, py_type)
130
+ param_name = _get_param_name(args)
131
+
132
+ if param_name is None:
133
+ if len(args) > 1:
134
+ return None, _unpacked_annotated(args[0], args[1:])
135
+ return None, args[0]
136
+
137
+ if len(args) > 2:
138
+ return param_name, _unpacked_annotated(args[0], args[1:-1])
139
+ return param_name, args[0]
140
+
141
+
142
+ def _unpacked_annotated(arg_0: Any, args: Any) -> _AnnotatedAlias:
143
+ return Annotated.__class_getitem__((arg_0, *args)) # type:ignore[attr-defined]
144
+
145
+
146
+ def _get_param_name(py_type_args: Any) -> Optional[str]:
147
+ if isinstance(py_type_args[-1], str) and not isinstance(
148
+ py_type_args[-1], (PortDeclarationDirection, TypeQualifier)
149
+ ):
150
+ return py_type_args[-1]
151
+ elif py_type_args[-1] is Literal:
152
+ return str(version_portable_get_args(py_type_args[-1])[0]) # type: ignore[arg-type]
153
+ else:
154
+ return None
155
+
156
+
157
+ def _validate_annotations(py_type_args: Any, py_type: Any) -> None:
158
+ for arg in py_type_args[1:-1]:
159
+ if (
160
+ isinstance(arg, str) and not isinstance(arg, PortDeclarationDirection)
161
+ ) or arg is Literal:
162
+ raise ClassiqValueError(
163
+ f"Operand parameter declaration must be of the form <param-type> or "
164
+ f"Annotated[<param-type>, <param-name>]. Got {py_type}"
165
+ )
146
166
 
147
167
 
148
168
  @overload
@@ -177,7 +197,7 @@ def _extract_positional_args(
177
197
  if name is not None:
178
198
  param = param.rename(name)
179
199
  result.append(param)
180
- elif QVar.from_type_hint(py_type) is not None:
200
+ elif is_qvar(py_type):
181
201
  result.append(_extract_port_decl(name, py_type))
182
202
  else:
183
203
  result.append(_extract_operand_decl(name, py_type, qmodule=qmodule))
@@ -193,3 +213,11 @@ def infer_func_decl(
193
213
  list(py_func.__annotations__.items()), qmodule=qmodule
194
214
  ),
195
215
  )
216
+
217
+
218
+ def is_qvar(type_hint: Any) -> Any:
219
+ non_annotated_type = (
220
+ type_hint.__origin__ if isinstance(type_hint, _AnnotatedAlias) else type_hint
221
+ )
222
+ type_ = get_origin(non_annotated_type) or non_annotated_type
223
+ return issubclass(type_, QVar)
@@ -18,6 +18,8 @@ from classiq.interface.generator.functions.port_declaration import (
18
18
  PortDeclarationDirection,
19
19
  )
20
20
  from classiq.interface.generator.functions.type_name import TypeName
21
+ from classiq.interface.generator.functions.type_qualifier import TypeQualifier
22
+ from classiq.interface.generator.types.compilation_metadata import CompilationMetadata
21
23
  from classiq.interface.generator.types.enum_declaration import EnumDeclaration
22
24
  from classiq.interface.generator.types.qstruct_declaration import QStructDeclaration
23
25
  from classiq.interface.generator.types.struct_declaration import StructDeclaration
@@ -95,6 +97,7 @@ class DSLPrettyPrinter(ModelVisitor):
95
97
  self._level = 0
96
98
  self._decimal_precision = decimal_precision
97
99
  self._emit_open_lib_functions = emit_open_lib_functions
100
+ self._compilation_metadata: dict[str, CompilationMetadata] = {}
98
101
 
99
102
  def visit(self, node: NodeType) -> str:
100
103
  res = super().visit(node)
@@ -105,6 +108,8 @@ class DSLPrettyPrinter(ModelVisitor):
105
108
  def visit_Model(self, model: Model) -> str:
106
109
  # FIXME - CAD-20149: Remove this line once the froggies are removed, and the visit of lambdas can be done without accessing the func_decl property (with rename_params values only).
107
110
  resolve_function_calls(model, model.function_dict)
111
+ self._compilation_metadata = model.functions_compilation_metadata
112
+
108
113
  enum_decls = [self.visit(enum_decl) for enum_decl in model.enums]
109
114
  struct_decls = [self.visit(struct_decl) for struct_decl in model.types]
110
115
  qstruct_decls = [self.visit(qstruct_decl) for qstruct_decl in model.qstructs]
@@ -135,10 +140,20 @@ class DSLPrettyPrinter(ModelVisitor):
135
140
  )
136
141
  return f"({positional_args})"
137
142
 
143
+ def _get_decl_string(self, func_decl: QuantumFunctionDeclaration) -> str:
144
+ no_qualifiers_decl = "qfunc"
145
+ if func_decl.name not in self._compilation_metadata:
146
+ return no_qualifiers_decl
147
+ atomic_qualifiers = self._compilation_metadata[func_decl.name].atomic_qualifiers
148
+ if len(atomic_qualifiers) == 0:
149
+ return no_qualifiers_decl
150
+ return f"atomic_qualifiers ({', '.join(atomic_qualifiers)})\n" f"qfunc"
151
+
138
152
  def visit_QuantumFunctionDeclaration(
139
153
  self, func_decl: QuantumFunctionDeclaration
140
154
  ) -> str:
141
- return f"qfunc {func_decl.name}{self._visit_arg_decls(func_decl)}"
155
+ qfunc_decl = self._get_decl_string(func_decl)
156
+ return f"{qfunc_decl} {func_decl.name}{self._visit_arg_decls(func_decl)}"
142
157
 
143
158
  def visit_EnumDeclaration(self, enum_decl: EnumDeclaration) -> str:
144
159
  return f"enum {enum_decl.name} {{\n{self._visit_members(enum_decl.members)}}}\n"
@@ -175,13 +190,20 @@ class DSLPrettyPrinter(ModelVisitor):
175
190
  return f"{var_decl.name}: {self.visit(var_decl.quantum_type)}"
176
191
 
177
192
  def visit_AnonPortDeclaration(self, port_decl: AnonPortDeclaration) -> str:
193
+ qualifier_str = (
194
+ f"{port_decl.type_qualifier} "
195
+ if port_decl.type_qualifier is not TypeQualifier.Quantum
196
+ else ""
197
+ )
178
198
  dir_str = (
179
199
  f"{port_decl.direction} "
180
- if port_decl.direction != PortDeclarationDirection.Inout
200
+ if port_decl.direction is not PortDeclarationDirection.Inout
181
201
  else ""
182
202
  )
183
203
  param_name = f"{port_decl.name}: " if port_decl.name is not None else ""
184
- return f"{dir_str}{param_name}{self.visit(port_decl.quantum_type)}"
204
+ return (
205
+ f"{qualifier_str}{dir_str}{param_name}{self.visit(port_decl.quantum_type)}"
206
+ )
185
207
 
186
208
  def visit_QuantumBit(self, qtype: QuantumBit) -> str:
187
209
  return "qbit"
@@ -21,6 +21,8 @@ from classiq.interface.generator.functions.port_declaration import (
21
21
  PortDeclarationDirection,
22
22
  )
23
23
  from classiq.interface.generator.functions.type_name import TypeName
24
+ from classiq.interface.generator.functions.type_qualifier import TypeQualifier
25
+ from classiq.interface.generator.types.compilation_metadata import CompilationMetadata
24
26
  from classiq.interface.generator.types.enum_declaration import EnumDeclaration
25
27
  from classiq.interface.generator.types.qstruct_declaration import QStructDeclaration
26
28
  from classiq.interface.generator.types.struct_declaration import StructDeclaration
@@ -131,6 +133,7 @@ class PythonPrettyPrinter(ModelVisitor):
131
133
  self._import_annotated = False
132
134
  self._symbolic_imports: dict[str, int] = dict()
133
135
  self._functions: Optional[Mapping[str, QuantumFunctionDeclaration]] = None
136
+ self._compilation_metadata: dict[str, CompilationMetadata] = dict()
134
137
 
135
138
  def visit(self, node: NodeType) -> str:
136
139
  res = super().visit(node)
@@ -140,6 +143,7 @@ class PythonPrettyPrinter(ModelVisitor):
140
143
 
141
144
  def visit_Model(self, model: Model) -> str:
142
145
  self._functions = {**model.function_dict, **BUILTIN_FUNCTION_DECLARATIONS}
146
+ self._compilation_metadata = model.functions_compilation_metadata
143
147
  enum_decls = [self.visit(decl) for decl in model.enums]
144
148
  struct_decls = [self.visit(decl) for decl in model.types]
145
149
  qstruct_decls = [self.visit(qstruct_decl) for qstruct_decl in model.qstructs]
@@ -196,12 +200,23 @@ class PythonPrettyPrinter(ModelVisitor):
196
200
  self.visit(arg_decl) for arg_decl in func_def.positional_arg_declarations
197
201
  )
198
202
 
203
+ def _get_qfunc_decorator(self, func_decl: QuantumFunctionDeclaration) -> str:
204
+ no_qualifiers_decorator = "@qfunc"
205
+ if func_decl.name not in self._compilation_metadata:
206
+ return no_qualifiers_decorator
207
+ atomic_qualifiers = self._compilation_metadata[func_decl.name].atomic_qualifiers
208
+ if len(atomic_qualifiers) == 0:
209
+ return no_qualifiers_decorator
210
+
211
+ qualifiers = (f'"{qualifier}"' for qualifier in atomic_qualifiers)
212
+ qualifier_list = f"[{', '.join(qualifiers)}]"
213
+ return no_qualifiers_decorator + f" (atomic_qualifiers={qualifier_list})"
214
+
199
215
  def visit_QuantumFunctionDeclaration(
200
216
  self, func_decl: QuantumFunctionDeclaration
201
217
  ) -> str:
202
- return (
203
- f"@qfunc\ndef {func_decl.name}({self._visit_arg_decls(func_decl)}) -> None:"
204
- )
218
+ qfunc_decorator = self._get_qfunc_decorator(func_decl)
219
+ return f"{qfunc_decorator}\ndef {func_decl.name}({self._visit_arg_decls(func_decl)}) -> None:"
205
220
 
206
221
  def visit_EnumDeclaration(self, enum_decl: EnumDeclaration) -> str:
207
222
  self._import_enum = True
@@ -241,16 +256,18 @@ class PythonPrettyPrinter(ModelVisitor):
241
256
  return f"{var_decl.name}: {self.visit(var_decl.quantum_type)}"
242
257
 
243
258
  def visit_AnonPortDeclaration(self, port_decl: AnonPortDeclaration) -> str:
244
- var = f"{port_decl.name}: {self.visit(port_decl.quantum_type)}"
245
- var_name, var_type = var.split(": ")
246
- for direction in PortDeclarationDirection:
247
- if port_decl.direction == PortDeclarationDirection.Inout:
248
- return var
249
- if port_decl.direction == direction:
250
- direction_identifier = direction.name
251
- self._imports[direction_identifier] = 1
252
- return f"{var_name}: {direction_identifier}[{var_type}]"
253
- raise RuntimeError("Should not reach here")
259
+ var_type = self._extract_port_type(port_decl)
260
+ return f"{port_decl.name}: {var_type}"
261
+
262
+ def _extract_port_type(self, port_decl: AnonPortDeclaration) -> str:
263
+ var_type = self.visit(port_decl.quantum_type)
264
+ if port_decl.direction is not PortDeclarationDirection.Inout:
265
+ self._imports[port_decl.direction.name] = 1
266
+ var_type = f"{port_decl.direction.name}[{var_type}]"
267
+ if port_decl.type_qualifier is not TypeQualifier.Quantum:
268
+ self._imports[port_decl.type_qualifier.name] = 1
269
+ var_type = f"{port_decl.type_qualifier.name}[{var_type}]"
270
+ return var_type
254
271
 
255
272
  def visit_QuantumBit(self, qtype: QuantumBit) -> str:
256
273
  self._imports["QBit"] = 1
@@ -349,7 +366,7 @@ class PythonPrettyPrinter(ModelVisitor):
349
366
 
350
367
  def _visit_operand_arg_decl(self, arg_decl: AnonPositionalArg) -> str:
351
368
  if isinstance(arg_decl, AnonPortDeclaration):
352
- type_str = self.visit(arg_decl.quantum_type)
369
+ type_str = self._extract_port_type(arg_decl)
353
370
  elif isinstance(arg_decl, AnonClassicalParameterDeclaration):
354
371
  type_str = self.visit(arg_decl.classical_type)
355
372
  else:
@@ -2,6 +2,9 @@ import dataclasses
2
2
  import inspect
3
3
  from enum import EnumMeta
4
4
  from typing import (
5
+ Any,
6
+ ForwardRef,
7
+ Literal,
5
8
  Optional,
6
9
  get_args,
7
10
  get_origin,
@@ -19,7 +22,6 @@ from classiq.interface.generator.functions.concrete_types import ConcreteClassic
19
22
  from classiq.interface.generator.functions.type_name import Enum, Struct
20
23
 
21
24
  from classiq.qmod.cparam import CArray, CBool, CInt, CReal
22
- from classiq.qmod.qmod_variable import get_type_hint_expr
23
25
  from classiq.qmod.utilities import version_portable_get_args
24
26
 
25
27
  CARRAY_ERROR_MESSAGE = (
@@ -71,3 +73,12 @@ class PythonClassicalType:
71
73
 
72
74
  def register_enum(self, py_type: EnumMeta) -> None:
73
75
  pass
76
+
77
+
78
+ def get_type_hint_expr(type_hint: Any) -> str:
79
+ if isinstance(type_hint, ForwardRef): # expression in string literal
80
+ return str(type_hint.__forward_arg__)
81
+ if get_origin(type_hint) == Literal: # explicit numeric literal
82
+ return str(get_args(type_hint)[0])
83
+ else:
84
+ return str(type_hint) # implicit numeric literal