classiq 0.75.0__py3-none-any.whl → 0.77.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 (101) 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 +15 -7
  4. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +11 -1
  5. classiq/applications/combinatorial_optimization/combinatorial_problem.py +8 -7
  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 +14 -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 +16 -2
  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 +45 -21
  22. classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
  23. classiq/interface/generator/expressions/proxies/classical/utils.py +12 -11
  24. classiq/interface/generator/expressions/proxies/quantum/qmod_qarray_proxy.py +6 -15
  25. classiq/interface/generator/expressions/proxies/quantum/qmod_qscalar_proxy.py +22 -6
  26. classiq/interface/generator/expressions/proxies/quantum/qmod_sized_proxy.py +9 -4
  27. classiq/interface/generator/expressions/sympy_supported_expressions.py +1 -0
  28. classiq/interface/generator/functions/classical_type.py +6 -1
  29. classiq/interface/generator/functions/type_name.py +7 -2
  30. classiq/interface/generator/functions/type_qualifier.py +15 -0
  31. classiq/interface/generator/model/preferences/preferences.py +7 -0
  32. classiq/interface/generator/quantum_program.py +5 -19
  33. classiq/interface/helpers/backward_compatibility.py +9 -0
  34. classiq/interface/helpers/datastructures.py +6 -0
  35. classiq/interface/model/handle_binding.py +8 -0
  36. classiq/interface/model/model.py +3 -6
  37. classiq/interface/model/port_declaration.py +1 -2
  38. classiq/interface/model/quantum_function_call.py +31 -1
  39. classiq/interface/model/quantum_lambda_function.py +2 -1
  40. classiq/interface/model/quantum_statement.py +14 -1
  41. classiq/interface/server/routes.py +6 -0
  42. classiq/interface/source_reference.py +7 -2
  43. classiq/model_expansions/atomic_expression_functions_defs.py +62 -19
  44. classiq/model_expansions/capturing/captured_vars.py +18 -6
  45. classiq/model_expansions/closure.py +5 -0
  46. classiq/model_expansions/evaluators/arg_type_match.py +2 -2
  47. classiq/model_expansions/evaluators/argument_types.py +3 -3
  48. classiq/model_expansions/evaluators/classical_expression.py +9 -9
  49. classiq/model_expansions/evaluators/classical_type_inference.py +17 -6
  50. classiq/model_expansions/evaluators/parameter_types.py +45 -24
  51. classiq/model_expansions/expression_evaluator.py +21 -12
  52. classiq/model_expansions/function_builder.py +45 -0
  53. classiq/model_expansions/generative_functions.py +62 -35
  54. classiq/model_expansions/interpreters/base_interpreter.py +32 -7
  55. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +9 -3
  56. classiq/model_expansions/interpreters/generative_interpreter.py +17 -5
  57. classiq/model_expansions/quantum_operations/allocate.py +8 -3
  58. classiq/model_expansions/quantum_operations/assignment_result_processor.py +221 -20
  59. classiq/model_expansions/quantum_operations/bind.py +54 -30
  60. classiq/model_expansions/quantum_operations/block_evaluator.py +42 -0
  61. classiq/model_expansions/quantum_operations/call_emitter.py +35 -18
  62. classiq/model_expansions/quantum_operations/composite_emitter.py +1 -1
  63. classiq/model_expansions/quantum_operations/declarative_call_emitter.py +23 -9
  64. classiq/model_expansions/quantum_operations/emitter.py +21 -9
  65. classiq/model_expansions/quantum_operations/quantum_function_call.py +4 -3
  66. classiq/model_expansions/scope.py +63 -10
  67. classiq/model_expansions/sympy_conversion/arithmetics.py +18 -0
  68. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +2 -0
  69. classiq/model_expansions/sympy_conversion/sympy_to_python.py +10 -1
  70. classiq/model_expansions/transformers/model_renamer.py +45 -7
  71. classiq/model_expansions/utils/handles_collector.py +1 -1
  72. classiq/model_expansions/visitors/symbolic_param_inference.py +3 -3
  73. classiq/model_expansions/visitors/variable_references.py +45 -9
  74. classiq/open_library/functions/lookup_table.py +1 -1
  75. classiq/open_library/functions/state_preparation.py +1 -1
  76. classiq/qmod/builtins/functions/allocation.py +2 -2
  77. classiq/qmod/builtins/functions/arithmetic.py +14 -12
  78. classiq/qmod/builtins/functions/standard_gates.py +23 -23
  79. classiq/qmod/create_model_function.py +21 -3
  80. classiq/qmod/declaration_inferrer.py +19 -7
  81. classiq/qmod/generative.py +9 -1
  82. classiq/qmod/global_declarative_switch.py +19 -0
  83. classiq/qmod/native/expression_to_qmod.py +4 -0
  84. classiq/qmod/native/pretty_printer.py +12 -3
  85. classiq/qmod/pretty_print/pretty_printer.py +5 -1
  86. classiq/qmod/python_classical_type.py +4 -5
  87. classiq/qmod/qfunc.py +31 -23
  88. classiq/qmod/qmod_constant.py +15 -7
  89. classiq/qmod/qmod_variable.py +7 -1
  90. classiq/qmod/quantum_expandable.py +29 -1
  91. classiq/qmod/quantum_function.py +45 -25
  92. classiq/qmod/semantics/lambdas.py +6 -2
  93. classiq/qmod/semantics/validation/main_validation.py +17 -4
  94. classiq/qmod/symbolic.py +8 -19
  95. classiq/qmod/symbolic_expr.py +26 -0
  96. classiq/qmod/write_qmod.py +36 -10
  97. classiq/synthesis.py +24 -37
  98. classiq/visualization.py +35 -0
  99. {classiq-0.75.0.dist-info → classiq-0.77.0.dist-info}/METADATA +1 -1
  100. {classiq-0.75.0.dist-info → classiq-0.77.0.dist-info}/RECORD +101 -96
  101. {classiq-0.75.0.dist-info → classiq-0.77.0.dist-info}/WHEEL +0 -0
