classiq 0.69.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 (97) hide show
  1. classiq/analyzer/analyzer.py +0 -18
  2. classiq/analyzer/url_utils.py +9 -4
  3. classiq/applications/combinatorial_helpers/pyomo_utils.py +2 -0
  4. classiq/interface/_version.py +1 -1
  5. classiq/interface/backend/quantum_backend_providers.py +6 -0
  6. classiq/interface/chemistry/operator.py +1 -21
  7. classiq/interface/debug_info/debug_info.py +4 -0
  8. classiq/interface/executor/quantum_instruction_set.py +1 -0
  9. classiq/interface/generator/arith/arithmetic.py +21 -6
  10. classiq/interface/generator/circuit_code/circuit_code.py +4 -0
  11. classiq/interface/generator/circuit_code/types_and_constants.py +1 -0
  12. classiq/interface/generator/expressions/atomic_expression_functions.py +1 -2
  13. classiq/interface/generator/expressions/expression_constants.py +0 -3
  14. classiq/interface/generator/expressions/expression_types.py +12 -4
  15. classiq/interface/generator/expressions/proxies/__init__.py +0 -0
  16. classiq/interface/generator/expressions/proxies/classical/__init__.py +0 -0
  17. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +79 -0
  18. classiq/interface/generator/expressions/proxies/classical/classical_proxy.py +26 -0
  19. classiq/interface/generator/expressions/proxies/classical/classical_scalar_proxy.py +32 -0
  20. classiq/interface/generator/expressions/proxies/classical/classical_struct_proxy.py +35 -0
  21. classiq/interface/generator/expressions/proxies/classical/utils.py +34 -0
  22. classiq/interface/generator/expressions/proxies/quantum/__init__.py +0 -0
  23. classiq/interface/generator/expressions/{qmod_qarray_proxy.py → proxies/quantum/qmod_qarray_proxy.py} +3 -1
  24. classiq/interface/generator/expressions/{qmod_qscalar_proxy.py → proxies/quantum/qmod_qscalar_proxy.py} +3 -1
  25. classiq/interface/generator/expressions/{qmod_qstruct_proxy.py → proxies/quantum/qmod_qstruct_proxy.py} +3 -1
  26. classiq/interface/generator/functions/classical_type.py +24 -30
  27. classiq/interface/generator/functions/type_name.py +42 -2
  28. classiq/interface/generator/functions/type_qualifier.py +7 -0
  29. classiq/interface/generator/generated_circuit_data.py +22 -4
  30. classiq/interface/generator/model/preferences/preferences.py +1 -0
  31. classiq/interface/generator/quantum_function_call.py +8 -1
  32. classiq/interface/generator/quantum_program.py +0 -1
  33. classiq/interface/generator/synthesis_execution_parameter.py +1 -0
  34. classiq/interface/generator/types/compilation_metadata.py +1 -0
  35. classiq/interface/ide/visual_model.py +1 -0
  36. classiq/interface/interface_version.py +1 -1
  37. classiq/interface/model/allocate.py +7 -0
  38. classiq/interface/model/block.py +12 -0
  39. classiq/interface/model/classical_if.py +4 -0
  40. classiq/interface/model/inplace_binary_operation.py +4 -0
  41. classiq/interface/model/model.py +3 -1
  42. classiq/interface/model/native_function_definition.py +0 -10
  43. classiq/interface/model/phase_operation.py +4 -0
  44. classiq/interface/model/port_declaration.py +3 -0
  45. classiq/interface/model/power.py +4 -0
  46. classiq/interface/model/quantum_expressions/quantum_expression.py +4 -0
  47. classiq/interface/model/quantum_function_call.py +4 -0
  48. classiq/interface/model/quantum_function_declaration.py +1 -1
  49. classiq/interface/model/quantum_statement.py +5 -0
  50. classiq/interface/model/quantum_type.py +37 -3
  51. classiq/interface/model/repeat.py +4 -0
  52. classiq/interface/model/statement_block.py +3 -0
  53. classiq/interface/model/variable_declaration_statement.py +5 -0
  54. classiq/model_expansions/atomic_expression_functions_defs.py +9 -3
  55. classiq/model_expansions/capturing/captured_vars.py +156 -34
  56. classiq/model_expansions/evaluators/arg_type_match.py +4 -2
  57. classiq/model_expansions/evaluators/classical_expression.py +2 -2
  58. classiq/model_expansions/evaluators/classical_type_inference.py +70 -0
  59. classiq/model_expansions/evaluators/control.py +1 -1
  60. classiq/model_expansions/evaluators/parameter_types.py +72 -16
  61. classiq/model_expansions/evaluators/quantum_type_utils.py +7 -57
  62. classiq/model_expansions/expression_evaluator.py +3 -12
  63. classiq/model_expansions/function_builder.py +2 -8
  64. classiq/model_expansions/generative_functions.py +39 -3
  65. classiq/model_expansions/interpreters/base_interpreter.py +3 -4
  66. classiq/model_expansions/quantum_operations/arithmetic/__init__.py +0 -0
  67. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +60 -0
  68. classiq/model_expansions/quantum_operations/assignment_result_processor.py +9 -0
  69. classiq/model_expansions/quantum_operations/call_emitter.py +46 -6
  70. classiq/model_expansions/quantum_operations/emitter.py +41 -0
  71. classiq/model_expansions/quantum_operations/expression_evaluator.py +4 -0
  72. classiq/model_expansions/quantum_operations/quantum_function_call.py +0 -22
  73. classiq/model_expansions/scope.py +7 -14
  74. classiq/model_expansions/scope_initialization.py +32 -39
  75. classiq/model_expansions/transformers/model_renamer.py +13 -4
  76. classiq/model_expansions/visitors/variable_references.py +8 -4
  77. classiq/open_library/functions/__init__.py +2 -0
  78. classiq/open_library/functions/lookup_table.py +58 -0
  79. classiq/qmod/__init__.py +3 -1
  80. classiq/qmod/declaration_inferrer.py +55 -25
  81. classiq/qmod/native/pretty_printer.py +25 -3
  82. classiq/qmod/pretty_print/pretty_printer.py +31 -14
  83. classiq/qmod/python_classical_type.py +12 -1
  84. classiq/qmod/qfunc.py +33 -8
  85. classiq/qmod/qmod_parameter.py +8 -0
  86. classiq/qmod/qmod_variable.py +189 -151
  87. classiq/qmod/quantum_function.py +3 -4
  88. classiq/qmod/semantics/annotation/call_annotation.py +0 -28
  89. classiq/qmod/semantics/annotation/qstruct_annotator.py +21 -1
  90. classiq/qmod/semantics/validation/main_validation.py +1 -1
  91. classiq/qmod/semantics/validation/type_hints.py +38 -0
  92. classiq/qmod/utilities.py +38 -1
  93. {classiq-0.69.0.dist-info → classiq-0.71.0.dist-info}/METADATA +10 -12
  94. {classiq-0.69.0.dist-info → classiq-0.71.0.dist-info}/RECORD +97 -82
  95. {classiq-0.69.0.dist-info → classiq-0.71.0.dist-info}/WHEEL +1 -1
  96. /classiq/interface/generator/expressions/{qmod_struct_instance.py → proxies/classical/qmod_struct_instance.py} +0 -0
  97. /classiq/interface/generator/expressions/{qmod_sized_proxy.py → proxies/quantum/qmod_sized_proxy.py} +0 -0
