classiq 0.78.0__py3-none-any.whl → 0.79.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 (48) hide show
  1. classiq/interface/_version.py +1 -1
  2. classiq/interface/generator/arith/arithmetic.py +10 -6
  3. classiq/interface/generator/arith/binary_ops.py +8 -11
  4. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +90 -23
  5. classiq/interface/generator/expressions/proxies/classical/utils.py +4 -0
  6. classiq/interface/generator/functions/classical_type.py +74 -0
  7. classiq/interface/generator/functions/concrete_types.py +3 -0
  8. classiq/interface/generator/functions/type_name.py +32 -3
  9. classiq/interface/generator/generated_circuit_data.py +3 -1
  10. classiq/interface/helpers/model_normalizer.py +47 -0
  11. classiq/interface/interface_version.py +1 -1
  12. classiq/interface/model/bounds.py +12 -0
  13. classiq/interface/model/model.py +9 -5
  14. classiq/interface/model/quantum_type.py +25 -3
  15. classiq/interface/model/statement_block.py +2 -0
  16. classiq/model_expansions/atomic_expression_functions_defs.py +20 -6
  17. classiq/model_expansions/evaluators/argument_types.py +20 -0
  18. classiq/model_expansions/evaluators/classical_type_inference.py +42 -13
  19. classiq/model_expansions/evaluators/quantum_type_utils.py +31 -3
  20. classiq/model_expansions/generative_functions.py +1 -1
  21. classiq/model_expansions/interpreters/generative_interpreter.py +9 -0
  22. classiq/model_expansions/quantum_operations/__init__.py +3 -0
  23. classiq/model_expansions/quantum_operations/allocate.py +3 -1
  24. classiq/model_expansions/quantum_operations/assignment_result_processor.py +7 -3
  25. classiq/model_expansions/quantum_operations/bind.py +8 -1
  26. classiq/model_expansions/quantum_operations/bounds.py +30 -0
  27. classiq/model_expansions/quantum_operations/call_emitter.py +47 -7
  28. classiq/model_expansions/quantum_operations/function_calls_cache.py +8 -1
  29. classiq/model_expansions/quantum_operations/quantum_function_call.py +5 -6
  30. classiq/model_expansions/scope.py +14 -4
  31. classiq/model_expansions/scope_initialization.py +1 -14
  32. classiq/model_expansions/visitors/symbolic_param_inference.py +32 -16
  33. classiq/model_expansions/visitors/variable_references.py +39 -43
  34. classiq/open_library/functions/linear_pauli_rotation.py +6 -6
  35. classiq/open_library/functions/state_preparation.py +1 -1
  36. classiq/open_library/functions/utility_functions.py +2 -2
  37. classiq/qmod/declaration_inferrer.py +5 -3
  38. classiq/qmod/model_state_container.py +21 -1
  39. classiq/qmod/native/pretty_printer.py +8 -0
  40. classiq/qmod/pretty_print/expression_to_python.py +8 -1
  41. classiq/qmod/pretty_print/pretty_printer.py +7 -0
  42. classiq/qmod/python_classical_type.py +1 -1
  43. classiq/qmod/qmod_parameter.py +43 -7
  44. classiq/qmod/semantics/annotation/qstruct_annotator.py +15 -4
  45. classiq/qmod/utilities.py +1 -1
  46. {classiq-0.78.0.dist-info → classiq-0.79.0.dist-info}/METADATA +1 -1
  47. {classiq-0.78.0.dist-info → classiq-0.79.0.dist-info}/RECORD +48 -45
  48. {classiq-0.78.0.dist-info → classiq-0.79.0.dist-info}/WHEEL +0 -0
@@ -31,8 +31,13 @@ from classiq.interface.model.handle_binding import (
31
31
  SubscriptHandleBinding,
32
32
  )
33
33
  from classiq.interface.model.quantum_function_call import ArgValue
34
+ from classiq.interface.model.quantum_function_declaration import (
35
+ AnonPositionalArg,
36
+ AnonQuantumOperandDeclaration,
37
+ )
34
38
  from classiq.interface.model.quantum_type import (
35
39
  QuantumBitvector,
40
+ QuantumNumeric,
36
41
  QuantumType,
37
42
  )
38
43
 
@@ -184,6 +189,9 @@ class QuantumSymbolList(QuantumVariable):
184
189
  length = Expression(
185
190
  expr=str(sum(symbol.quantum_type.size_in_bits for symbol in symbols))
186
191
  )
192
+ for symbol in symbols:
193
+ if isinstance(symbol.quantum_type, QuantumNumeric):
194
+ symbol.quantum_type.reset_bounds()
187
195
  return QuantumSymbolList(
188
196
  handles=handles, quantum_type=QuantumBitvector(length=length)
189
197
  )