@@ -1,5 +1,5 @@
1
1
  from itertools import chain
2
- from typing import TYPE_CHECKING, Generic
2
+ from typing import TYPE_CHECKING, Generic, Optional
3
3
 
4
4
  from classiq.interface.generator.functions.port_declaration import (
5
5
  PortDeclarationDirection,
@@ -34,13 +34,24 @@ class DeclarativeCallEmitter(
34
34
 
35
35
  if self._is_function_purely_declarative(
36
36
  function
37
- ) and self._are_args_purely_declarative(args):
37
+ ) and self._are_args_purely_declarative(args, function.name):
38
38
  self._interpreter.add_purely_declarative_function(function)
39
39
  return False
40
40
 
41
41
  return True
42
42
 
43
- def _is_function_purely_declarative(self, function: FunctionClosure) -> bool:
43
+ def _is_function_purely_declarative(
44
+ self, function: FunctionClosure, seen_funcs: Optional[set[str]] = None
45
+ ) -> bool:
46
+ if seen_funcs is None:
47
+ seen_funcs = set()
48
+ if function.name in seen_funcs:
49
+ return True
50
+ seen_funcs.add(function.name)
51
+
52
+ if function.is_atomic:
53
+ return True
54
+
44
55
  if function.name not in QMODULE.native_defs:
45
56
  return False
46
57
 
@@ -56,9 +67,9 @@ class DeclarativeCallEmitter(
56
67
  return False
57
68
 
58
69
  dependencies = QMODULE.function_dependencies[function.name]
59
- return self._are_identifiers_purely_declarative(dependencies)
70
+ return self._are_identifiers_purely_declarative(dependencies, seen_funcs)
60
71
 
61
- def _are_args_purely_declarative(self, args: list[Evaluated]) -> bool:
72
+ def _are_args_purely_declarative(self, args: list[Evaluated], caller: str) -> bool:
62
73
  values = [arg.value for arg in args]
63
74
  function_inputs: list[FunctionClosure] = list(
64
75
  chain.from_iterable(
@@ -78,10 +89,13 @@ class DeclarativeCallEmitter(
78
89
  if any(func.is_lambda for func in function_inputs):
79
90
  return False
80
91
  dependencies = [func.name for func in function_inputs if not func.is_lambda]
81
- return self._are_identifiers_purely_declarative(dependencies)
92
+ return self._are_identifiers_purely_declarative(dependencies, {caller})
82
93
 
83
- def _are_identifiers_purely_declarative(self, dependencies: list[str]) -> bool:
84
- return not any(
85
- isinstance(self._current_scope[dep].value, GenerativeClosure)
94
+ def _are_identifiers_purely_declarative(
95
+ self, dependencies: list[str], seen_funcs: set[str]
96
+ ) -> bool:
97
+ return all(
98
+ self._is_function_purely_declarative(self._current_scope[dep].value)
86
99
  for dep in dependencies
100
+ if seen_funcs is None or dep not in seen_funcs
87
101
  )
@@ -15,6 +15,11 @@ from classiq.interface.debug_info.debug_info import (
15
15
  DebugInfoCollection,
16
16
  )
17
17
  from classiq.interface.exceptions import ClassiqInternalExpansionError
18
+ from classiq.interface.generator.expressions.atomic_expression_functions import (
19
+ CLASSICAL_ATTRIBUTES,
20
+ SUPPORTED_CLASSIQ_BUILTIN_FUNCTIONS,
21
+ SUPPORTED_PYTHON_BUILTIN_FUNCTIONS,
22
+ )
18
23
  from classiq.interface.generator.expressions.evaluated_expression import (
19
24
  EvaluatedExpression,
20
25
  )
@@ -30,7 +35,11 @@ from classiq.interface.generator.functions.port_declaration import (
30
35
  PortDeclarationDirection,
31
36
  )
32
37
  from classiq.interface.helpers.pydantic_model_helpers import nameables_to_dict
33
- from classiq.interface.model.handle_binding import HandleBinding, NestedHandleBinding
38
+ from classiq.interface.model.handle_binding import (
39
+ FieldHandleBinding,
40
+ HandleBinding,
41
+ NestedHandleBinding,
42
+ )
34
43
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
35
44
  from classiq.interface.model.quantum_function_declaration import (
36
45
  NamedParamsQuantumFunctionDeclaration,
@@ -172,12 +181,7 @@ class Emitter(Generic[QuantumStatementT], ABC):
172
181
  self._capture_classical_var(var_name, var_type)
173
182
 
174
183
  def _update_captured_vars(self, op: QuantumOperation) -> None:
175
- handles = (
176
- [(handle, PortDeclarationDirection.Input) for handle in op.inputs]
177
- + [(handle, PortDeclarationDirection.Output) for handle in op.outputs]
178
- + [(handle, PortDeclarationDirection.Inout) for handle in op.inouts]
179
- )
180
- for handle, direction in handles:
184
+ for handle, direction in op.handles_with_directions:
181
185
  self._capture_handle(handle, direction)
182
186
 
183
187
  def _capture_handle(
@@ -220,9 +224,17 @@ class Emitter(Generic[QuantumStatementT], ABC):
220
224
  handles = dict.fromkeys(
221
225
  handle
222
226
  for handle in vrc.var_handles
223
- if isinstance(self._current_scope[handle.name].value, QuantumSymbol)
227
+ if handle.name
228
+ not in SUPPORTED_PYTHON_BUILTIN_FUNCTIONS
229
+ | SUPPORTED_CLASSIQ_BUILTIN_FUNCTIONS
230
+ and isinstance(self._current_scope[handle.name].value, QuantumSymbol)
224
231
  )
225
- return [self._interpreter.evaluate(handle).value for handle in handles]
232
+ return [
233
+ self._interpreter.evaluate(handle).value
234
+ for handle in handles
235
+ if not isinstance(handle, FieldHandleBinding)
236
+ or handle.field not in CLASSICAL_ATTRIBUTES
237
+ ]
226
238
 
227
239
  def _get_classical_vars_in_expression(
228
240
  self, expr: Expression
@@ -49,13 +49,14 @@ class QuantumFunctionCallEmitter(CallEmitter[QuantumFunctionCall]):
49
49
  raise ClassiqInternalExpansionError(
50
50
  f"Unexpected lambda list type {type(funcs).__name__!r}"
51
51
  )
52
- self._interpreter.emit(self._create_recursive_if(call, index, len(funcs)))
52
+ for stmt in self._create_recursive_if(call, index, len(funcs)):
53
+ self._interpreter.emit(stmt)
53
54
  return True
54
55
 
55
56
  @staticmethod
56
57
  def _create_recursive_if(
57
58
  call: QuantumFunctionCall, index: ClassicalScalarProxy, num_funcs: int
58
- ) -> QuantumStatement:
59
+ ) -> list[QuantumStatement]:
59
60
  if TYPE_CHECKING:
60
61
  assert isinstance(call.function, OperandIdentifier)
61
62
  stmt: list[QuantumStatement] = []
@@ -74,7 +75,7 @@ class QuantumFunctionCallEmitter(CallEmitter[QuantumFunctionCall]):
74
75
  else_=stmt,
75
76
  )
76
77
  ]
77
- return stmt[0]
78
+ return stmt
78
79
 
79
80
 
80
81
  class DeclarativeQuantumFunctionCallEmitter(
@@ -22,7 +22,9 @@ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_insta
22
22
  from classiq.interface.generator.functions.type_name import TypeName
23
23
  from classiq.interface.model.handle_binding import (
24
24
  FieldHandleBinding,
25
+ GeneralHandle,
25
26
  HandleBinding,
27
+ HandlesList,
26
28
  SlicedHandleBinding,
27
29
  SubscriptHandleBinding,
28
30
  )
@@ -39,10 +41,17 @@ T = TypeVar("T")
39
41
 
40
42
 
41
43
  @dataclass(frozen=True)
42
- class QuantumSymbol:
43
- handle: HandleBinding
44
+ class QuantumVariable:
44
45
  quantum_type: QuantumType
45
46
 
47
+ def emit(self) -> GeneralHandle:
48
+ raise NotImplementedError
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class QuantumSymbol(QuantumVariable):
53
+ handle: HandleBinding
54
+
46
55
  @property
47
56
  def is_subscript(self) -> bool:
48
57
  return isinstance(self.handle, (SubscriptHandleBinding, SlicedHandleBinding))
@@ -66,8 +75,6 @@ class QuantumSymbol:
66
75
  raise ClassiqExpansionError(
67
76
  f"{self.quantum_type.type_name} is not subscriptable"
68
77
  )
69
- if TYPE_CHECKING:
70
- assert self.quantum_type.length is not None
71
78
  if isinstance(start, int) and isinstance(end, int) and start >= end:
72
79
  raise ClassiqExpansionError(
73
80
  f"{self.quantum_type.type_name} slice '{self.handle}[{start}:{end}]' "
@@ -75,6 +82,7 @@ class QuantumSymbol:
75
82
  )
76
83
  if (isinstance(start, int) and start < 0) or (
77
84
  isinstance(end, int)
85
+ and self.quantum_type.length is not None
78
86
  and self.quantum_type.length.is_constant()
79
87
  and end > self.quantum_type.length_value
80
88
  ):
@@ -100,19 +108,23 @@ class QuantumSymbol:
100
108
  raise ClassiqExpansionError(
101
109
  f"{self.quantum_type.type_name} is not subscriptable"
102
110
  )
103
- if TYPE_CHECKING:
104
- assert self.quantum_type.length is not None
105
111
  if isinstance(index, int) and (
106
112
  index < 0
107
113
  or (
108
- self.quantum_type.length.is_constant()
114
+ self.quantum_type.length is not None
115
+ and self.quantum_type.length.is_constant()
109
116
  and index >= self.quantum_type.length_value
110
117
  )
111
118
  ):
119
+ length_suffix = (
120
+ f" (of length {self.quantum_type.length})"
121
+ if self.quantum_type.length is not None
122
+ else ""
123
+ )
112
124
  raise ClassiqExpansionError(
113
125
  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})"
126
+ f"{self.quantum_type.type_name.lower()} {str(self.handle)!r}"
127
+ f"{length_suffix}"
116
128
  )
117
129
  return QuantumSymbol(
118
130
  handle=SubscriptHandleBinding(
@@ -137,6 +149,47 @@ class QuantumSymbol:
137
149
  for field_name, field_type in quantum_type.fields.items()
138
150
  }
139
151
 
152
+ def __str__(self) -> str:
153
+ return str(self.handle)
154
+
155
+
156
+ @dataclass(frozen=True)
157
+ class QuantumSymbolList(QuantumVariable):
158
+ handles: list[HandleBinding]
159
+
160
+ @staticmethod
161
+ def from_symbols(
162
+ symbols: list[Union[QuantumSymbol, "QuantumSymbolList"]],
163
+ ) -> "QuantumSymbolList":
164
+ handles = list(
165
+ itertools.chain.from_iterable(
166
+ (
167
+ symbol.handles
168
+ if isinstance(symbol, QuantumSymbolList)
169
+ else [symbol.handle]
170
+ )
171
+ for symbol in symbols
172
+ )
173
+ )
174
+ if len(handles) == 0:
175
+ raise ClassiqExpansionError("Empty concatenation expression")
176
+ length: Optional[Expression]
177
+ if any(not symbol.quantum_type.has_size_in_bits for symbol in symbols):
178
+ length = None
179
+ else:
180
+ length = Expression(
181
+ expr=str(sum(symbol.quantum_type.size_in_bits for symbol in symbols))
182
+ )
183
+ return QuantumSymbolList(
184
+ handles=handles, quantum_type=QuantumBitvector(length=length)
185
+ )
186
+
187
+ def emit(self) -> HandlesList:
188
+ return HandlesList(handles=self.handles)
189
+
190
+ def __str__(self) -> str:
191
+ return str(self.handles)
192
+
140
193
 
141
194
  @singledispatch
142
195
  def evaluated_to_str(value: Any) -> str:
@@ -180,7 +233,7 @@ class Evaluated: # FIXME: Merge with EvaluatedExpression if possible
180
233
  def emit(self) -> ArgValue:
181
234
  from classiq.model_expansions.closure import FunctionClosure
182
235
 
183
- if isinstance(self.value, (QuantumSymbol, FunctionClosure)):
236
+ if isinstance(self.value, (QuantumVariable, FunctionClosure)):
184
237
  return self.value.emit()
185
238
  if isinstance(self.value, list) and all(
186
239
  isinstance(item, FunctionClosure) for item in self.value
@@ -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
@@ -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
 
@@ -62,9 +62,9 @@ class SymbolicParamInference(ModelVisitor):
62
62
  def __init__(
63
63
  self,
64
64
  functions: list[NativeFunctionDefinition],
65
- additional_signatures: (
66
- list[NamedParamsQuantumFunctionDeclaration] | None
67
- ) = None,
65
+ additional_signatures: Optional[
66
+ list[NamedParamsQuantumFunctionDeclaration]
67
+ ] = None,
68
68
  ) -> None:
69
69
  self._functions = nameables_to_dict(functions)
70
70
  self._additional_signatures = (
@@ -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
@@ -39,7 +39,7 @@ def span_lookup_table(func: RealFunction, *targets: QNum) -> QNum:
39
39
  The quantum result of applying func to targets
40
40
 
41
41
  Notes:
42
- Must be called inside a generative function (`@qfunc(generative=True)`)
42
+ Must be called inside a generative function (`@qfunc`)
43
43
  """
44
44
  if len(targets) == 0:
45
45
  raise ClassiqValueError("No targets specified")
@@ -334,7 +334,7 @@ def _classical_hadamard_transform(arr: list[float]) -> np.ndarray:
334
334
  return 1 / np.sqrt(len(arr)) * np.array(sympy.fwht(np.array(arr)))
335
335
 
336
336
 
337
- @qfunc(generative=True)
337
+ @qfunc
338
338
  def _load_phases(
339
339
  phases: list[float],
340
340
  target: QArray[QBit, Literal["log(get_field(phases, 'len'), 2)"]],
@@ -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