classiq 0.77.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 (69) hide show
  1. classiq/applications/iqae/__init__.py +0 -0
  2. classiq/applications/iqae/iqae.py +207 -0
  3. classiq/execution/__init__.py +1 -1
  4. classiq/interface/_version.py +1 -1
  5. classiq/interface/applications/iqae/__init__.py +0 -0
  6. classiq/interface/applications/iqae/generic_iqae.py +222 -0
  7. classiq/interface/applications/iqae/iqae_result.py +45 -0
  8. classiq/interface/debug_info/debug_info.py +3 -0
  9. classiq/interface/executor/execution_result.py +1 -1
  10. classiq/interface/executor/user_budget.py +1 -1
  11. classiq/interface/generator/arith/arithmetic.py +10 -6
  12. classiq/interface/generator/arith/binary_ops.py +8 -11
  13. classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +9 -3
  14. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +90 -23
  15. classiq/interface/generator/expressions/proxies/classical/utils.py +4 -0
  16. classiq/interface/generator/functions/classical_type.py +74 -0
  17. classiq/interface/generator/functions/concrete_types.py +3 -0
  18. classiq/interface/generator/functions/type_name.py +32 -3
  19. classiq/interface/generator/generated_circuit_data.py +21 -8
  20. classiq/interface/helpers/model_normalizer.py +47 -0
  21. classiq/interface/ide/visual_model.py +2 -0
  22. classiq/interface/interface_version.py +1 -1
  23. classiq/interface/model/bounds.py +12 -0
  24. classiq/interface/model/model.py +9 -5
  25. classiq/interface/model/quantum_type.py +25 -3
  26. classiq/interface/model/statement_block.py +2 -0
  27. classiq/model_expansions/atomic_expression_functions_defs.py +20 -6
  28. classiq/model_expansions/closure.py +1 -58
  29. classiq/model_expansions/evaluators/argument_types.py +21 -2
  30. classiq/model_expansions/evaluators/classical_type_inference.py +42 -13
  31. classiq/model_expansions/evaluators/quantum_type_utils.py +31 -3
  32. classiq/model_expansions/function_builder.py +0 -45
  33. classiq/model_expansions/generative_functions.py +1 -1
  34. classiq/model_expansions/interpreters/base_interpreter.py +12 -14
  35. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +0 -17
  36. classiq/model_expansions/interpreters/generative_interpreter.py +9 -0
  37. classiq/model_expansions/quantum_operations/__init__.py +3 -0
  38. classiq/model_expansions/quantum_operations/allocate.py +3 -1
  39. classiq/model_expansions/quantum_operations/assignment_result_processor.py +7 -3
  40. classiq/model_expansions/quantum_operations/bind.py +8 -1
  41. classiq/model_expansions/quantum_operations/bounds.py +30 -0
  42. classiq/model_expansions/quantum_operations/call_emitter.py +116 -37
  43. classiq/model_expansions/quantum_operations/emitter.py +6 -1
  44. classiq/model_expansions/quantum_operations/function_calls_cache.py +91 -0
  45. classiq/model_expansions/quantum_operations/quantum_function_call.py +5 -6
  46. classiq/model_expansions/scope.py +33 -13
  47. classiq/model_expansions/scope_initialization.py +1 -14
  48. classiq/model_expansions/transformers/type_qualifier_inference.py +183 -0
  49. classiq/model_expansions/utils/text_utils.py +4 -2
  50. classiq/model_expansions/visitors/symbolic_param_inference.py +33 -28
  51. classiq/model_expansions/visitors/variable_references.py +39 -43
  52. classiq/open_library/functions/linear_pauli_rotation.py +6 -6
  53. classiq/open_library/functions/state_preparation.py +1 -1
  54. classiq/open_library/functions/utility_functions.py +2 -2
  55. classiq/qmod/builtins/classical_execution_primitives.py +1 -1
  56. classiq/qmod/declaration_inferrer.py +5 -3
  57. classiq/qmod/model_state_container.py +21 -1
  58. classiq/qmod/native/pretty_printer.py +8 -0
  59. classiq/qmod/pretty_print/expression_to_python.py +8 -1
  60. classiq/qmod/pretty_print/pretty_printer.py +7 -0
  61. classiq/qmod/python_classical_type.py +1 -1
  62. classiq/qmod/qmod_parameter.py +43 -7
  63. classiq/qmod/qmod_variable.py +7 -4
  64. classiq/qmod/semantics/annotation/qstruct_annotator.py +15 -4
  65. classiq/qmod/utilities.py +5 -1
  66. {classiq-0.77.0.dist-info → classiq-0.79.0.dist-info}/METADATA +1 -1
  67. {classiq-0.77.0.dist-info → classiq-0.79.0.dist-info}/RECORD +68 -59
  68. classiq/interface/executor/iqae_result.py +0 -17
  69. {classiq-0.77.0.dist-info → classiq-0.79.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,183 @@
1
+ import ast
2
+ import functools
3
+ import itertools
4
+ from collections.abc import Collection, Iterator, Sequence
5
+ from contextlib import contextmanager
6
+
7
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
8
+ from classiq.interface.generator.functions.type_qualifier import TypeQualifier
9
+ from classiq.interface.model.allocate import Allocate
10
+ from classiq.interface.model.bind_operation import BindOperation
11
+ from classiq.interface.model.control import Control
12
+ from classiq.interface.model.invert import Invert
13
+ from classiq.interface.model.model_visitor import ModelVisitor
14
+ from classiq.interface.model.phase_operation import PhaseOperation
15
+ from classiq.interface.model.port_declaration import PortDeclaration
16
+ from classiq.interface.model.power import Power
17
+ from classiq.interface.model.quantum_expressions.amplitude_loading_operation import (
18
+ AmplitudeLoadingOperation,
19
+ )
20
+ from classiq.interface.model.quantum_expressions.arithmetic_operation import (
21
+ ArithmeticOperation,
22
+ )
23
+ from classiq.interface.model.quantum_expressions.quantum_expression import (
24
+ QuantumExpressionOperation,
25
+ )
26
+ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
27
+ from classiq.interface.model.quantum_statement import QuantumStatement
28
+ from classiq.interface.model.within_apply_operation import WithinApply
29
+
30
+ from classiq.model_expansions.visitors.variable_references import VarRefCollector
31
+
32
+
33
+ class TypeQualifierInference(ModelVisitor):
34
+ """
35
+ This class assumes that function calls are topologically sorted, so it traverses
36
+ the list of function calls and infers the type qualifiers for each function call
37
+ without going recursively into the function calls.
38
+ The function definition ports are modified inplace.
39
+ """
40
+
41
+ def __init__(self, support_unused_ports: bool = True) -> None:
42
+ self._signature_ports: dict[str, PortDeclaration] = dict()
43
+ self._inferred_ports: dict[str, PortDeclaration] = dict()
44
+ self._support_unused_ports = (
45
+ support_unused_ports # could be turned off for debugging
46
+ )
47
+
48
+ @contextmanager
49
+ def infer_ports(self, ports: Collection[PortDeclaration]) -> Iterator[bool]:
50
+ for port in ports:
51
+ if port.type_qualifier is TypeQualifier.Inferred:
52
+ self._inferred_ports[port.name] = port
53
+ else:
54
+ self._signature_ports[port.name] = port
55
+
56
+ yield len(self._inferred_ports) > 0
57
+
58
+ self._set_unused_as_const()
59
+ self._signature_ports.clear()
60
+ self._inferred_ports.clear()
61
+
62
+ def _set_unused_as_const(self) -> None:
63
+ unresolved_ports = [
64
+ port
65
+ for port in self._inferred_ports.values()
66
+ if port.type_qualifier is TypeQualifier.Inferred
67
+ ]
68
+ if not self._support_unused_ports and len(unresolved_ports) > 0:
69
+ raise ClassiqInternalExpansionError(
70
+ f"Unresolved inferred ports detected: {', '.join(port.name for port in unresolved_ports)}. "
71
+ "All ports must have their type qualifiers resolved."
72
+ )
73
+ for port in unresolved_ports:
74
+ port.type_qualifier = TypeQualifier.Const
75
+
76
+ def _reduce_qualifier(self, candidate: str, qualifier: TypeQualifier) -> None:
77
+ if candidate in self._inferred_ports:
78
+ self._inferred_ports[candidate].type_qualifier = TypeQualifier.and_(
79
+ self._inferred_ports[candidate].type_qualifier, qualifier
80
+ )
81
+
82
+ def run(
83
+ self, ports: Collection[PortDeclaration], body: Sequence[QuantumStatement]
84
+ ) -> None:
85
+ with self.infer_ports(ports) as should_infer:
86
+ if should_infer:
87
+ self.visit(body)
88
+
89
+ def visit_QuantumFunctionCall(self, call: QuantumFunctionCall) -> None:
90
+ for handle, port in call.handles_with_params:
91
+ self._reduce_qualifier(handle.name, port.type_qualifier)
92
+
93
+ def visit_Allocate(self, alloc: Allocate) -> None:
94
+ self._reduce_qualifier(alloc.target.name, TypeQualifier.QFree)
95
+
96
+ def visit_BindOperation(self, bind_op: BindOperation) -> None:
97
+ reduced_qualifier = self._get_reduced_qualifier(bind_op)
98
+ for handle in itertools.chain(bind_op.in_handles, bind_op.out_handles):
99
+ self._reduce_qualifier(handle.name, reduced_qualifier)
100
+
101
+ def _get_reduced_qualifier(self, bind_op: BindOperation) -> TypeQualifier:
102
+ handles = itertools.chain(bind_op.in_handles, bind_op.out_handles)
103
+ op_vars = {handle.name for handle in handles}
104
+
105
+ signature_ports = {
106
+ name: self._signature_ports[name]
107
+ for name in op_vars.intersection(self._signature_ports)
108
+ }
109
+ known_inferred_ports = {
110
+ name: self._inferred_ports[name]
111
+ for name in op_vars.intersection(self._inferred_ports)
112
+ if self._inferred_ports[name].type_qualifier is not TypeQualifier.Inferred
113
+ }
114
+ min_qualifier = self._get_min_qualifier(known_inferred_ports, signature_ports)
115
+ if not all(
116
+ port.type_qualifier is min_qualifier for port in signature_ports.values()
117
+ ):
118
+ raise ClassiqInternalExpansionError(
119
+ f"Bind operation {bind_op} has inconsistent type qualifiers: "
120
+ f"{signature_ports}, {known_inferred_ports}"
121
+ )
122
+
123
+ return min_qualifier
124
+
125
+ @staticmethod
126
+ def _get_min_qualifier(
127
+ known_inferred_ports: dict[str, PortDeclaration],
128
+ signature_ports: dict[str, PortDeclaration],
129
+ ) -> TypeQualifier:
130
+ known_ports = tuple(
131
+ itertools.chain(signature_ports.values(), known_inferred_ports.values())
132
+ )
133
+ if len(known_ports) == 0:
134
+ return TypeQualifier.Quantum
135
+ elif len(known_ports) == 1:
136
+ return known_ports[0].type_qualifier
137
+ else:
138
+ return functools.reduce(
139
+ TypeQualifier.and_, (port.type_qualifier for port in known_ports)
140
+ )
141
+
142
+ @staticmethod
143
+ def _extract_expr_vars(expr_op: QuantumExpressionOperation) -> list[str]:
144
+ vrc = VarRefCollector(
145
+ ignore_duplicated_handles=True, ignore_sympy_symbols=True, unevaluated=True
146
+ )
147
+ vrc.visit(ast.parse(expr_op.expression.expr))
148
+ return [handle.name for handle in vrc.var_handles]
149
+
150
+ def visit_ArithmeticOperation(self, arith: ArithmeticOperation) -> None:
151
+ result_var = arith.result_var.name
152
+ self._reduce_qualifier(result_var, TypeQualifier.QFree)
153
+ for expr_var in self._extract_expr_vars(arith):
154
+ self._reduce_qualifier(expr_var, TypeQualifier.Const)
155
+
156
+ def visit_AmplitudeLoadingOperation(
157
+ self, amp_load: AmplitudeLoadingOperation
158
+ ) -> None:
159
+ result_var = amp_load.result_var.name
160
+ self._reduce_qualifier(result_var, TypeQualifier.Quantum)
161
+ for expr_var in self._extract_expr_vars(amp_load):
162
+ self._reduce_qualifier(expr_var, TypeQualifier.Const)
163
+
164
+ def visit_PhaseOperation(self, phase_op: PhaseOperation) -> None:
165
+ for expr_var in self._extract_expr_vars(phase_op):
166
+ self._reduce_qualifier(expr_var, TypeQualifier.Const)
167
+
168
+ def visit_Control(self, control: Control) -> None:
169
+ for control_var in self._extract_expr_vars(control):
170
+ self._reduce_qualifier(control_var, TypeQualifier.Const)
171
+ self.visit(control.body)
172
+ if control.else_block is not None:
173
+ self.visit(control.else_block)
174
+
175
+ def visit_Invert(self, invert: Invert) -> None:
176
+ self.visit(invert.body)
177
+
178
+ def visit_Power(self, power: Power) -> None:
179
+ self.visit(power.body)
180
+
181
+ def visit_WithinApply(self, within_apply: WithinApply) -> None:
182
+ self.visit(within_apply.compute)
183
+ self.visit(within_apply.action)
@@ -10,7 +10,9 @@ def they(items: list) -> str:
10
10
  return "it" if len(items) == 1 else "they"
11
11
 
12
12
 
13
- def readable_list(items: list) -> str:
13
+ def readable_list(items: list, quote: bool = False) -> str:
14
+ if quote:
15
+ items = [repr(str(item)) for item in items]
14
16
  if len(items) == 1:
15
17
  return str(items[0])
16
- return f"{', '.join(items[:-1])} and {items[-1]}"
18
+ return f"{', '.join(items[:-1])}{',' if len(items) > 2 else ''} and {items[-1]}"
@@ -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]
@@ -73,7 +93,6 @@ class SymbolicParamInference(ModelVisitor):
73
93
  else nameables_to_dict(additional_signatures)
