classiq 0.74.0__py3-none-any.whl → 0.76.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 (96) hide show
  1. classiq/_internals/api_wrapper.py +36 -0
  2. classiq/analyzer/show_interactive_hack.py +58 -2
  3. classiq/applications/chemistry/chemistry_model_constructor.py +8 -1
  4. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +2 -0
  5. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -4
  6. classiq/applications/qnn/gradients/quantum_gradient.py +3 -5
  7. classiq/applications/qnn/gradients/simple_quantum_gradient.py +2 -2
  8. classiq/applications/qnn/qlayer.py +23 -19
  9. classiq/applications/qnn/types.py +1 -4
  10. classiq/execution/__init__.py +3 -0
  11. classiq/execution/execution_session.py +3 -16
  12. classiq/execution/qnn.py +2 -2
  13. classiq/execution/user_budgets.py +38 -0
  14. classiq/executor.py +7 -19
  15. classiq/interface/_version.py +1 -1
  16. classiq/interface/debug_info/debug_info.py +18 -13
  17. classiq/interface/executor/user_budget.py +56 -0
  18. classiq/interface/generator/application_apis/finance_declarations.py +3 -0
  19. classiq/interface/generator/expressions/atomic_expression_functions.py +3 -0
  20. classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +30 -124
  21. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +46 -22
  22. classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
  23. classiq/interface/generator/expressions/proxies/classical/utils.py +14 -13
  24. classiq/interface/generator/expressions/proxies/quantum/qmod_qscalar_proxy.py +9 -2
  25. classiq/interface/generator/expressions/proxies/quantum/qmod_sized_proxy.py +4 -1
  26. classiq/interface/generator/expressions/sympy_supported_expressions.py +1 -0
  27. classiq/interface/generator/functions/classical_type.py +36 -1
  28. classiq/interface/generator/functions/type_name.py +32 -5
  29. classiq/interface/generator/functions/type_qualifier.py +15 -0
  30. classiq/interface/generator/generated_circuit_data.py +11 -25
  31. classiq/interface/generator/model/preferences/preferences.py +7 -0
  32. classiq/interface/generator/quantum_program.py +5 -19
  33. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +10 -13
  34. classiq/interface/helpers/backward_compatibility.py +9 -0
  35. classiq/interface/helpers/datastructures.py +6 -0
  36. classiq/interface/helpers/versioned_model.py +12 -0
  37. classiq/interface/interface_version.py +1 -1
  38. classiq/interface/model/handle_binding.py +12 -0
  39. classiq/interface/model/port_declaration.py +1 -2
  40. classiq/interface/model/quantum_lambda_function.py +2 -1
  41. classiq/interface/model/statement_block.py +9 -1
  42. classiq/interface/model/within_apply_operation.py +12 -0
  43. classiq/interface/server/routes.py +6 -0
  44. classiq/model_expansions/atomic_expression_functions_defs.py +82 -23
  45. classiq/model_expansions/capturing/captured_vars.py +2 -0
  46. classiq/model_expansions/closure.py +18 -0
  47. classiq/model_expansions/evaluators/argument_types.py +6 -5
  48. classiq/model_expansions/evaluators/classical_type_inference.py +17 -6
  49. classiq/model_expansions/evaluators/parameter_types.py +26 -13
  50. classiq/model_expansions/evaluators/type_type_match.py +2 -2
  51. classiq/model_expansions/expression_evaluator.py +1 -1
  52. classiq/model_expansions/generative_functions.py +66 -33
  53. classiq/model_expansions/interpreters/base_interpreter.py +27 -19
  54. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +26 -0
  55. classiq/model_expansions/interpreters/generative_interpreter.py +25 -1
  56. classiq/model_expansions/quantum_operations/allocate.py +27 -11
  57. classiq/model_expansions/quantum_operations/assignment_result_processor.py +220 -19
  58. classiq/model_expansions/quantum_operations/bind.py +54 -30
  59. classiq/model_expansions/quantum_operations/block_evaluator.py +42 -0
  60. classiq/model_expansions/quantum_operations/call_emitter.py +14 -12
  61. classiq/model_expansions/quantum_operations/composite_emitter.py +1 -1
  62. classiq/model_expansions/quantum_operations/declarative_call_emitter.py +23 -9
  63. classiq/model_expansions/quantum_operations/emitter.py +21 -8
  64. classiq/model_expansions/quantum_operations/expression_evaluator.py +1 -0
  65. classiq/model_expansions/quantum_operations/handle_evaluator.py +1 -0
  66. classiq/model_expansions/quantum_operations/quantum_function_call.py +4 -3
  67. classiq/model_expansions/scope.py +10 -7
  68. classiq/model_expansions/sympy_conversion/arithmetics.py +18 -0
  69. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +2 -0
  70. classiq/model_expansions/sympy_conversion/sympy_to_python.py +10 -1
  71. classiq/model_expansions/transformers/model_renamer.py +48 -8
  72. classiq/model_expansions/utils/handles_collector.py +1 -1
  73. classiq/model_expansions/visitors/symbolic_param_inference.py +197 -0
  74. classiq/model_expansions/visitors/variable_references.py +45 -9
  75. classiq/qmod/builtins/functions/allocation.py +2 -2
  76. classiq/qmod/builtins/functions/arithmetic.py +14 -12
  77. classiq/qmod/builtins/functions/standard_gates.py +23 -23
  78. classiq/qmod/declaration_inferrer.py +19 -7
  79. classiq/qmod/generative.py +9 -1
  80. classiq/qmod/native/expression_to_qmod.py +4 -0
  81. classiq/qmod/native/pretty_printer.py +8 -3
  82. classiq/qmod/pretty_print/pretty_printer.py +1 -1
  83. classiq/qmod/python_classical_type.py +4 -5
  84. classiq/qmod/qmod_constant.py +15 -7
  85. classiq/qmod/qmod_variable.py +30 -2
  86. classiq/qmod/quantum_function.py +19 -6
  87. classiq/qmod/semantics/lambdas.py +6 -2
  88. classiq/qmod/semantics/validation/main_validation.py +17 -4
  89. classiq/qmod/symbolic.py +8 -19
  90. classiq/qmod/symbolic_expr.py +34 -2
  91. classiq/qmod/write_qmod.py +5 -1
  92. classiq/synthesis.py +17 -31
  93. classiq/visualization.py +35 -0
  94. {classiq-0.74.0.dist-info → classiq-0.76.0.dist-info}/METADATA +1 -1
  95. {classiq-0.74.0.dist-info → classiq-0.76.0.dist-info}/RECORD +96 -91
  96. {classiq-0.74.0.dist-info → classiq-0.76.0.dist-info}/WHEEL +1 -1