@@ -1,4 +1,5 @@
1
1
  import ast
2
+ import re
2
3
  from collections.abc import Mapping, Sequence
3
4
  from dataclasses import dataclass
4
5
  from typing import TypeVar, cast
@@ -16,6 +17,12 @@ from classiq.model_expansions.visitors.variable_references import VarRefCollecto
16
17
  AST_NODE = TypeVar("AST_NODE", bound=NodeType)
17
18
 
18
19
 
20
+ def _replace_full_word(pattern: str, substitution: str, target: str) -> str:
21
+ return re.sub(
22
+ rf"(^|\b|\W)({re.escape(pattern)})($|\b|\W)", rf"\1{substitution}\3", target
23
+ )
24
+
25
+
19
26
  @dataclass(frozen=True)
20
27
  class HandleRenaming:
21
28
  source_handle: HandleBinding
@@ -74,7 +81,7 @@ class ModelRenamer:
74
81
  symbol_mapping: SymbolRenaming,
75
82
  expression: Expression,
76
83
  ) -> Expression:
77
- vrc = VarRefCollector(ignore_duplicated_handles=True)
84
+ vrc = VarRefCollector(ignore_duplicated_handles=True, ignore_sympy_symbols=True)
78
85
  vrc.visit(ast.parse(expression.expr))
79
86
 
