classiq 0.58.0__py3-none-any.whl → 0.59.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 (40) hide show
  1. classiq/_internals/api_wrapper.py +8 -3
  2. classiq/_internals/jobs.py +3 -5
  3. classiq/execution/execution_session.py +36 -20
  4. classiq/executor.py +2 -1
  5. classiq/interface/_version.py +1 -1
  6. classiq/interface/generator/arith/arithmetic_operations.py +1 -0
  7. classiq/interface/generator/register_role.py +8 -0
  8. classiq/interface/model/handle_binding.py +22 -3
  9. classiq/model_expansions/capturing/captured_vars.py +316 -0
  10. classiq/model_expansions/capturing/mangling_utils.py +18 -9
  11. classiq/model_expansions/closure.py +29 -74
  12. classiq/model_expansions/function_builder.py +51 -66
  13. classiq/model_expansions/interpreter.py +4 -7
  14. classiq/model_expansions/quantum_operations/bind.py +1 -3
  15. classiq/model_expansions/quantum_operations/call_emitter.py +46 -11
  16. classiq/model_expansions/quantum_operations/classicalif.py +2 -5
  17. classiq/model_expansions/quantum_operations/control.py +13 -16
  18. classiq/model_expansions/quantum_operations/emitter.py +36 -8
  19. classiq/model_expansions/quantum_operations/expression_operation.py +9 -19
  20. classiq/model_expansions/quantum_operations/inplace_binary_operation.py +4 -6
  21. classiq/model_expansions/quantum_operations/invert.py +5 -8
  22. classiq/model_expansions/quantum_operations/power.py +5 -10
  23. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +1 -3
  24. classiq/model_expansions/quantum_operations/quantum_function_call.py +1 -3
  25. classiq/model_expansions/quantum_operations/repeat.py +3 -3
  26. classiq/model_expansions/quantum_operations/variable_decleration.py +1 -1
  27. classiq/model_expansions/quantum_operations/within_apply.py +1 -5
  28. classiq/model_expansions/scope.py +2 -2
  29. classiq/model_expansions/transformers/var_splitter.py +32 -19
  30. classiq/model_expansions/utils/handles_collector.py +33 -0
  31. classiq/model_expansions/visitors/variable_references.py +18 -2
  32. classiq/qmod/qfunc.py +9 -13
  33. classiq/qmod/quantum_expandable.py +1 -21
  34. classiq/qmod/quantum_function.py +16 -0
  35. {classiq-0.58.0.dist-info → classiq-0.59.0.dist-info}/METADATA +2 -2
  36. {classiq-0.58.0.dist-info → classiq-0.59.0.dist-info}/RECORD +37 -38
  37. classiq/interface/executor/aws_execution_cost.py +0 -90
  38. classiq/model_expansions/capturing/captured_var_manager.py +0 -48
  39. classiq/model_expansions/capturing/propagated_var_stack.py +0 -194
  40. {classiq-0.58.0.dist-info → classiq-0.59.0.dist-info}/WHEEL +0 -0
@@ -9,26 +9,28 @@ from typing import (
9
9
 
10
10
  import sympy
11
11
 
12
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
12
13
  from classiq.interface.generator.expressions.evaluated_expression import (
13
14
  EvaluatedExpression,
14
15
  )
15
16
  from classiq.interface.generator.expressions.expression import Expression
17
+ from classiq.interface.generator.functions.port_declaration import (
18
+ PortDeclarationDirection,
19
+ )
20
+ from classiq.interface.model.handle_binding import HandleBinding
16
21
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
17
22
  from classiq.interface.model.quantum_function_declaration import (
18
23
  NamedParamsQuantumFunctionDeclaration,
19
24
  )
20
25
  from classiq.interface.model.quantum_statement import QuantumOperation, QuantumStatement
21
26
 
22
- from classiq.model_expansions.capturing.propagated_var_stack import (
23
- PropagatedVarStack,
24
- )
25
27
  from classiq.model_expansions.closure import Closure, FunctionClosure, GenerativeClosure
26
28
  from classiq.model_expansions.function_builder import (
27
29
  FunctionContext,
28
30
  OperationBuilder,
29
31
  OperationContext,
30
32
  )
31
- from classiq.model_expansions.scope import Scope
33
+ from classiq.model_expansions.scope import QuantumSymbol, Scope
32
34
  from classiq.model_expansions.sympy_conversion.sympy_to_python import (
33
35
  translate_sympy_quantum_expression,
34
36
  )
@@ -69,10 +71,6 @@ class Emitter(Generic[QuantumStatementT]):
69
71
  return self._generative_contexts[closure.name]
70
72
  return self._interpreter._expand_operation(closure)
71
73
 
72
- @property
73
- def _propagated_var_stack(self) -> PropagatedVarStack:
74
- return self._interpreter._propagated_var_stack
75
-
76
74
  @property
77
75
  def _builder(self) -> OperationBuilder:
78
76
  return self._interpreter._builder
@@ -134,3 +132,33 @@ class Emitter(Generic[QuantumStatementT]):
134
132
  value=evaluated_expression.value
135
133
  )