@@ -66,8 +66,6 @@ class QuantumSymbol:
66
66
  raise ClassiqExpansionError(
67
67
  f"{self.quantum_type.type_name} is not subscriptable"
68
68
  )
69
- if TYPE_CHECKING:
70
- assert self.quantum_type.length is not None
71
69
  if isinstance(start, int) and isinstance(end, int) and start >= end:
72
70
  raise ClassiqExpansionError(
73
71
  f"{self.quantum_type.type_name} slice '{self.handle}[{start}:{end}]' "
@@ -75,6 +73,7 @@ class QuantumSymbol:
75
73
  )
76
74
  if (isinstance(start, int) and start < 0) or (
77
75
  isinstance(end, int)
76
+ and self.quantum_type.length is not None
78
77
  and self.quantum_type.length.is_constant()
79
78
  and end > self.quantum_type.length_value
80
79
  ):
@@ -100,19 +99,23 @@ class QuantumSymbol:
100
99
  raise ClassiqExpansionError(
101
100
  f"{self.quantum_type.type_name} is not subscriptable"
102
101
  )
103
- if TYPE_CHECKING:
104
- assert self.quantum_type.length is not None
105
102
  if isinstance(index, int) and (
106
103
  index < 0
107
104
  or (
108
- self.quantum_type.length.is_constant()
105
+ self.quantum_type.length is not None
106
+ and self.quantum_type.length.is_constant()
109
107
  and index >= self.quantum_type.length_value
110
108
  )
111
109
  ):