80
87
  handle_names = {
@@ -87,10 +94,12 @@ class ModelRenamer:
87
94
  new_handle = handle.collapse()
88
95
  for handle_to_replace, replacement in handle_names.items():
89
96
  new_handle = new_handle.replace_prefix(handle_to_replace, replacement)
90
- new_expr_str = new_expr_str.replace(str(handle), str(new_handle))
97
+ new_expr_str = _replace_full_word(
98
+ str(handle), str(new_handle), new_expr_str
99
+ )
91
100
  if handle.qmod_expr != str(handle):
92
- new_expr_str = new_expr_str.replace(
93
- handle.qmod_expr, new_handle.qmod_expr
101
+ new_expr_str = _replace_full_word(
102
+ str(handle.qmod_expr), str(new_handle.qmod_expr), new_expr_str
94
103
  )
95
104
 
96
105
  new_expr = Expression(expr=new_expr_str)
@@ -24,10 +24,14 @@ from classiq.interface.model.handle_binding import (
24
24
 
25
25
  class VarRefCollector(ast.NodeVisitor):
26
26
  def __init__(
27
- self, ignore_duplicated_handles: bool = False, unevaluated: bool = False
27
+ self,
28
+ ignore_duplicated_handles: bool = False,
29
+ unevaluated: bool = False,
30
+ ignore_sympy_symbols: bool = False,
28
31
  ) -> None:
29
32
  self._var_handles: dict[HandleBinding, bool] = {}
30
33
  self._ignore_duplicated_handles = ignore_duplicated_handles
34
+ self._ignore_sympy_symbols = ignore_sympy_symbols
31
35
  self._unevaluated = unevaluated
32
36
  self._is_nested = False
33
37
 
@@ -117,9 +121,9 @@ class VarRefCollector(ast.NodeVisitor):
117
121
  return handle
118
122
 
119
123
  def visit_Name(self, node: ast.Name) -> Optional[HandleBinding]:
120
- if node.id in set(SYMPY_SUPPORTED_EXPRESSIONS) | set(
121
- DEFAULT_SUPPORTED_FUNC_NAMES
122
- ):
124
+ if not self._ignore_sympy_symbols and node.id in set(
125
+ SYMPY_SUPPORTED_EXPRESSIONS
126
+ ) | set(DEFAULT_SUPPORTED_FUNC_NAMES):
123
127
  return None
124
128
  handle = HandleBinding(name=node.id)
125
129
  if not self._is_nested:
@@ -9,6 +9,7 @@ from .grover import *
9
9
  from .hea import *
10
10
  from .linear_pauli_rotation import *
11
11
  from .linear_pauli_rotation import _single_pauli
12
+ from .lookup_table import span_lookup_table
12
13
  from .modular_exponentiation import *
13
14
  from .modular_exponentiation import _check_msb
14
15
  from .qaoa_penalty import *
@@ -131,6 +132,7 @@ __all__ = [
131
132
  "qsvt_lcu_step",
132
133
  "qsvt_step",
133
134
  "reflect_about_zero",
135
+ "span_lookup_table",
134
136
  "suzuki_trotter",
135
137
  "swap_test",
136
138
  "switch",
@@ -0,0 +1,58 @@
1
+ from itertools import product
2
+
3
+ from classiq.interface.exceptions import ClassiqValueError
4
+
5
+ from classiq.qmod.builtins.operations import assign, bind, within_apply
6
+ from classiq.qmod.qmod_variable import QNum
7
+ from classiq.qmod.symbolic import subscript
8
+ from classiq.qmod.utilities import RealFunction, get_temp_var_name, qnum_values
9
+
10
+
11
+ def _get_qnum_values(num: QNum) -> list[float]:
12
+ size = num.size
13
+ is_signed = num.is_signed
14
+ fraction_digits = num.fraction_digits
15
+ if (
16
+ not isinstance(size, int)
17
+ or not isinstance(is_signed, bool)
18
+ or not isinstance(fraction_digits, int)
19
+ ):
20
+ raise ClassiqValueError(
21
+ "Must call 'span_lookup_table' inside a generative qfunc"
22
+ )
23
+
24
+ return qnum_values(size, is_signed, fraction_digits)
25
+
26
+
27
+ def span_lookup_table(func: RealFunction, *targets: QNum) -> QNum:
28
+ """
29
+ Applies a classical function to quantum numbers.
30
+
31
+ Works by reducing the function into a lookup table over all the possible values
32
+ of the quantum numbers.
33
+
34
+ Args:
35
+ func: A Python function
36
+ *targets: One or more initialized quantum numbers
37
+
38
+ Returns:
39
+ The quantum result of applying func to targets
40
+
41
+ Notes:
42
+ Must be called inside a generative function (`@qfunc(generative=True)`)
43
+ """
44
+ if len(targets) == 0:
45
+ raise ClassiqValueError("No targets specified")
46
+
47
+ target_vals = [_get_qnum_values(target) for target in targets]
48
+ lookup_table = [func(*vals[::-1]) for vals in product(*target_vals[::-1])]
49
+
50
+ index_size = sum(target.size for target in targets)
51
+ index: QNum = QNum(get_temp_var_name(), size=index_size)
52
+ result: QNum = QNum(get_temp_var_name("result"))
53
+
54
+ within_apply(
55
+ lambda: bind(list(targets), index),
56
+ lambda: assign(subscript(lookup_table, index), result),
57
+ )
58
+ return result
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,8 +37,9 @@ 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
42
+ from classiq.qmod.semantics.validation.type_hints import validate_annotation
41
43
  from classiq.qmod.utilities import unmangle_keyword, version_portable_get_args
42
44
 
43
45
  if sys.version_info[0:2] >= (3, 9):
@@ -84,15 +86,12 @@ def python_type_to_qmod(
84
86
 
85
87
 
86
88
  def _extract_port_decl(name: Optional[str], py_type: Any) -> AnonPortDeclaration:
87
- # FIXME: CAD-13409
88
- qtype: type[QVar] = QVar.from_type_hint(py_type) # type:ignore[assignment]
89
- direction = qtype.port_direction(py_type)
90
- if isinstance(py_type, _AnnotatedAlias):
91
- py_type = py_type.__args__[0]
89
+ quantum_type, direction, qualifier = get_port_from_type_hint(py_type)
92
90
  param = AnonPortDeclaration(
93
91
  name=None,
94
92
  direction=direction,
95
- quantum_type=qtype.to_qmod_quantum_type(py_type),
93
+ quantum_type=quantum_type,
94
+ type_qualifier=qualifier,
96
95
  )
97
96
  if name is not None:
98
97
  param = param.rename(name)
@@ -123,25 +122,47 @@ def _extract_operand_decl(
123
122
 
124
123
 
125
124
  def _extract_operand_param(py_type: Any) -> tuple[Optional[str], Any]:
126
- if sys.version_info[0:2] < (3, 9) or get_origin(py_type) is not Annotated:
125
+ if get_origin(py_type) is not Annotated:
127
126
  return None, py_type
127
+
128
128
  args = get_args(py_type)
129
- if len(args) == 2:
130
- if isinstance(args[1], PortDeclarationDirection):
131
- return None, py_type
132
- elif isinstance(args[1], str):
133
- return args[1], args[0]
134
- elif get_origin(args[1]) is Literal:
135
- return version_portable_get_args(args[1])[0], args[0]
136
- elif len(args) == 3 and isinstance(args[1], PortDeclarationDirection):
137
- if isinstance(args[2], str):
138
- return args[2], Annotated[args[0], args[1]]
139
- elif get_origin(args[2]) is Literal:
140
- return version_portable_get_args(args[2])[0], Annotated[args[0], args[1]]
141
- raise ClassiqValueError(
142
- f"Operand parameter declaration must be of the form <param-type> or "
143
- f"Annotated[<param-type>, <param-name>]. Got {py_type}"
144
- )
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
+ )
145
166
 
146
167
 
147
168
  @overload
@@ -163,6 +184,7 @@ def _extract_positional_args(
163
184
  ) -> Sequence[AnonPositionalArg]:
164
185
  result: list[AnonPositionalArg] = []
165
186
  for name, py_type in args:
187
+ validate_annotation(py_type)
166
188
  if name == "return":
167
189
  continue
168
190
  name = unmangle_keyword(name)
@@ -175,7 +197,7 @@ def _extract_positional_args(
175
197
  if name is not None:
176
198
  param = param.rename(name)
177
199
  result.append(param)
178
- elif QVar.from_type_hint(py_type) is not None:
200
+ elif is_qvar(py_type):
179
201
  result.append(_extract_port_decl(name, py_type))
180
202
  else:
181
203
  result.append(_extract_operand_decl(name, py_type, qmodule=qmodule))
@@ -191,3 +213,11 @@ def infer_func_decl(
191
213
  list(py_func.__annotations__.items()), qmodule=qmodule
192
214
  ),
193
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
classiq/qmod/qfunc.py CHANGED
@@ -35,6 +35,7 @@ def qfunc(
35
35
  *,
36
36
  external: Literal[True],
37
37
  synthesize_separately: Literal[False] = False,
38
+ atomic_qualifiers: Optional[list[str]] = None,
38
39
  ) -> Callable[[Callable], ExternalQFunc]: ...
39
40
 
40
41
 
@@ -43,11 +44,22 @@ def qfunc(
43
44
  *,
44
45
  generative: Literal[True],
45
46
  synthesize_separately: bool = False,
47
+ atomic_qualifiers: Optional[list[str]] = None,
46
48
  ) -> Callable[[Callable], GenerativeQFunc]: ...
47
49
 
48
50
 
49
51
  @overload
50
- def qfunc(*, synthesize_separately: bool) -> Callable[[Callable], QFunc]: ...
52
+ def qfunc(
53
+ *, synthesize_separately: bool, atomic_qualifiers: Optional[list[str]] = None
54
+ ) -> Callable[[Callable], QFunc]: ...
55
+
56
+
57
+ @overload
58
+ def qfunc(
59
+ *,
60
+ synthesize_separately: bool = False,
61
+ atomic_qualifiers: Optional[list[str]] = None,
62
+ ) -> Callable[[Callable], QFunc]: ...
51
63
 
52
64
 
53
65
  def qfunc(
@@ -56,24 +68,37 @@ def qfunc(
56
68
  external: bool = False,
57
69
  generative: bool = False,
58
70
  synthesize_separately: bool = False,
71
+ atomic_qualifiers: Optional[list[str]] = None,
59
72
  ) -> Union[Callable[[Callable], QCallable], QCallable]:
60
73
  def wrapper(func: Callable) -> QCallable:
61
74
  qfunc: BaseQFunc
75
+
76
+ if external:
77
+ _validate_directives(synthesize_separately, atomic_qualifiers)
78
+ return ExternalQFunc(func)
79
+
62
80
  if generative or _GENERATIVE_SWITCH:
63
81
  qfunc = GenerativeQFunc(func)
64
- elif external:
65
- if synthesize_separately:
66
- raise ClassiqInternalError(
67
- "External functions can't be marked as synthesized separately"
68
- )
69
- return ExternalQFunc(func)
70
82
  else:
71
83
  qfunc = QFunc(func)
72
84
  if synthesize_separately:
73
85
  qfunc.update_compilation_metadata(should_synthesize_separately=True)
86
+ if atomic_qualifiers is not None and len(atomic_qualifiers) > 0:
87
+ qfunc.update_compilation_metadata(atomic_qualifiers=atomic_qualifiers)
74
88
  return qfunc
75
89
 
76
90
  if func is not None:
77
91
  return wrapper(func)
78
-
79
92
  return wrapper
93
+
94
+
95
+ def _validate_directives(
96
+ synthesize_separately: bool, atomic_qualifiers: Optional[list[str]] = None
97
+ ) -> None:
98
+ error_msg = ""
99
+ if synthesize_separately:
100
+ error_msg += "External functions can't be marked as synthesized separately. \n"
101
+ if atomic_qualifiers is not None and len(atomic_qualifiers) > 0:
102
+ error_msg += "External functions can't have atomic qualifiers."
103
+ if error_msg:
104
+ raise ClassiqInternalError(error_msg)
@@ -22,6 +22,11 @@ from classiq.qmod.cparam import ( # noqa: F401
22
22
  CParamScalar,
23
23
  CReal,
24
24
  )
25
+ from classiq.qmod.generative import (
26
+ generative_mode_context,
27
+ interpret_expression,
28
+ is_generative_mode,
29
+ )
25
30
  from classiq.qmod.model_state_container import ModelStateContainer
26
31
  from classiq.qmod.symbolic_expr import Symbolic, SymbolicExpr
27
32
 
@@ -66,6 +71,9 @@ class CParamList(CParam):
66
71
 
67
72
  @property
68
73
  def len(self) -> CParamScalar:
74
+ if is_generative_mode():
75
+ with generative_mode_context(False):
76
+ return interpret_expression(str(self.len))
69
77
  return CParamScalar(f"get_field({self}, 'len')")
70
78
 
71
79