136
134
  return new_expression
135
+
136
+ def emit_statement(self, statement: QuantumStatement) -> None:
137
+ if isinstance(statement, QuantumOperation):
138
+ self._update_captured_vars(statement)
139
+ self._builder.emit_statement(statement)
140
+
141
+ def _update_captured_vars(self, op: QuantumOperation) -> None:
142
+ handles = (
143
+ [(handle, PortDeclarationDirection.Input) for handle in op.inputs]
144
+ + [(handle, PortDeclarationDirection.Output) for handle in op.outputs]
145
+ + [(handle, PortDeclarationDirection.Inout) for handle in op.inouts]
146
+ )
147
+ for handle, direction in handles:
148
+ self._capture_handle(handle, direction)
149
+
150
+ def _capture_handle(
151
+ self, handle: HandleBinding, direction: PortDeclarationDirection
152
+ ) -> None:
153
+ if handle.name not in self._current_scope:
154
+ return
155
+ defining_function = self._current_scope[handle.name].defining_function
156
+ if defining_function is None:
157
+ raise ClassiqInternalExpansionError
158
+ symbol: QuantumSymbol = self._interpreter.evaluate(handle).value
159
+ self._builder.current_block.captured_vars.capture_handle(
160
+ handle=symbol.handle,
161
+ quantum_type=symbol.quantum_type,
162
+ defining_function=defining_function,
163
+ direction=direction,
164
+ )
@@ -1,9 +1,7 @@
1
1
  import ast
2
- from typing import TYPE_CHECKING, Generic, TypeVar, Union
2
+ from typing import Generic, TypeVar, Union
3
3
 
4
- from classiq.interface.generator.expressions.evaluated_expression import (
5
- EvaluatedExpression,
6
- )
4
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
7
5
  from classiq.interface.generator.expressions.expression import Expression
8
6
  from classiq.interface.generator.visitor import NodeType
9
7
  from classiq.interface.model.control import Control
@@ -18,26 +16,16 @@ from classiq.interface.model.within_apply_operation import WithinApply
18
16
 
19
17
  from classiq.model_expansions.quantum_operations.call_emitter import CallEmitter
20
18
  from classiq.model_expansions.scope import QuantumSymbol
21
- from classiq.model_expansions.transformers.var_splitter import (
22
- SymbolParts,
23
- VarSplitter,
24
- )
19
+ from classiq.model_expansions.transformers.var_splitter import SymbolParts
25
20
  from classiq.model_expansions.visitors.variable_references import VarRefCollector
26
21
 
27
- if TYPE_CHECKING:
28
- from classiq.model_expansions.interpreter import Interpreter
29
-
30
22
  ExpressionOperationT = TypeVar("ExpressionOperationT", bound=QuantumExpressionOperation)
31
23
  AST_NODE = TypeVar("AST_NODE", bound=NodeType)
32
24
 
33
25
 
34
26
  class ExpressionOperationEmitter(
35
- Generic[ExpressionOperationT], CallEmitter[ExpressionOperationT], VarSplitter
27
+ Generic[ExpressionOperationT], CallEmitter[ExpressionOperationT]
36
28
  ):