@@ -212,7 +220,7 @@ def _evaluated_to_str_struct_literal(value: QmodStructInstance) -> str:
212
220
 
213
221
  def _raise_type_error(val: Any, t: type, location_hint: Optional[str]) -> NoReturn:
214
222
  if isinstance(val, sympy.Basic) and len(val.free_symbols) > 0:
215
- symbolic_vars = list(val.free_symbols)
223
+ symbolic_vars = sorted(map(str, val.free_symbols))
216
224
  suffix = f" {location_hint}" if location_hint is not None else ""
217
225
  raise ClassiqExpansionError(
218
226
  f"Cannot use execution parameter{s(symbolic_vars)} {readable_list(symbolic_vars, quote=True)} in a compile-time context{suffix}"
@@ -240,13 +248,15 @@ class Evaluated: # FIXME: Merge with EvaluatedExpression if possible
240
248
 
241
249
  return int(self.value)
242
250
 
243
- def emit(self) -> ArgValue:
251
+ def emit(self, param: Optional[AnonPositionalArg] = None) -> ArgValue:
244
252
  from classiq.model_expansions.closure import FunctionClosure
245
253
 
246
254
  if isinstance(self.value, (QuantumVariable, FunctionClosure)):
247
255
  return self.value.emit()
248
- if isinstance(self.value, list) and all(
249
- isinstance(item, FunctionClosure) for item in self.value
256
+ if (
257
+ isinstance(param, AnonQuantumOperandDeclaration)
258
+ and isinstance(self.value, list)
259
+ and all(isinstance(item, FunctionClosure) for item in self.value)
250
260
  ):
251
261
  return [item.emit() for item in self.value]
252
262
 
@@ -1,12 +1,7 @@
1
1
  from collections.abc import Sequence
2
2
 
3
- from classiq.interface.exceptions import ClassiqError, ClassiqInternalExpansionError
3
+ from classiq.interface.exceptions import ClassiqError
4
4
  from classiq.interface.generator.constant import Constant
5
- from classiq.interface.generator.functions.classical_type import (
6
- ClassicalArray,
7
- ClassicalList,
8
- )
9
- from classiq.interface.generator.functions.concrete_types import ConcreteClassicalType
10
5
  from classiq.interface.model.classical_parameter_declaration import (
11
6
  ClassicalParameterDeclaration,
12
7
  )
@@ -138,11 +133,3 @@ def init_top_level_scope(model: Model, scope: Scope) -> None:
138
133
  def init_builtin_types() -> None:
139
134
  QMODULE.enum_decls |= BUILTIN_ENUM_DECLARATIONS
140
135
  QMODULE.type_decls |= BUILTIN_STRUCT_DECLARATIONS
141
-
142
-
143
- def _get_shape(classical_type: ConcreteClassicalType) -> tuple[int, ...]:
144
- if isinstance(classical_type, ClassicalList):
145
- raise ClassiqInternalExpansionError("Unexpected classical list")
146
- if isinstance(classical_type, ClassicalArray):
147
- return classical_type.size, *_get_shape(classical_type.element_type)
148
- return ()
@@ -8,7 +8,12 @@ from classiq.interface.generator.expressions.atomic_expression_functions import
8
8
  CLASSICAL_ATTRIBUTES,
9
9
  )
10
10
  from classiq.interface.generator.expressions.expression import Expression
11
- from classiq.interface.generator.functions.classical_type import ClassicalType
11
+ from classiq.interface.generator.functions.classical_type import (
12
+ ClassicalArray,
13
+ ClassicalList,
14
+ ClassicalTuple,
15
+ ClassicalType,
16
+ )
12
17
  from classiq.interface.generator.functions.type_name import TypeName
13
18
  from classiq.interface.helpers.pydantic_model_helpers import nameables_to_dict
14
19
  from classiq.interface.model.classical_parameter_declaration import (
@@ -20,7 +25,6 @@ from classiq.interface.model.native_function_definition import NativeFunctionDef
20
25
  from classiq.interface.model.quantum_function_call import ArgValue, QuantumFunctionCall
21
26
  from classiq.interface.model.quantum_function_declaration import (
22
27
  AnonPositionalArg,
23
- AnonQuantumFunctionDeclaration,
24
28
  AnonQuantumOperandDeclaration,
25
29
  NamedParamsQuantumFunctionDeclaration,
26
30
  QuantumOperandDeclaration,
@@ -33,6 +37,22 @@ from classiq.interface.model.quantum_lambda_function import (
33
37
  from classiq.model_expansions.visitors.variable_references import VarRefCollector
34
38
 
35
39
 
40
+ def set_generative_recursively(classical_type: ClassicalType) -> None:
41
+ if (
42
+ isinstance(classical_type, TypeName)
43
+ and classical_type.has_classical_struct_decl
44
+ ):
45
+ for field_type in classical_type.classical_struct_decl.variables.values():
46
+ set_generative_recursively(field_type)
47
+ return
48
+ classical_type.set_generative()
49
+ if isinstance(classical_type, (ClassicalArray, ClassicalList)):
50
+ set_generative_recursively(classical_type.element_type)
51
+ if isinstance(classical_type, ClassicalTuple):
52
+ for element_type in classical_type.element_types:
53
+ set_generative_recursively(element_type)
54
+
55
+
36
56
  def _get_expressions(arg: ArgValue) -> list[Expression]:
37
57
  if isinstance(arg, Expression):
38
58
  return [arg]
@@ -105,19 +125,8 @@ class SymbolicParamInference(ModelVisitor):
105
125
  for param in func.positional_arg_declarations:
106
126
  for expr in _get_param_expressions(param):
107
127
  self._process_compile_time_expression(expr.expr)
108
- self._set_enums_generative(func)
109
128
  self.visit(func.body)
110
129
 
111
- def _set_enums_generative(self, decl: AnonQuantumFunctionDeclaration) -> None:
112
- for param in decl.positional_arg_declarations:
113
- if (
114
- isinstance(param, AnonClassicalParameterDeclaration)
115
- and param.name is not None
116
- and isinstance(param.classical_type, TypeName)
117
- and param.classical_type.is_enum
118
- ):
119
- self._scope[param.name].set_generative()
120
-
121
130
  def visit_QuantumLambdaFunction(self, func: QuantumLambdaFunction) -> None:
122
131
  func.set_op_decl(func.func_decl.model_copy(deep=True))
123
132
  scope = dict(self._scope) | {
@@ -131,7 +140,6 @@ class SymbolicParamInference(ModelVisitor):
131
140
  )
132
141
  )
133
142
  with self.function_context(None, scope, scope_operands):
134
- self._set_enums_generative(func.named_func_decl)
135
143
  self.visit(func.body)
136
144
 
137
145
  def visit_QuantumFunctionCall(self, call: QuantumFunctionCall) -> None:
@@ -140,14 +148,20 @@ class SymbolicParamInference(ModelVisitor):
140
148
  for param, arg in zip_longest(params, call.positional_args):
141
149
  if (
142
150
  not isinstance(param, AnonClassicalParameterDeclaration)
143
- or param.classical_type.is_generative
151
+ or param.classical_type.is_purely_generative
144
152
  ):
145
153
  self._process_compile_time_expressions(arg)
146
154
  else:
155
+ if isinstance(arg, Expression):
156
+ for expr_part in self.get_generative_expression_parts(arg.expr):
157
+ self._process_compile_time_expression(expr_part)
147
158
  for expr in _get_expressions(arg):
148
159
  self._process_nested_compile_time_expression(expr.expr)
149
160
  self.generic_visit(call)
150
161
 
162
+ def get_generative_expression_parts(self, expr: str) -> list[str]:
163
+ return []
164
+
151
165
  def _get_params(self, call: QuantumFunctionCall) -> Sequence[AnonPositionalArg]:
152
166
  name = call.func_name
153
167
  if name in self._scope_operands:
@@ -174,7 +188,7 @@ class SymbolicParamInference(ModelVisitor):
174
188
  not isinstance(handle, FieldHandleBinding)
175
189
  or handle.field not in CLASSICAL_ATTRIBUTES
176
190
  ):
177
- self._scope[handle.name].set_generative()
191
+ set_generative_recursively(self._scope[handle.name])
178
192
 
179
193
  def _process_nested_compile_time_expression(self, expr: str) -> None:
180
194
  vrc = VarRefCollector(
@@ -184,3 +198,5 @@ class SymbolicParamInference(ModelVisitor):
184
198
  for handle in vrc.var_handles:
185
199
  for nested_expr in handle.expressions():
186
200
  self._process_compile_time_expression(nested_expr.expr)
201
+ for handle in vrc.subscript_handles:
202
+ self._process_compile_time_expression(str(handle))
@@ -34,11 +34,18 @@ class VarRefCollector(ast.NodeVisitor):
34
34
  self._ignore_sympy_symbols = ignore_sympy_symbols
35
35
  self._unevaluated = unevaluated
36
36
  self._is_nested = False
37
+ self._in_subscript = False
37
38
 
38
39
  @property
39
40
  def var_handles(self) -> list[HandleBinding]:
40
41
  return list(self._var_handles)
41
42
 
43
+ @property
44
+ def subscript_handles(self) -> list[HandleBinding]:
45
+ return [
46
+ handle for handle, in_subscript in self._var_handles.items() if in_subscript
47
+ ]
48
+
42
49
  def visit(self, node: ast.AST) -> Union[
43
50
  SubscriptHandleBinding,
44
51
  SlicedHandleBinding,
@@ -55,42 +62,8 @@ class VarRefCollector(ast.NodeVisitor):
55
62
  )
56
63
  return res
57
64
 
58
- def visit_Subscript(
59
- self, node: ast.Subscript
60
- ) -> Union[SubscriptHandleBinding, SlicedHandleBinding, None]:
61
- self.visit(node.slice)
62
- with self.set_nested():
63
- base_handle = self.visit(node.value)
64
- if base_handle is None:
65
- return None
66
-
67
- handle: Union[SubscriptHandleBinding, SlicedHandleBinding]
68
- if isinstance(node.slice, ast.Slice):
69
- if not self._unevaluated and (
70
- not isinstance(node.slice.lower, ast.Num)
71
- or not isinstance(node.slice.upper, ast.Num)
72
- ):
73
- raise ClassiqInternalExpansionError("Unevaluated slice bounds.")
74
- if node.slice.lower is None or node.slice.upper is None:
75
- raise ClassiqExpansionError(
76
- f"{str(base_handle)!r} slice must specify both lower and upper bounds"
77
- )
78
- handle = SlicedHandleBinding(
79
- base_handle=base_handle,
80
- start=Expression(expr=ast.unparse(node.slice.lower)),
81
- end=Expression(expr=ast.unparse(node.slice.upper)),
82
- )
83
- elif not self._unevaluated and not isinstance(node.slice, ast.Num):
84
- raise ClassiqInternalExpansionError("Unevaluated slice.")
85
- else:
86
- handle = SubscriptHandleBinding(
87
- base_handle=base_handle,
88
- index=Expression(expr=ast.unparse(node.slice)),
89
- )
90
-
91
- if not self._is_nested:
92
- self._var_handles[handle] = True
93
- return handle
65
+ def visit_Subscript(self, node: ast.Subscript) -> Union[HandleBinding, None]:
66
+ return self._get_subscript_handle(node.value, node.slice)
94
67
 
95
68
  def visit_Attribute(self, node: ast.Attribute) -> Optional[FieldHandleBinding]:
96
69
  return self._get_field_handle(node.value, node.attr)
@@ -111,7 +84,6 @@ class VarRefCollector(ast.NodeVisitor):
111
84
  raise ClassiqInternalExpansionError(
112
85
  "Unexpected 'do_subscript' arguments"
113
86
  )
114
- self.visit(node.args[1])
115
87
  return self._get_subscript_handle(node.args[0], node.args[1])
116
88
  return self.generic_visit(node)
117
89
 
@@ -127,20 +99,27 @@ class VarRefCollector(ast.NodeVisitor):
127
99
  field=field,
128
100
  )
129
101
  if not self._is_nested:
130
- self._var_handles[handle] = True
102
+ self._add_handle(handle)
131
103
  return handle
132
104
 
133
105
  def _get_subscript_handle(
134
106
  self, subject: ast.expr, subscript: ast.expr
135
107
  ) -> Optional[HandleBinding]:
108
+ with self.set_in_subscript():
109
+ self.visit(subscript)
136
110
  with self.set_nested():
137
111
  base_handle = self.visit(subject)
138
112
  if base_handle is None:
139
113
  return None
140
114
  handle: HandleBinding
141
115
  if isinstance(subscript, ast.Slice):
116
+ if not self._unevaluated and (
117
+ not isinstance(subscript.lower, ast.Num)
118
+ or not isinstance(subscript.upper, ast.Num)
119
+ ):
120
+ raise ClassiqInternalExpansionError("Unevaluated slice bounds")
142
121
  if subscript.lower is None or subscript.upper is None:
143
- raise ClassiqExpansionError(
122
+ raise ClassiqInternalExpansionError(
144
123
  f"{str(base_handle)!r} slice must specify both lower and upper bounds"
145
124
  )
146
125
  handle = SlicedHandleBinding(
@@ -148,13 +127,15 @@ class VarRefCollector(ast.NodeVisitor):
148
127
  start=Expression(expr=ast.unparse(subscript.lower)),
149
128
  end=Expression(expr=ast.unparse(subscript.upper)),
150
129
  )
130
+ elif not self._unevaluated and not isinstance(subscript, ast.Num):
131
+ raise ClassiqInternalExpansionError("Unevaluated subscript")
151
132
  else:
152
133
  handle = SubscriptHandleBinding(
153
134
  base_handle=base_handle,
154
135
  index=Expression(expr=ast.unparse(subscript)),
155
136
  )
156
137
  if not self._is_nested:
157
- self._var_handles[handle] = True
138
+ self._add_handle(handle)
158
139
  return handle
159
140
 
160
141
  def visit_Name(self, node: ast.Name) -> Optional[HandleBinding]:
@@ -164,16 +145,31 @@ class VarRefCollector(ast.NodeVisitor):
164
145
  return None
165
146
  handle = HandleBinding(name=node.id)
166
147
  if not self._is_nested:
167
- self._var_handles[handle] = True
148
+ self._add_handle(handle)
168
149
  return handle
169
150
 
170
151
  @contextmanager
171
- def set_nested(self) -> Iterator[None]:
152
+ def set_nested(self, val: bool = True) -> Iterator[None]:
172
153
  previous_is_nested = self._is_nested
173
- self._is_nested = True
154
+ self._is_nested = val
174
155
  yield
175
156
  self._is_nested = previous_is_nested
176
157
 
158
+ @contextmanager
159
+ def set_in_subscript(self) -> Iterator[None]:
160
+ previous_in_subscript = self._in_subscript
161
+ self._in_subscript = True
162
+ with self.set_nested(False):
163
+ yield
164
+ self._in_subscript = previous_in_subscript
165
+
166
+ def _add_handle(self, handle: HandleBinding) -> None:
167
+ if handle not in self._var_handles:
168
+ self._var_handles[handle] = self._in_subscript
169
+ return
170
+ if self._in_subscript and not self._var_handles[handle]:
171
+ self._var_handles[handle] = True
172
+
177
173
 
178
174
  class VarRefTransformer(ast.NodeTransformer):
179
175
  def __init__(self, var_mapping: dict[str, str]) -> None:
@@ -42,14 +42,14 @@ def linear_pauli_rotations(
42
42
  Corresponds to the braket notation:
43
43
 
44
44
  $$
45
- \\left|x\right\rangle _{n}\\left|q\right\rangle
46
- _{m}\rightarrow\\left|x\right\rangle
47
- _{n}\\prod_{k=1}^{m}\\left(\\cos\\left(\frac{a_{k}}{2}x+\frac{b_{k}}{2}\right)-
48
- i\\sin\\left(\frac{a_{k}}{2}x+\frac{b_{k}}{2}\right)P_{k}\right)\\left|q_{k}\right\rangle
45
+ \\left|x\\right\\rangle _{n}\\left|q\\right\\rangle
46
+ _{m}\\rightarrow\\left|x\\right\\rangle
47
+ _{n}\\prod_{k=1}^{m}\\left(\\cos\\left(\\frac{a_{k}}{2}x+\\frac{b_{k}}{2}\\right)-
48
+ i\\sin\\left(\\frac{a_{k}}{2}x+\\frac{b_{k}}{2}\\right)P_{k}\\right)\\left|q_{k}\\right\\rangle
49
49
  $$
50
50
 
51
- where $\\left|x\right\rangle$ is the control register,
52
- $\\left|q\right\rangle$ is the target register, each $P_{k}$ is one of
51
+ where $\\left|x\\right\\rangle$ is the control register,
52
+ $\\left|q\\right\\rangle$ is the target register, each $P_{k}$ is one of
53
53
  the three Pauli matrices $X$, $Y$, or $Z$, and $a_{k}$, $b_{k}$ are
54
54
  the user given slopes and offsets, respectively.
55
55
 
@@ -353,7 +353,7 @@ def _load_phases(
353
353
  @qfunc
354
354
  def inplace_prepare_complex_amplitudes(
355
355
  magnitudes: CArray[CReal],
356
- phases: CArray[CReal],
356
+ phases: list[float],
357
357
  target: QArray[QBit, Literal["log(get_field(magnitudes, 'len'), 2)"]],
358
358
  ) -> None:
359
359
  """
@@ -32,7 +32,7 @@ def apply_to_all(
32
32
 
33
33
  @qfunc
34
34
  def hadamard_transform(target: QArray[QBit]) -> None:
35
- """
35
+ r"""
36
36
  [Qmod Classiq-library function]
37
37
 
38
38
  Applies Hadamard transform to the target qubits.
@@ -40,7 +40,7 @@ def hadamard_transform(target: QArray[QBit]) -> None:
40
40
  Corresponds to the braket notation:
41
41
 
42
42
  $$
43
- H^{\\otimes n} |x\rangle = \frac{1}{\\sqrt{2^n}} \\sum_{y=0}^{2^n - 1} (-1)^{x \\cdot y} |y\rangle
43
+ H^{\otimes n} |x\rangle = \frac{1}{\sqrt{2^n}} \sum_{y=0}^{2^n - 1} (-1)^{x \\cdot y} |y\rangle
44
44
  $$
45
45
 
46
46
  Args:
@@ -65,8 +65,8 @@ class _PythonClassicalType(PythonClassicalType):
65
65
  return
66
66
 
67
67
  enum_decl = declaration_from_enum(py_type)
68
+ check_duplicate_types([enum_decl, *self.qmodule.user_types()])
68
69
  self.qmodule.enum_decls[py_type.__name__] = enum_decl
69
- check_duplicate_types([enum_decl])
70
70
 
71
71
  def register_struct(self, py_type: type) -> TypeName:
72
72
  classical_type = super().register_struct(py_type)
@@ -74,7 +74,9 @@ class _PythonClassicalType(PythonClassicalType):
74
74
  return classical_type
75
75
  all_decls = BUILTIN_STRUCT_DECLARATIONS | self.qmodule.type_decls
76
76
  if py_type.__name__ in all_decls:
77
- classical_type.set_classical_struct_decl(all_decls[py_type.__name__])
77
+ classical_type.set_classical_struct_decl(
78
+ all_decls[py_type.__name__].model_copy()
79
+ )
78
80
  return classical_type
79
81
 
80
82
  struct_decl = StructDeclaration(
@@ -83,8 +85,8 @@ class _PythonClassicalType(PythonClassicalType):
83
85
  f.name: self.convert(f.type) for f in dataclasses.fields(py_type)
84
86
  },
85
87
  )
88
+ check_duplicate_types([struct_decl, *self.qmodule.user_types()])
86
89
  self.qmodule.type_decls[py_type.__name__] = struct_decl
87
- check_duplicate_types([struct_decl])
88
90
  validate_cstruct(struct_decl)
89
91
 
90
92
  classical_type.set_classical_struct_decl(struct_decl)
@@ -1,5 +1,6 @@
1
1
  from collections import defaultdict
2
- from typing import TYPE_CHECKING
2
+ from collections.abc import Sequence
3
+ from typing import TYPE_CHECKING, Union
3
4
 
4
5
  from classiq.interface.generator.constant import Constant
5
6
  from classiq.interface.generator.types.compilation_metadata import CompilationMetadata
@@ -10,6 +11,9 @@ from classiq.interface.model.native_function_definition import (
10
11
  NativeFunctionDefinition,
11
12
  )
12
13
 
14
+ from classiq.qmod.builtins.enums import BUILTIN_ENUM_DECLARATIONS
15
+ from classiq.qmod.builtins.structs import BUILTIN_STRUCT_DECLARATIONS
16
+
13
17
  if TYPE_CHECKING:
14
18
  from classiq.qmod.quantum_function import GenerativeQFunc
15
19
 
@@ -34,6 +38,22 @@ class ModelStateContainer:
34
38
  self.generative_functions = {}
35
39
  self.function_dependencies = defaultdict(list)
36
40
 
41
+ def user_types(
42
+ self,
43
+ ) -> Sequence[Union[EnumDeclaration, StructDeclaration, QStructDeclaration]]:
44
+ type_decls = [
45
+ t
46
+ for t in self.type_decls.values()
47
+ if t.name not in BUILTIN_STRUCT_DECLARATIONS
48
+ ]
49
+ enum_decls = [
50
+ t
51
+ for t in self.enum_decls.values()
52
+ if t.name not in BUILTIN_ENUM_DECLARATIONS
53
+ ]
54
+ qstruct_decls = list(self.qstruct_decls.values())
55
+ return [*type_decls, *enum_decls, *qstruct_decls]
56
+
37
57
 
38
58
  QMODULE = ModelStateContainer()
39
59
  QMODULE.reset()
@@ -1,12 +1,14 @@
1
1
  from collections.abc import Mapping
2
2
  from typing import Optional, Union
3
3
 
4
+ from classiq.interface.exceptions import ClassiqInternalError
4
5
  from classiq.interface.generator.constant import Constant
5
6
  from classiq.interface.generator.expressions.expression import Expression
6
7
  from classiq.interface.generator.functions.classical_type import (
7
8
  Bool,
8
9
  ClassicalArray,
9
10
  ClassicalList,
11
+ ClassicalTuple,
10
12
  Integer,
11
13
  Real,
12
14
  )
@@ -250,6 +252,12 @@ class DSLPrettyPrinter(ModelVisitor):
250
252
  def visit_ClassicalArray(self, ctarray: ClassicalArray) -> str:
251
253
  return f"{self.visit(ctarray.element_type)}[{ctarray.size}]"
252
254
 
255
+ def visit_ClassicalTuple(self, classical_tuple: ClassicalTuple) -> str:
256
+ raw_type = classical_tuple.get_raw_type()
257
+ if isinstance(raw_type, ClassicalTuple):
258
+ raise ClassiqInternalError("Empty tuple pretty-print not supported")
259
+ return self.visit(raw_type)
260
+
253
261
  def visit_TypeName(self, type_: TypeName) -> str:
254
262
  return type_.name
255
263
 
@@ -47,6 +47,7 @@ class ASTToQMODCode(ast.NodeVisitor):
47
47
  imports: dict[str, int]
48
48
  symbolic_imports: dict[str, int]
49
49
  decimal_precision: int
50
+ one_line: bool
50
51
  indent_seq: str = " "
51
52
 
52
53
  @property
@@ -140,6 +141,10 @@ class ASTToQMODCode(ast.NodeVisitor):
140
141
  if not IDENTIFIER.match(field):
141
142
  raise AssertionError("Error parsing struct field access.")
142
143
  return f"{self.visit(node.args[0])}.{field}"
144
+ elif func == "do_subscript":
145
+ if len(node.args) != 2:
146
+ raise AssertionError("Error parsing array subscript.")
147
+ return f"{self.visit(node.args[0])}[{self.visit(node.args[1])}]"
143
148
  elif func == "struct_literal":
144
149
  if len(node.args) != 1 or not isinstance(node.args[0], ast.Name):
145
150
  raise AssertionError("Error parsing struct literal.")
@@ -164,7 +169,7 @@ class ASTToQMODCode(ast.NodeVisitor):
164
169
  raise AssertionError("Cannot parse node of type: " + type(node).__name__)
165
170
 
166
171
  def indent_items(self, items: Callable[[], list[str]]) -> str:
167
- should_indent = (
172
+ should_indent = not self.one_line and (
168
173
  len("".join([i.strip() for i in items()])) >= LIST_FORMAT_CHAR_LIMIT
169
174
  )
170
175
  if should_indent:
@@ -213,10 +218,12 @@ def transform_expression(
213
218
  symbolic_imports: dict[str, int],
214
219
  level: int = 0,
215
220
  decimal_precision: int = DEFAULT_DECIMAL_PRECISION,
221
+ one_line: bool = False,
216
222
  ) -> str:
217
223
  return ASTToQMODCode(
218
224
  level=level,
219
225
  decimal_precision=decimal_precision,
220
226
  imports=imports,
227
+ one_line=one_line,
221
228
  symbolic_imports=symbolic_imports,
222
229
  ).visit(ast.parse(expr))
@@ -10,6 +10,7 @@ from classiq.interface.generator.functions.classical_type import (
10
10
  Bool,
11
11
  ClassicalArray,
12
12
  ClassicalList,
13
+ ClassicalTuple,
13
14
  Integer,
14
15
  Real,
15
16
  )
@@ -345,6 +346,12 @@ class PythonPrettyPrinter(ModelVisitor):
345
346
  self._imports["CArray"] = 1
346
347
  return f"CArray[{self.visit(ctarray.element_type)}, {ctarray.size}]"
347
348
 
349
+ def visit_ClassicalTuple(self, classical_tuple: ClassicalTuple) -> str:
350
+ raw_type = classical_tuple.get_raw_type()
351
+ if isinstance(raw_type, ClassicalTuple):
352
+ raise ClassiqInternalError("Empty tuple pretty-print not supported")
353
+ return self.visit(raw_type)
354
+
348
355
  def visit_TypeName(self, type_: TypeName) -> str:
349
356
  self._import_type_name(type_)
350
357
  return type_.name
@@ -62,7 +62,7 @@ class PythonClassicalType:
62
62
  return self.register_struct(py_type)
63
63
  elif inspect.isclass(py_type) and isinstance(py_type, EnumMeta):
64
64
  self.register_enum(py_type)
65
- return Enum(name=py_type.__name__)
65
+ return Enum(name=py_type.__name__).set_generative()
66
66
  elif py_type in (CArray, list):
67
67
  raise ClassiqValueError(CARRAY_ERROR_MESSAGE)
68
68
  return None
@@ -1,10 +1,11 @@
1
1
  from typing import TYPE_CHECKING, Any, Union
2
2
 
3
- from classiq.interface.exceptions import ClassiqValueError
3
+ from classiq.interface.exceptions import ClassiqInternalError, ClassiqValueError
4
4
  from classiq.interface.generator.functions.classical_type import (
5
5
  Bool,
6
6
  ClassicalArray,
7
7
  ClassicalList,
8
+ ClassicalTuple,
8
9
  ClassicalType,
9
10
  Integer,
10
11
  Real,
@@ -41,7 +42,7 @@ class CParamList(CParam):
41
42
  def __init__(
42
43
  self,
43
44
  expr: str,
44
- list_type: Union[ClassicalList, ClassicalArray],
45
+ list_type: Union[ClassicalList, ClassicalArray, ClassicalTuple],
45
46
  qmodule: ModelStateContainer,
46
47
  ) -> None:
47
48
  super().__init__(expr)
@@ -49,9 +50,35 @@ class CParamList(CParam):
49
50
  self._list_type = list_type
50
51
 
51
52
  def __getitem__(self, key: Any) -> CParam:
52
- param_type = self._list_type.element_type
53
- if isinstance(key, slice):
54
- param_type = self._list_type
53
+ param_type: ClassicalType
54
+ if not isinstance(key, slice):
55
+ if isinstance(self._list_type, ClassicalTuple):
56
+ if isinstance(key, int) and 0 <= key < len(
57
+ self._list_type.element_types
58
+ ):
59
+ param_type = self._list_type.element_types[key]
60
+ elif len(self._list_type.element_types) == 0:
61
+ raise ClassiqValueError("Array is empty")
62
+ else:
63
+ param_type = self._list_type.element_types[0].get_raw_type()
64
+ else:
65
+ param_type = self._list_type.element_type
66
+ else:
67
+ if not isinstance(self._list_type, ClassicalTuple):
68
+ param_type = self._list_type
69
+ else:
70
+ if (
71
+ (isinstance(key.start, int) or key.start is None)
72
+ and (isinstance(key.stop, int) or key.stop is None)
73
+ and (isinstance(key.step, int) or key.step is None)
74
+ ):
75
+ param_type = ClassicalTuple(
76
+ element_types=self._list_type.element_types.__getitem__(key)
77
+ )
78
+ elif len(self._list_type.element_types) == 0:
79
+ param_type = self._list_type
80
+ else:
81
+ param_type = self._list_type.element_types[0].get_raw_type()
55
82
  start = key.start if key.start is not None else ""
56
83
  stop = key.stop if key.stop is not None else ""
57
84
  if key.step is not None:
@@ -110,7 +137,7 @@ class CParamStruct(CParam):
110
137
 
111
138
  return create_param(
112
139
  f"get_field({variable_name},{field_name!r})",
113
- field_type,
140
+ field_type.model_copy(deep=True),
114
141
  qmodule=qmodule,
115
142
  )
116
143
 
@@ -118,7 +145,11 @@ class CParamStruct(CParam):
118
145
  def create_param(
119
146
  expr_str: str, ctype: ClassicalType, qmodule: ModelStateContainer
120
147
  ) -> CParam:
121
- if isinstance(ctype, (ClassicalList, ClassicalArray)):
148
+ if isinstance(ctype, TypeName) and ctype.has_classical_struct_decl:
149
+ decl = ctype.classical_struct_decl
150
+ ctype = Struct(name=ctype.name)
151
+ ctype.set_classical_struct_decl(decl)
152
+ if isinstance(ctype, (ClassicalList, ClassicalArray, ClassicalTuple)):
122
153
  return CParamList(expr_str, ctype, qmodule=qmodule)
123
154
  elif isinstance(ctype, Struct):
124
155
  return CParamStruct(expr_str, ctype, qmodule=qmodule)
@@ -137,6 +168,11 @@ def get_qmod_type(ctype: ClassicalType) -> type:
137
168
  return CArray[get_qmod_type(ctype.element_type)] # type: ignore[misc]
138
169
  elif isinstance(ctype, ClassicalArray):
139
170
  return CArray[get_qmod_type(ctype.element_type), ctype.size] # type: ignore[misc]
171
+ elif isinstance(ctype, ClassicalTuple):
172
+ raw_type = ctype.get_raw_type()
173
+ if isinstance(raw_type, ClassicalTuple):
174
+ raise ClassiqInternalError("Tuple is empty")
175
+ return get_qmod_type(raw_type)
140
176
  elif isinstance(ctype, TypeName):
141
177
  type_ = type(ctype.name, (TypeName,), dict())
142
178
  if isinstance(ctype, Struct):