74
94
  )
75
95
  self._inferred_funcs: set[str] = set()
76
- self._call_stack: list[str] = []
77
96
  self._scope: Mapping[str, ClassicalType] = {}
78
97
  self._scope_operands: dict[str, QuantumOperandDeclaration] = {}
79
98
 
@@ -81,9 +100,6 @@ class SymbolicParamInference(ModelVisitor):
81
100
  for func in self._functions.values():
82
101
  self._infer_func_params(func)
83
102
 
84
- def _is_recursive_call(self, func: str) -> bool:
85
- return func in self._call_stack
86
-
87
103
  @contextmanager
88
104
  def function_context(
89
105
  self,
@@ -91,8 +107,6 @@ class SymbolicParamInference(ModelVisitor):
91
107
  scope: Mapping[str, ClassicalType],
92
108
  scope_operands: dict[str, QuantumOperandDeclaration],
93
109
  ) -> Iterator[None]:
94
- if func_name is not None:
95
- self._call_stack.append(func_name)
96
110
  prev_scope = self._scope
97
111
  self._scope = scope
98
112
  prev_scope_ops = self._scope_operands
@@ -100,31 +114,18 @@ class SymbolicParamInference(ModelVisitor):
100
114
  yield
101
115
  self._scope = prev_scope
102
116
  self._scope_operands = prev_scope_ops