110
+ length_suffix = (
111
+ f" (of length {self.quantum_type.length})"
112
+ if self.quantum_type.length is not None
113
+ else ""
114
+ )
112
115
  raise ClassiqExpansionError(
113
116
  f"Index {index} is out of bounds for "
114
- f"{self.quantum_type.type_name.lower()} {str(self.handle)!r} (of "
115
- f"length {self.quantum_type.length})"
117
+ f"{self.quantum_type.type_name.lower()} {str(self.handle)!r}"
118
+ f"{length_suffix}"
116
119
  )
117
120
  return QuantumSymbol(
118
121
  handle=SubscriptHandleBinding(
@@ -47,3 +47,21 @@ class BitwiseNot(Function):
47
47
  return ~a
48
48
 
49
49
  return None
50
+
51
+
52
+ class RShift(Function):
53
+ @classmethod
54
+ def eval(cls, a: Any, b: Any) -> Optional[int]:
55
+ if isinstance(a, Integer) and isinstance(b, Integer):
56
+ return a >> b
57
+
58
+ return None
59
+
60
+
61
+ class LShift(Function):
62
+ @classmethod
63
+ def eval(cls, a: Any, b: Any) -> Optional[int]:
64
+ if isinstance(a, Integer) and isinstance(b, Integer):
65
+ return a << b
66
+
67
+ return None
@@ -23,6 +23,8 @@ class ExpressionSympyTranslator(ast.NodeTransformer):
23
23
  ast.BitAnd: "BitwiseAnd",
24
24
  ast.BitXor: "BitwiseXor",
25
25
  ast.Div: "do_div",
26
+ ast.RShift: "RShift",
27
+ ast.LShift: "LShift",
26
28
  }
27
29
 
28
30
  UNARY_OPERATORS: dict[type[ast.AST], str] = {
@@ -17,6 +17,9 @@ from sympy.printing.pycode import PythonCodePrinter
17
17
 
18
18
  from classiq.interface.exceptions import ClassiqInternalExpansionError
19
19
  from classiq.interface.generator.expressions.expression_types import ExpressionValue
20
+ from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
21
+ AnyClassicalValue,
22
+ )
20
23
 
21
24
  from classiq.model_expansions.sympy_conversion.arithmetics import LogicalXor
22
25
 
@@ -24,7 +27,9 @@ from classiq.model_expansions.sympy_conversion.arithmetics import LogicalXor
24
27
  def sympy_to_python(
25
28
  value: Any, locals: Optional[dict[str, ExpressionValue]] = None
26
29
  ) -> ExpressionValue:
27
- if isinstance(value, Integer):
30
+ if isinstance(value, AnyClassicalValue):
31
+ pass
32
+ elif isinstance(value, Integer):
28
33
  value = int(value)
29
34
  elif isinstance(value, Float):
30
35
  value = float(value)
@@ -66,6 +71,8 @@ class SympyToQuantumExpressionTranslator(PythonCodePrinter):
66
71
  "BitwiseOr": "|",
67
72
  "BitwiseXor": "^",
68
73
  "LogicalXor": "^",
74
+ "RShift": ">>",
75
+ "LShift": "<<",
69
76
  }
70
77
  UNARY_BITWISE_OPERATORS_MAPPING = {"BitwiseNot": "~"}
71
78
 
@@ -117,6 +124,8 @@ class SympyToBoolExpressionTranslator(SympyToQuantumExpressionTranslator):
117
124
 
118
125
 
119
126
  def translate_sympy_quantum_expression(expr: Basic, preserve_bool_ops: bool) -> str:
127
+ if isinstance(expr, AnyClassicalValue):
128
+ return str(expr)
120
129
  if preserve_bool_ops:
121
130
  return SympyToBoolExpressionTranslator().doprint(expr)