37
- def __init__(self, interpreter: "Interpreter") -> None:
38
- CallEmitter.__init__(self, interpreter) # type:ignore[arg-type]
39
- VarSplitter.__init__(self, interpreter._current_scope)
40
-
41
29
  def _emit_with_split(
42
30
  self,
43
31
  op: ExpressionOperationT,
@@ -49,9 +37,11 @@ class ExpressionOperationEmitter(
49
37
  bind_ops = self.get_bind_ops(symbol_parts)
50
38
 
51
39
  new_expression = self.rewrite(expression, symbol_parts)
52
- new_expression._evaluated_expr = EvaluatedExpression(
53
- value=self._interpreter.evaluate(new_expression).value
54
- )
40
+ if len(self.split_symbols(new_expression, lambda name: name)) > 0:
41
+ raise ClassiqInternalExpansionError(
42
+ f"Did not replace all handles in expression: {expression.expr!r} -> "
43
+ f"{new_expression.expr!r}"
44
+ )
55
45
  new_op = op.model_copy(update=dict(expression=new_expression))
56
46
  new_op = self._get_updated_op_split_symbols(new_op, symbol_parts)
57
47
 
@@ -83,8 +83,7 @@ class InplaceBinaryOperationEmitter(CallEmitter[InplaceBinaryOperation]):
83
83
  frac_digits_diff == value_var.quantum_type.size_in_bits
84
84
  or -frac_digits_diff == target_var.quantum_type.size_in_bits
85
85
  ):
86
- with self._propagated_var_stack.capture_variables(op):
87
- return
86
+ return
88
87
 
89
88
  value_var = QuantumSymbol(
90
89
  handle=HandleBinding(name="value"), quantum_type=value_var.quantum_type
@@ -122,10 +121,9 @@ class InplaceBinaryOperationEmitter(CallEmitter[InplaceBinaryOperation]):
122
121
  body=body,
123
122
  scope=Scope(parent=self._current_scope),
124
123
  )
125
- with self._propagated_var_stack.capture_variables(op):
126
- self._emit_quantum_function_call(
127
- inplace_binary_op_function, [op.value, op.target]
128
- )
124
+ self._emit_quantum_function_call(
125
+ inplace_binary_op_function, [op.value, op.target]
126
+ )
129
127
 
130
128
  def _emit_constant_operation(self, op: InplaceBinaryOperation) -> None:
131
129
  if TYPE_CHECKING:
@@ -10,15 +10,12 @@ from classiq.model_expansions.scope import Scope
10
10
 
11
11
  class InvertEmitter(CallEmitter[Invert]):
12
12
  def emit(self, invert: Invert, /) -> None:
13
- with self._propagated_var_stack.capture_variables(invert):
14
- self._emit_propagated(invert)
15
-
16
- def _emit_propagated(self, invert: Invert, /) -> None:
17
- if invert.is_generative():
13
+ is_generative = invert.is_generative()
14
+ if is_generative:
18
15
  context = self._register_generative_context(invert, INVERT_OPERATOR_NAME)
19
16
  invert = invert.model_copy(update={"body": context.statements("body")})
20
17
 
21
- if self._should_wrap(invert.body):
18
+ if not is_generative and self._should_wrap(invert.body):
22
19
  self._emit_wrapped(invert)
23
20
  return
24
21
 
@@ -31,7 +28,7 @@ class InvertEmitter(CallEmitter[Invert]):
31
28
  scope=Scope(parent=self._current_scope),
32
29
  )
33
30
  context = self._expand_operation(invert_operation)