103
- if func_name is not None:
104
- self._call_stack.pop()
105
117
 
106
118
  def _infer_func_params(self, func: NativeFunctionDefinition) -> None:
107
119
  if func.name in self._inferred_funcs:
108
120
  return
121
+ self._inferred_funcs.add(func.name)
109
122
  scope = {param.name: param.classical_type for param in func.param_decls}
110
123
  scope_operands = func.operand_declarations_dict
111
124
  with self.function_context(func.name, scope, scope_operands):
112
125
  for param in func.positional_arg_declarations:
113
126
  for expr in _get_param_expressions(param):
114
127
  self._process_compile_time_expression(expr.expr)
115
- self._set_enums_generative(func)
116
128
  self.visit(func.body)
117
- self._inferred_funcs.add(func.name)
118
-
119
- def _set_enums_generative(self, decl: AnonQuantumFunctionDeclaration) -> None:
120
- for param in decl.positional_arg_declarations:
121
- if (
122
- isinstance(param, AnonClassicalParameterDeclaration)
123
- and param.name is not None
124
- and isinstance(param.classical_type, TypeName)
125
- and param.classical_type.is_enum
126
- ):
127
- self._scope[param.name].set_generative()
128
129
 
129
130
  def visit_QuantumLambdaFunction(self, func: QuantumLambdaFunction) -> None:
130
131
  func.set_op_decl(func.func_decl.model_copy(deep=True))
@@ -139,26 +140,28 @@ class SymbolicParamInference(ModelVisitor):
139
140
  )
140
141
  )
141
142
  with self.function_context(None, scope, scope_operands):
142
- self._set_enums_generative(func.named_func_decl)
143
143
  self.visit(func.body)
144
144
 
145
145
  def visit_QuantumFunctionCall(self, call: QuantumFunctionCall) -> None:
146
146
  self._process_compile_time_expressions(call.function)
147
- name = call.func_name
148
- if self._is_recursive_call(name):
149
- return # Recursion is not fully supported
150
147
  params = self._get_params(call)
151
148
  for param, arg in zip_longest(params, call.positional_args):
152
149
  if (
153
150
  not isinstance(param, AnonClassicalParameterDeclaration)
154
- or param.classical_type.is_generative
151
+ or param.classical_type.is_purely_generative
155
152
  ):
156
153
  self._process_compile_time_expressions(arg)
157
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)
158
158
  for expr in _get_expressions(arg):
159
159
  self._process_nested_compile_time_expression(expr.expr)
160
160
  self.generic_visit(call)
161
161
 
162
+ def get_generative_expression_parts(self, expr: str) -> list[str]:
163
+ return []
164
+
162
165
  def _get_params(self, call: QuantumFunctionCall) -> Sequence[AnonPositionalArg]:
163
166
  name = call.func_name
164
167
  if name in self._scope_operands:
@@ -185,7 +188,7 @@ class SymbolicParamInference(ModelVisitor):
185
188
  not isinstance(handle, FieldHandleBinding)
186
189
  or handle.field not in CLASSICAL_ATTRIBUTES
187
190
  ):
188
- self._scope[handle.name].set_generative()
191
+ set_generative_recursively(self._scope[handle.name])
189
192
 
190
193
  def _process_nested_compile_time_expression(self, expr: str) -> None:
191
194
  vrc = VarRefCollector(
@@ -195,3 +198,5 @@ class SymbolicParamInference(ModelVisitor):
195
198
  for handle in vrc.var_handles:
196
199
  for nested_expr in handle.expressions():
197
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:
@@ -1,8 +1,8 @@
1
1
  from typing import Final, Optional, Union
2
2
 
3
+ from classiq.interface.applications.iqae.iqae_result import IQAEResult
3
4
  from classiq.interface.exceptions import ClassiqError
4
5
  from classiq.interface.executor.execution_preferences import QaeWithQpeEstimationMethod
5
- from classiq.interface.executor.iqae_result import IQAEResult
6
6
  from classiq.interface.executor.result import (
7
7
  EstimationResult,
8
8
  EstimationResults,
@@ -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