122
131
  else:
@@ -2,8 +2,10 @@ import ast
2
2
  import re
3
3
  from collections.abc import Mapping, Sequence
4
4
  from dataclasses import dataclass
5
+ from functools import cmp_to_key
5
6
  from typing import TypeVar, cast
6
7
 
8
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
7
9
  from classiq.interface.generator.expressions.expression import Expression
8
10
  from classiq.interface.generator.visitor import NodeType
9
11
  from classiq.interface.model.handle_binding import HandleBinding
@@ -23,6 +25,39 @@ def _replace_full_word(pattern: str, substitution: str, target: str) -> str:
23
25
  )
24
26
 
25
27
 
28
+ def _handle_contains_handle(handle: HandleBinding, other_handle: HandleBinding) -> int:
29
+ if str(other_handle) in str(handle) or other_handle.qmod_expr in handle.qmod_expr:
30
+ return 1
31
+ if str(handle) in str(other_handle) or handle.qmod_expr in other_handle.qmod_expr:
32
+ return -1
33
+ return 0
34
+
35
+
36
+ class _ExprNormalizer(ast.NodeTransformer):
37
+ def visit_Call(self, node: ast.Call) -> ast.AST:
38
+ if not isinstance(node.func, ast.Name):
39
+ return self.generic_visit(node)
40
+ if node.func.id == "get_field":
41
+ if (
42
+ len(node.args) != 2
43
+ or not isinstance(node.args[1], ast.Constant)
44
+ or not isinstance(node.args[1].value, str)
45
+ ):
46
+ raise ClassiqInternalExpansionError("Unexpected 'get_field' arguments")
47
+ return ast.Attribute(
48
+ value=self.visit(node.args[0]), attr=node.args[1].value
49
+ )
50
+ if node.func.id == "do_subscript":
51
+ if len(node.args) != 2:
52
+ raise ClassiqInternalExpansionError(
53
+ "Unexpected 'do_subscript' arguments"
54
+ )
55
+ return ast.Subscript(
56
+ value=self.visit(node.args[0]), slice=self.visit(node.args[1])
57
+ )
58
+ return self.generic_visit(node)
59
+
60
+
26
61
  @dataclass(frozen=True)
27
62
  class HandleRenaming:
28
63
  source_handle: HandleBinding
@@ -39,26 +74,29 @@ SymbolRenaming = Mapping[HandleBinding, Sequence[HandleRenaming]]
39
74
  def _rewrite_expression(
40
75
  symbol_mapping: SymbolRenaming, expression: Expression
41
76
  ) -> Expression:
77
+ normalized_expr = _ExprNormalizer().visit(ast.parse(expression.expr))
42
78
  vrc = VarRefCollector(
43
79
  ignore_duplicated_handles=True, ignore_sympy_symbols=True, unevaluated=True
44
80
  )
45
- vrc.visit(ast.parse(expression.expr))
81
+ vrc.visit(normalized_expr)
46
82
 
47
83
  handle_names = {
48
84
  part.source_handle: part.target_var_handle
49
85
  for parts in symbol_mapping.values()
50
86
  for part in parts
51
87
  }
52
- new_expr_str = expression.expr
53
- for handle in vrc.var_handles:
88
+ new_expr_str = ast.unparse(normalized_expr)
89
+ sorted_handles = sorted(
90
+ vrc.var_handles,
91
+ key=cmp_to_key( # type:ignore[misc]
92
+ lambda handle, other_handle: _handle_contains_handle(other_handle, handle)
93
+ ),
94
+ )
95
+ for handle in sorted_handles:
54
96
  new_handle = handle.collapse()
55
97
  for handle_to_replace, replacement in handle_names.items():
56
98
  new_handle = new_handle.replace_prefix(handle_to_replace, replacement)
57
99
  new_expr_str = _replace_full_word(str(handle), str(new_handle), new_expr_str)