34
- self._builder.emit_statement(
31
+ self.emit_statement(
35
32
  Invert(body=context.statements("body"), source_ref=invert.source_ref)
36
33
  )
37
34
 
@@ -39,6 +36,6 @@ class InvertEmitter(CallEmitter[Invert]):
39
36
  wrapping_function = self._create_expanded_wrapping_function(
40
37
  INVERT_OPERATOR_NAME, invert.body
41
38
  )
42
- self._builder.emit_statement(
39
+ self.emit_statement(
43
40
  Invert(body=[wrapping_function], source_ref=invert.source_ref)
44
41
  )
@@ -22,10 +22,6 @@ class PowerEmitter(CallEmitter[Power]):
22
22
  _power_expr: Expression
23
23
 
24
24
  def emit(self, power: Power, /) -> None:
25
- with self._propagated_var_stack.capture_variables(power):
26
- self._emit_propagated(power)
27
-
28
- def _emit_propagated(self, power: Power) -> None:
29
25
  if power.is_generative():
30
26
  context = self._register_generative_context(power, POWER_OPERATOR_NAME)
31
27
  power = power.model_copy(update={"body": context.statements("body")})
@@ -50,7 +46,7 @@ class PowerEmitter(CallEmitter[Power]):
50
46
  scope=Scope(parent=self._current_scope),
51
47
  )
52
48
  context = self._expand_operation(power_operation)
53
- self._builder.emit_statement(
49
+ self.emit_statement(
54
50
  Power(
55
51
  body=context.statements("body"),
56
52
  power=self._power_expr,
@@ -59,11 +55,10 @@ class PowerEmitter(CallEmitter[Power]):
59
55
  )
60
56
 
61
57
  def _emit_wrapped(self) -> None:
62
- with self._propagated_var_stack.capture_variables(self._power):
63
- wrapping_function = self._create_expanded_wrapping_function(
64
- POWER_OPERATOR_NAME, self._power.body
65
- )
66
- self._builder.emit_statement(
58
+ wrapping_function = self._create_expanded_wrapping_function(
59
+ POWER_OPERATOR_NAME, self._power.body
60
+ )
61
+ self.emit_statement(
67
62
  Power(
68
63
  body=[wrapping_function],
69
64
  power=self._power_expr,
@@ -79,8 +79,6 @@ class QuantumAssignmentOperationEmitter(
79
79
 
80
80
  def _emit_op(self, expression: Expression, op: QuantumAssignmentOperation) -> None:
81
81
  op = self._evaluate_types_in_expression(op, expression)
82
- with self._propagated_var_stack.capture_variables(op):
83
- pass # This propagates the expression variables
84
82
  if isinstance(op, ArithmeticOperation):
85
83
  self._emit_arithmetic_op(op, expression)
86
84
  return
@@ -130,7 +128,7 @@ class QuantumAssignmentOperationEmitter(
130
128
  ) -> None:
131
129
  result = self._interpreter.evaluate(op.result_var).as_type(QuantumSymbol)
132
130
  copy_type_information(op.result_type, result.quantum_type, str(result.handle))
133
- self._builder.emit_statement(op)
131
+ self.emit_statement(op)
134
132
 
135
133
  def _optimize_boolean_expression(
136
134
  self, op: ArithmeticOperation, expression: Expression
@@ -18,7 +18,5 @@ class QuantumFunctionCallEmitter(CallEmitter[QuantumFunctionCall]):
18
18
  def emit(self, call: QuantumFunctionCall, /) -> None:
19
19
  function = self._interpreter.evaluate(call.function).as_type(FunctionClosure)
20
20
  args = call.positional_args
21
- with ErrorManager().call(
22
- function.name
23
- ), function.scope.freeze(), self._propagated_var_stack.capture_variables(call):
21
+ with ErrorManager().call(function.name), function.freeze():
24
22
  self._emit_quantum_function_call(function, args)
@@ -23,10 +23,9 @@ class RepeatEmitter(CallEmitter[Repeat]):
23
23
  f"repeat count must be non-negative, got {count}"
24
24
  )
25
25
  for i in range(count):
26
- with self._propagated_var_stack.capture_variables(repeat):
27
- self._emit_propagated(repeat, i)
26
+ self._emit_iteration(repeat, i)
28
27
 
29
- def _emit_propagated(self, repeat: Repeat, i: int) -> None:
28
+ def _emit_iteration(self, repeat: Repeat, i: int) -> None:
30
29
  closure_constructor: type[FunctionClosure]
31
30
  extra_args: dict
32
31
  if repeat.is_generative():
@@ -50,6 +49,7 @@ class RepeatEmitter(CallEmitter[Repeat]):
50
49
  ],
51
50
  body=repeat.body,
52
51
  scope=Scope(parent=self._current_scope),
52
+ is_lambda=True,
53
53
  **extra_args,
54
54
  )
55
55
  self._emit_quantum_function_call(iteration_function, [Expression(expr=str(i))])
@@ -25,4 +25,4 @@ class VariableDeclarationStatementEmitter(Emitter[VariableDeclarationStatement])
25
25
  ),
26
26
  defining_function=self._builder.current_function,
27
27
  )
28
- self._builder.emit_statement(var_decl)
28
+ self.emit_statement(var_decl)
@@ -10,10 +10,6 @@ from classiq.model_expansions.scope import Scope
10
10
 
11
11
  class WithinApplyEmitter(Emitter[WithinApply]):
12
12
  def emit(self, within_apply: WithinApply, /) -> None:
13
- with self._propagated_var_stack.capture_variables(within_apply):
14
- self._emit_propagated(within_apply)
15
-
16
- def _emit_propagated(self, within_apply: WithinApply) -> None:
17
13
  if within_apply.is_generative():
18
14
  within_apply_context = self._register_generative_context(
19
15
  within_apply, WITHIN_APPLY_NAME, ["within", "apply"]
@@ -34,7 +30,7 @@ class WithinApplyEmitter(Emitter[WithinApply]):
34
30
  scope=Scope(parent=self._current_scope),
35
31
  )
36
32
  context = self._expand_operation(within_apply_operation)
37
- self._builder.emit_statement(
33
+ self.emit_statement(
38
34
  WithinApply(
39
35
  compute=context.statements("within"),
40
36
  action=context.statements("apply"),
@@ -34,7 +34,7 @@ from classiq.interface.model.quantum_type import (
34
34
  )
35
35
 
36
36
  if TYPE_CHECKING:
37
- from classiq.model_expansions.closure import Closure
37
+ from classiq.model_expansions.closure import FunctionClosure
38
38
 
39
39
  T = TypeVar("T")
40
40
 
@@ -138,7 +138,7 @@ def _evaluated_to_str_struct_literal(value: QmodStructInstance) -> str:
138
138
  @dataclass(frozen=True)
139
139
  class Evaluated: # FIXME: Merge with EvaluatedExpression if possible
140
140
  value: Any
141
- defining_function: Optional["Closure"] = None
141
+ defining_function: Optional["FunctionClosure"] = None
142
142
 
143
143
  def as_type(self, t: type[T]) -> T:
144
144
  if t is int:
@@ -1,7 +1,7 @@
1
1
  import ast
2
2
  from dataclasses import dataclass
3
3
  from itertools import chain
4
- from typing import TYPE_CHECKING, Callable, TypeVar
4
+ from typing import TYPE_CHECKING, Callable, TypeVar, cast
5
5
 
6
6
  from classiq.interface.exceptions import (
7
7
  ClassiqExpansionError,
@@ -15,6 +15,9 @@ from classiq.interface.model.handle_binding import (
15
15
  NestedHandleBinding,
16
16
  SlicedHandleBinding,
17
17
  )
18
+ from classiq.interface.model.quantum_expressions.quantum_expression import (
19
+ QuantumExpressionOperation,
20
+ )
18
21
  from classiq.interface.model.quantum_type import (
19
22
  QuantumBitvector,
20
23
  QuantumScalar,
@@ -54,8 +57,9 @@ class VarSplitter:
54
57
  vrc.visit(ast.parse(expression.expr))
55
58
  symbol_names_to_split = dict.fromkeys(
56
59
  handle.name
57
- for handle in self._get_handles(vrc)
58
- if isinstance(handle, NestedHandleBinding)
60
+ for handle in vrc.var_handles
61
+ if isinstance(self._scope[handle.name].value, QuantumSymbol)
62
+ and isinstance(handle, NestedHandleBinding)
59
63
  )
60
64
 
61
65
  symbol_handles = {
@@ -85,13 +89,6 @@ class VarSplitter:
85
89
  for symbol, handles in symbol_handles.items()
86
90
  }
87
91
 
88
- def _get_handles(self, collector: VarRefCollector) -> list[HandleBinding]:
89
- return [
90
- handle
91
- for handle in collector.var_handles
92
- if isinstance(self._scope[handle.name].value, QuantumSymbol)
93
- ]
94
-
95
92
  def _get_symbol_parts(
96
93
  self, symbol: QuantumSymbol, target_parts: list[HandleBinding]
97
94
  ) -> list[QuantumSymbol]:
@@ -184,14 +181,9 @@ class VarSplitter:
184
181
  for part in chain.from_iterable(symbol_parts.values())
185
182
  ]
186
183
 
187
- def _check_all_handles_were_replaced(self, new_expr_str: str) -> None:
188
- vrc = VarRefCollector(ignore_duplicated_handles=True)
189
- vrc.visit(ast.parse(new_expr_str))
190
- for handle in self._get_handles(vrc):
191
- if isinstance(handle, NestedHandleBinding):
192
- raise ClassiqInternalExpansionError(f"Did not replace handle {handle}")
193
-
194
184
  def rewrite(self, subject: AST_NODE, symbol_mapping: SymbolParts) -> AST_NODE:
185
+ if len(symbol_mapping) == 0:
186
+ return subject
195
187
  handle_replacements = {
196
188
  part.source_handle: part.target_var_handle
197
189
  for parts in symbol_mapping.values()
@@ -210,6 +202,22 @@ class VarSplitter:
210
202
  def visit_Expression(expr: Expression) -> Expression:
211
203
  return self._rewrite_expression(symbol_mapping, expr)
212
204
 
205
+ def visit_QuantumExpressionOperation(
206
+ self, op: QuantumExpressionOperation
207
+ ) -> QuantumExpressionOperation:
208
+ op = cast(QuantumExpressionOperation, self.generic_visit(op))
209
+ previous_var_handles = list(op._var_handles)
210
+ op._var_handles = self.visit(op._var_handles)
211
+ op._var_types = {
212
+ new_handle.name: op._var_types.get(
213
+ new_handle.name, op._var_types[previous_handle.name]
214
+ )
215
+ for previous_handle, new_handle in zip(
216
+ previous_var_handles, op._var_handles
217
+ )
218
+ }
219
+ return op
220
+
213
221
  return ReplaceSplitVars().visit(subject)
214
222
 
215
223
  def _rewrite_expression(
@@ -232,6 +240,11 @@ class VarSplitter:
232
240
  new_expr_str = new_expr_str.replace(
233
241
  str(handle), handle_names[collapsed_handle]
234
242
  )
235
- self._check_all_handles_were_replaced(new_expr_str)
243
+ if handle.qmod_expr != str(handle):
244
+ new_expr_str = new_expr_str.replace(
245
+ handle.qmod_expr, handle_names[collapsed_handle]
246
+ )
236
247
 
237
- return Expression(expr=new_expr_str)
248
+ new_expr = Expression(expr=new_expr_str)
249
+ new_expr._evaluated_expr = expression._evaluated_expr
250
+ return new_expr
@@ -0,0 +1,33 @@
1
+ import ast
2
+
3
+ from classiq.interface.generator.expressions.expression import Expression
4
+ from classiq.interface.generator.visitor import NodeType, Visitor
5
+ from classiq.interface.model.handle_binding import HandleBinding
6
+ from classiq.interface.model.quantum_expressions.quantum_expression import (
7
+ QuantumExpressionOperation,
8
+ )
9
+
10
+ from classiq.model_expansions.visitors.variable_references import VarRefCollector
11
+
12
+
13
+ class _HandlesCollector(Visitor):
14
+ def __init__(self) -> None:
15
+ self.handles: list[HandleBinding] = []
16
+
17
+ def visit_HandleBinding(self, handle: HandleBinding) -> None:
18
+ self.handles.append(handle)
19
+
20
+ def visit_Expression(self, expression: Expression) -> None:
21
+ vrc = VarRefCollector(ignore_duplicated_handles=True)
22
+ vrc.visit(ast.parse(expression.expr))
23
+ self.handles.extend(vrc.var_handles)
24
+
25
+ def visit_QuantumExpressionOperation(self, op: QuantumExpressionOperation) -> None:
26
+ self.handles.extend(op.var_handles)
27
+ self.generic_visit(op)
28
+
29
+
30
+ def extract_handles(node: NodeType) -> list[HandleBinding]:
31
+ collector = _HandlesCollector()
32
+ collector.visit(node)
33
+ return collector.handles
@@ -80,13 +80,29 @@ class VarRefCollector(ast.NodeVisitor):
80
80
  return handle
81
81
 
82
82
  def visit_Attribute(self, node: ast.Attribute) -> Optional[FieldHandleBinding]:
83
+ return self._get_field_handle(node.value, node.attr)
84
+
85
+ def visit_Call(self, node: ast.Call) -> Optional[FieldHandleBinding]:
86
+ if (
87
+ not isinstance(node.func, ast.Name)
88
+ or node.func.id != "get_field"
89
+ or len(node.args) != 2
90
+ or not isinstance(node.args[1], ast.Constant)
91
+ or not isinstance(node.args[1].value, str)
92
+ ):
93
+ return self.generic_visit(node)
94
+ return self._get_field_handle(node.args[0], node.args[1].value)
95
+
96
+ def _get_field_handle(
97
+ self, subject: ast.expr, field: str
98
+ ) -> Optional[FieldHandleBinding]:
83
99
  with self.set_nested():
84
- base_handle = self.visit(node.value)
100
+ base_handle = self.visit(subject)
85
101
  if base_handle is None:
86
102
  return None
87
103
  handle = FieldHandleBinding(
88
104
  base_handle=base_handle,
89
- field=node.attr,
105
+ field=field,
90
106
  )
91
107
  if not self._is_nested:
92
108
  self._var_handles[handle] = True
classiq/qmod/qfunc.py CHANGED
@@ -27,28 +27,24 @@ def set_discovered_functions(
27
27
  def qfunc(func: Callable) -> QFunc: ...
28
28
 
29
29
 
30
- @overload
31
- def qfunc(*, synthesize_separately: Literal[True]) -> Callable[[Callable], QFunc]: ...
32
-
33
-
34
- @overload
35
- def qfunc(*, external: Literal[True]) -> Callable[[Callable], ExternalQFunc]: ...
36
-
37
-
38
30
  @overload
39
31
  def qfunc(
40
- *, external: Literal[True], synthesize_separately: Literal[True]
32
+ *,
33
+ external: Literal[True],
34
+ synthesize_separately: Literal[False] = False,
41
35
  ) -> Callable[[Callable], ExternalQFunc]: ...
42
36
 
43
37
 
44
38
  @overload
45
- def qfunc(*, generative: Literal[True]) -> Callable[[Callable], GenerativeQFunc]: ...
39
+ def qfunc(
40
+ *,
41
+ generative: Literal[True],
42
+ synthesize_separately: bool = False,
43
+ ) -> Callable[[Callable], GenerativeQFunc]: ...
46
44
 
47
45
 
48
46
  @overload
49
- def qfunc(
50
- *, generative: Literal[True], synthesize_separately: Literal[True]
51
- ) -> Callable[[Callable], GenerativeQFunc]: ...
47
+ def qfunc(*, synthesize_separately: bool) -> Callable[[Callable], QFunc]: ...
52
48
 
53
49
 
54
50
  def qfunc(
@@ -1,5 +1,4 @@
1
1
  import inspect
2
- import warnings
3
2
  from abc import ABC
4
3
  from dataclasses import is_dataclass
5
4
  from enum import Enum as PythonEnum
@@ -19,7 +18,7 @@ import pydantic
19
18
  from sympy import Basic
20
19
  from typing_extensions import Self
21
20
 
22
- from classiq.interface.exceptions import ClassiqDeprecationWarning, ClassiqValueError
21
+ from classiq.interface.exceptions import ClassiqValueError
23
22
  from classiq.interface.generator.expressions.expression import Expression
24
23
  from classiq.interface.generator.functions.concrete_types import (
25
24
  NativePythonClassicalTypes,
@@ -390,7 +389,6 @@ def _get_operand_hint(
390
389
  def _prepare_args(
391
390
  decl: AnonQuantumFunctionDeclaration, arg_list: list[Any], kwargs: dict[str, Any]
392
391
  ) -> list[ArgValue]:
393
- _apply_control_backward_compatibility(decl, kwargs)
394
392
  result = []
395
393
  for idx, arg_decl in enumerate(decl.positional_arg_declarations):
396
394
  arg = None
@@ -413,24 +411,6 @@ def _prepare_args(
413
411
  return result
414
412
 
415
413
 
416
- def _apply_control_backward_compatibility(
417
- decl: AnonQuantumFunctionDeclaration, kwargs: dict[str, Any]
418
- ) -> None:
419
- from classiq.qmod.builtins.functions import __all__ as builtin_functions
420
-
421
- if decl.name in builtin_functions and "control" in kwargs:
422
- warnings.warn(
423
- f"Parameter 'control' of function {decl.name!r} has been renamed to "
424
- f"'ctrl'. Parameter 'control' will no longer be supported starting on "
425
- f"4/11/24 at the earliest.\nHint: Change {decl.name}(control=...) to "
426
- f"{decl.name}(ctrl=...).",
427
- ClassiqDeprecationWarning,
428
- stacklevel=6,
429
- )
430
- kwargs["ctrl"] = kwargs["control"]
431
- kwargs.pop("control")
432
-
433
-
434
414
  def _create_quantum_function_call(
435
415
  decl_: QuantumFunctionDeclaration,
436
416
  index_: Optional[Union[CParamScalar, int]] = None,
@@ -7,6 +7,9 @@ from typing import Any, Callable, Optional, get_origin
7
7
 
8
8
  from classiq.interface.exceptions import ClassiqError
9
9
  from classiq.interface.executor.execution_preferences import ExecutionPreferences
10
+ from classiq.interface.generator.functions.port_declaration import (
11
+ PortDeclarationDirection,
12
+ )
10
13
  from classiq.interface.generator.model.constraints import Constraints
11
14
  from classiq.interface.generator.model.preferences.preferences import Preferences
12
15
  from classiq.interface.generator.types.compilation_metadata import CompilationMetadata
@@ -37,7 +40,20 @@ class BaseQFunc(QExpandable):
37
40
  super().__init__(py_callable)
38
41
  self.compilation_metadata = compilation_metadata
39
42
 
43
+ @property
44
+ def func_decl(self) -> NamedParamsQuantumFunctionDeclaration:
45
+ raise NotImplementedError
46
+
47
+ @property
48
+ def _has_inputs(self) -> bool:
49
+ return any(
50
+ port.direction == PortDeclarationDirection.Input
51
+ for port in self.func_decl.port_declarations
52
+ )
53
+
40
54
  def update_compilation_metadata(self, **kwargs: Any) -> None:
55
+ if kwargs["should_synthesize_separately"] and self._has_inputs:
56
+ raise ClassiqError("Can't synthesize separately a function with inputs")
41
57
  self.compilation_metadata = self._compilation_metadata.model_copy(update=kwargs)
42
58
 
43
59
  @property
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: classiq
3
- Version: 0.58.0
3
+ Version: 0.59.0
4
4
  Summary: Classiq's Python SDK for quantum computing
5
5
  Home-page: https://classiq.io
6
6
  License: Proprietary
@@ -45,7 +45,7 @@ Requires-Dist: numpy (>=1.26.0,<2.0.0) ; python_version >= "3.12"
45
45
  Requires-Dist: packaging (>=23.2,<24.0)
46
46
  Requires-Dist: pandas (>=1.4.0,<3.0.0)
47
47
  Requires-Dist: plotly (>=5.7.0,<6.0.0)
48
- Requires-Dist: pydantic (>=2.9.0,<3.0.0)
48
+ Requires-Dist: pydantic (>=2.9.0,<2.10.0)
49
49
  Requires-Dist: pydantic-settings (>=2.4.0,<3.0.0)
50
50
  Requires-Dist: scipy (>=1.10.0,<2.0.0) ; python_version < "3.12"
51
51
  Requires-Dist: scipy (>=1.11.0,<2.0.0) ; python_version >= "3.12"