58
- if handle.qmod_expr != str(handle):
59
- new_expr_str = _replace_full_word(
60
- str(handle.qmod_expr), str(new_handle.qmod_expr), new_expr_str
61
- )
62
100
 
63
101
  new_expr = Expression(expr=new_expr_str)
64
102
  new_expr._evaluated_expr = expression._evaluated_expr
@@ -92,7 +130,9 @@ class _ReplaceSplitVarsExpressions(ModelTransformer):
92
130
  ) -> QuantumExpressionOperation:
93
131
  op = cast(QuantumExpressionOperation, self.generic_visit(op))
94
132
  previous_var_handles = list(op._var_handles)
95
- op._var_handles = self.visit(op._var_handles)
133
+ op._var_handles = _ReplaceSplitVarsHandles(self._symbol_mapping).visit(
134
+ op._var_handles
135
+ )
96
136
  op._var_types = {
97
137
  new_handle.name: op._var_types.get(
98
138
  new_handle.name, op._var_types[previous_handle.name]
@@ -18,7 +18,7 @@ class _HandlesCollector(Visitor):
18
18
  self.handles.append(handle)
19
19
 
20
20
  def visit_Expression(self, expression: Expression) -> None:
21
- vrc = VarRefCollector(ignore_duplicated_handles=True)
21
+ vrc = VarRefCollector(ignore_duplicated_handles=True, unevaluated=True)
22
22
  vrc.visit(ast.parse(expression.expr))
23
23
  self.handles.extend(vrc.var_handles)
24
24
 
@@ -0,0 +1,197 @@
1
+ import ast
2
+ from collections.abc import Iterator, Mapping, Sequence
3
+ from contextlib import contextmanager
4
+ from itertools import chain, zip_longest
5
+ from typing import Optional, cast
6
+
7
+ from classiq.interface.generator.expressions.atomic_expression_functions import (
8
+ CLASSICAL_ATTRIBUTES,
9
+ )
10
+ from classiq.interface.generator.expressions.expression import Expression
11
+ from classiq.interface.generator.functions.classical_type import ClassicalType
12
+ from classiq.interface.generator.functions.type_name import TypeName
13
+ from classiq.interface.helpers.pydantic_model_helpers import nameables_to_dict
14
+ from classiq.interface.model.classical_parameter_declaration import (
15
+ AnonClassicalParameterDeclaration,
16
+ )
17
+ from classiq.interface.model.handle_binding import FieldHandleBinding, HandleBinding
18
+ from classiq.interface.model.model_visitor import ModelVisitor
19
+ from classiq.interface.model.native_function_definition import NativeFunctionDefinition
20
+ from classiq.interface.model.quantum_function_call import ArgValue, QuantumFunctionCall
21
+ from classiq.interface.model.quantum_function_declaration import (
22
+ AnonPositionalArg,
23
+ AnonQuantumFunctionDeclaration,
24
+ AnonQuantumOperandDeclaration,
25
+ NamedParamsQuantumFunctionDeclaration,
26
+ QuantumOperandDeclaration,
27
+ )
28
+ from classiq.interface.model.quantum_lambda_function import (
29
+ OperandIdentifier,
30
+ QuantumLambdaFunction,
31
+ )
32
+
33
+ from classiq.model_expansions.visitors.variable_references import VarRefCollector
34
+
35
+
36
+ def _get_expressions(arg: ArgValue) -> list[Expression]:
37
+ if isinstance(arg, Expression):
38
+ return [arg]
39
+ if isinstance(arg, HandleBinding):
40
+ return arg.expressions()
41
+ if isinstance(arg, OperandIdentifier):
42
+ return [arg.index]
43
+ if isinstance(arg, list):
44
+ return list(chain.from_iterable(_get_expressions(item) for item in arg))
45
+ return []
46
+
47
+
48
+ def _get_param_expressions(param: AnonPositionalArg) -> list[Expression]:
49
+ if isinstance(param, AnonClassicalParameterDeclaration):
50
+ return param.classical_type.expressions
51
+ if isinstance(param, AnonQuantumOperandDeclaration):
52
+ return list(
53
+ chain.from_iterable(
54
+ _get_param_expressions(nested_param)
55
+ for nested_param in param.positional_arg_declarations
56
+ )
57
+ )
58
+ return param.quantum_type.expressions
59
+
60
+
61
+ class SymbolicParamInference(ModelVisitor):
62
+ def __init__(
63
+ self,
64
+ functions: list[NativeFunctionDefinition],
65
+ additional_signatures: (
66
+ list[NamedParamsQuantumFunctionDeclaration] | None
67
+ ) = None,
68
+ ) -> None:
69
+ self._functions = nameables_to_dict(functions)
70
+ self._additional_signatures = (
71
+ {}
72
+ if additional_signatures is None
73
+ else nameables_to_dict(additional_signatures)
74
+ )
75
+ self._inferred_funcs: set[str] = set()
76
+ self._call_stack: list[str] = []
77
+ self._scope: Mapping[str, ClassicalType] = {}
78
+ self._scope_operands: dict[str, QuantumOperandDeclaration] = {}
79
+
80
+ def infer(self) -> None:
81
+ for func in self._functions.values():
82
+ self._infer_func_params(func)
83
+
84
+ def _is_recursive_call(self, func: str) -> bool:
85
+ return func in self._call_stack
86
+
87
+ @contextmanager
88
+ def function_context(
89
+ self,
90
+ func_name: Optional[str],
91
+ scope: Mapping[str, ClassicalType],
92
+ scope_operands: dict[str, QuantumOperandDeclaration],
93
+ ) -> Iterator[None]:
94
+ if func_name is not None:
95
+ self._call_stack.append(func_name)
96
+ prev_scope = self._scope
97
+ self._scope = scope
98
+ prev_scope_ops = self._scope_operands
99
+ self._scope_operands = scope_operands
100
+ yield
101
+ self._scope = prev_scope
102
+ self._scope_operands = prev_scope_ops
103
+ if func_name is not None:
104
+ self._call_stack.pop()
105
+
106
+ def _infer_func_params(self, func: NativeFunctionDefinition) -> None:
107
+ if func.name in self._inferred_funcs:
108
+ return
109
+ scope = {param.name: param.classical_type for param in func.param_decls}
110
+ scope_operands = func.operand_declarations_dict
111
+ with self.function_context(func.name, scope, scope_operands):
112
+ for param in func.positional_arg_declarations:
113
+ for expr in _get_param_expressions(param):
114
+ self._process_compile_time_expression(expr.expr)
115
+ self._set_enums_generative(func)
116
+ 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
+ def visit_QuantumLambdaFunction(self, func: QuantumLambdaFunction) -> None:
130
+ func.set_op_decl(func.func_decl.model_copy(deep=True))
131
+ scope = dict(self._scope) | {
132
+ cast(str, param.name): param.classical_type
133
+ for param in func.named_func_decl.param_decls
134
+ }
135
+ scope_operands = self._scope_operands | nameables_to_dict(
136
+ cast(
137
+ Sequence[QuantumOperandDeclaration],
138
+ func.named_func_decl.operand_declarations,
139
+ )
140
+ )
141
+ with self.function_context(None, scope, scope_operands):
142
+ self._set_enums_generative(func.named_func_decl)
143
+ self.visit(func.body)
144
+
145
+ def visit_QuantumFunctionCall(self, call: QuantumFunctionCall) -> None:
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
+ params = self._get_params(call)
151
+ for param, arg in zip_longest(params, call.positional_args):
152
+ if (
153
+ not isinstance(param, AnonClassicalParameterDeclaration)
154
+ or param.classical_type.is_generative
155
+ ):
156
+ self._process_compile_time_expressions(arg)
157
+ else:
158
+ for expr in _get_expressions(arg):
159
+ self._process_nested_compile_time_expression(expr.expr)
160
+ self.generic_visit(call)
161
+
162
+ def _get_params(self, call: QuantumFunctionCall) -> Sequence[AnonPositionalArg]:
163
+ name = call.func_name
164
+ if name in self._scope_operands:
165
+ return self._scope_operands[name].positional_arg_declarations
166
+ elif name in self._functions:
167
+ func = self._functions[name]
168
+ self._infer_func_params(func)
169
+ return func.positional_arg_declarations
170
+ elif name in self._additional_signatures:
171
+ return self._additional_signatures[name].positional_arg_declarations
172
+ return call.func_decl.positional_arg_declarations
173
+
174
+ def _process_compile_time_expressions(self, arg: ArgValue) -> None:
175
+ for expr in _get_expressions(arg):
176
+ self._process_compile_time_expression(expr.expr)
177
+
178
+ def _process_compile_time_expression(self, expr: str) -> None:
179
+ vrc = VarRefCollector(
180
+ ignore_duplicated_handles=True, ignore_sympy_symbols=True, unevaluated=True
181
+ )
182
+ vrc.visit(ast.parse(expr))
183
+ for handle in vrc.var_handles:
184
+ if handle.name in self._scope and (
185
+ not isinstance(handle, FieldHandleBinding)
186
+ or handle.field not in CLASSICAL_ATTRIBUTES
187
+ ):
188
+ self._scope[handle.name].set_generative()
189
+
190
+ def _process_nested_compile_time_expression(self, expr: str) -> None:
191
+ vrc = VarRefCollector(
192
+ ignore_duplicated_handles=True, ignore_sympy_symbols=True, unevaluated=True
193
+ )
194
+ vrc.visit(ast.parse(expr))
195
+ for handle in vrc.var_handles:
196
+ for nested_expr in handle.expressions():
197
+ self._process_compile_time_expression(nested_expr.expr)
@@ -95,16 +95,25 @@ class VarRefCollector(ast.NodeVisitor):
95
95
  def visit_Attribute(self, node: ast.Attribute) -> Optional[FieldHandleBinding]:
96
96
  return self._get_field_handle(node.value, node.attr)
97
97
 
98
- def visit_Call(self, node: ast.Call) -> Optional[FieldHandleBinding]:
99
- if (
100
- not isinstance(node.func, ast.Name)
101
- or node.func.id != "get_field"
102
- or len(node.args) != 2
103
- or not isinstance(node.args[1], ast.Constant)
104
- or not isinstance(node.args[1].value, str)
105
- ):
98
+ def visit_Call(self, node: ast.Call) -> Optional[HandleBinding]:
99
+ if not isinstance(node.func, ast.Name):
106
100
  return self.generic_visit(node)
107
- return self._get_field_handle(node.args[0], node.args[1].value)
101
+ if node.func.id == "get_field":
102
+ if (
103
+ len(node.args) != 2
104
+ or not isinstance(node.args[1], ast.Constant)
105
+ or not isinstance(node.args[1].value, str)
106
+ ):
107
+ raise ClassiqInternalExpansionError("Unexpected 'get_field' arguments")
108
+ return self._get_field_handle(node.args[0], node.args[1].value)
109
+ if node.func.id == "do_subscript":
110
+ if len(node.args) != 2:
111
+ raise ClassiqInternalExpansionError(
112
+ "Unexpected 'do_subscript' arguments"
113
+ )
114
+ self.visit(node.args[1])
115
+ return self._get_subscript_handle(node.args[0], node.args[1])
116
+ return self.generic_visit(node)
108
117
 
109
118
  def _get_field_handle(
110
119
  self, subject: ast.expr, field: str
@@ -121,6 +130,33 @@ class VarRefCollector(ast.NodeVisitor):
121
130
  self._var_handles[handle] = True
122
131
  return handle
123
132
 
133
+ def _get_subscript_handle(
134
+ self, subject: ast.expr, subscript: ast.expr
135
+ ) -> Optional[HandleBinding]:
136
+ with self.set_nested():
137
+ base_handle = self.visit(subject)
138
+ if base_handle is None:
139
+ return None
140
+ handle: HandleBinding
141
+ if isinstance(subscript, ast.Slice):
142
+ if subscript.lower is None or subscript.upper is None:
143
+ raise ClassiqExpansionError(
144
+ f"{str(base_handle)!r} slice must specify both lower and upper bounds"
145
+ )
146
+ handle = SlicedHandleBinding(
147
+ base_handle=base_handle,
148
+ start=Expression(expr=ast.unparse(subscript.lower)),
149
+ end=Expression(expr=ast.unparse(subscript.upper)),
150
+ )
151
+ else:
152
+ handle = SubscriptHandleBinding(
153
+ base_handle=base_handle,
154
+ index=Expression(expr=ast.unparse(subscript)),
155
+ )
156
+ if not self._is_nested:
157
+ self._var_handles[handle] = True
158
+ return handle
159
+
124
160
  def visit_Name(self, node: ast.Name) -> Optional[HandleBinding]:
125
161
  if not self._ignore_sympy_symbols and node.id in set(
126
162
  SYMPY_SUPPORTED_EXPRESSIONS
@@ -2,11 +2,11 @@ from typing import Literal
2
2
 
3
3
  from classiq.qmod.qfunc import qfunc
4
4
  from classiq.qmod.qmod_parameter import CArray, CReal
5
- from classiq.qmod.qmod_variable import Input, Output, QArray, QBit
5
+ from classiq.qmod.qmod_variable import Input, Output, QArray, QBit, QFree
6
6
 
7
7
 
8
8
  @qfunc(external=True)
9
- def free(in_: Input[QArray[QBit]]) -> None:
9
+ def free(in_: QFree[Input[QArray[QBit]]]) -> None:
10
10
  """
11
11
  [Qmod core-library function]
12
12
 
@@ -2,7 +2,7 @@ from typing import Literal
2
2
 
3
3
  from classiq.qmod.qfunc import qfunc
4
4
  from classiq.qmod.qmod_parameter import CArray, CBool, CReal
5
- from classiq.qmod.qmod_variable import Output, QArray, QBit, QNum
5
+ from classiq.qmod.qmod_variable import Const, Output, QArray, QBit, QFree, QNum
6
6
 
7
7
 
8
8
  @qfunc(external=True)
@@ -24,13 +24,15 @@ def unitary(
24
24
 
25
25
  @qfunc(external=True)
26
26
  def add(
27
- left: QNum,
28
- right: QNum,
29
- result: Output[
30
- QNum[
31
- Literal["result_size"],
32
- Literal["result_is_signed"],
33
- Literal["result_fraction_places"],
27
+ left: Const[QNum],
28
+ right: Const[QNum],
29
+ result: QFree[
30
+ Output[
31
+ QNum[
32
+ Literal["result_size"],
33
+ Literal["result_is_signed"],
34
+ Literal["result_fraction_places"],
35
+ ]
34
36
  ]
35
37
  ],
36
38
  result_size: CReal,
@@ -41,20 +43,20 @@ def add(
41
43
 
42
44
 
43
45
  @qfunc(external=True)
44
- def modular_add(left: QArray[QBit], right: QArray[QBit]) -> None:
46
+ def modular_add(left: Const[QArray[QBit]], right: QFree[QArray[QBit]]) -> None:
45
47
  pass
46
48
 
47
49
 
48
50
  @qfunc(external=True)
49
- def modular_add_constant(left: CReal, right: QNum) -> None:
51
+ def modular_add_constant(left: CReal, right: QFree[QNum]) -> None:
50
52
  pass
51
53
 
52
54
 
53
55
  @qfunc(external=True)
54
- def integer_xor(left: QArray[QBit], right: QArray[QBit]) -> None:
56
+ def integer_xor(left: Const[QArray[QBit]], right: QFree[QArray[QBit]]) -> None:
55
57
  pass
56
58
 
57
59
 
58
60
  @qfunc(external=True)
59
- def real_xor_constant(left: CReal, right: QNum) -> None:
61
+ def real_xor_constant(left: CReal, right: QFree[QNum]) -> None:
60
62
  pass